Red Hat - Open Innovation Labsのブログ - TECH PLAY

TECH PLAY

Red Hat  - Open Innovation Labs

Red Hat - Open Innovation Labs の技術ブログ

全900件

こんにちは。Red Hatの森( @mosuke5 ) です。 先日シンガポールオフィスにてRed Hatの新しい試みの1つである、DevOps文化を学ぶトレーニングに参加してきたので、そのレポートです。一緒に行った鈴木( @tomoya_su )とともに本ブログを書いています。 1. DevOps文化を学ぶ? みなさんはRed Hatにどんなイメージを持っていますか?おそらく多くの人はRedHat Eterprise Linuxの印象が強いのではないかと思います。もちろん、今でもRedHat Enterprise Linuxは大きな事業の柱ですが、最近ではOpenShiftをはじめコンテナ技術に関する製品に注力していますし、また製品だけではなくてそれを「うまく使いこなせるようになるためのDevOps文化を育む」ためのトレーニングやビジネスを行っています。 その最たる例として、 Open Innovation Labs というがあります。これは、Red Hatのコンサルタントやエンジニア、対象分野の専門家とともに一定期間(標準的なものだと4〜12週間)ともにプロジェクトを進めていくタイプのサービスです。アジャイル開発やDevOps導入の方法論を学び、実践しながら、プロトタイピングしていくといったことを行います。最近ですと日本でも ふくおかフィナンシャルグループ様での事例 などがてきています。 今回は、プロジェクト型として大規模に行うものではなくて、DevOps文化を学ぶための5日間のトレーニングコース「DevOps Culture and Practice Enablement」をシンガポールで受講してきました。 こちらは上で紹介したOpen Innovation labsが実際のプロジェクトベースなものに対して、数日間の一般的な「トレーニング」として提供するものです。日本でも提供を企画しており、提供するにあたっての体験として参加してきました。 開催情報 期間:2019年7月22日〜26日 場所:Red Hatシンガポールオフィス 参加者:日本、中国、韓国、インド、シンガポールなどアジアのリージョンのRed Hat社員20人程度。ロールはコンサルタントやテクニカルトレーナーなど。 www.redhat.com 学んだ内容については、後述するのですが、このトレーニングの面白いところは、アジャイルなカルチャーを学ぶトレーニングをアジャイルなカルチャーで実行して学ぶというメタ的な視点で学ぶことにありました。それでは実際にどんなことを体験してきたのかみていきましょう。 Fun with DevOps 2. どのようなトレーニングか 2.1 全体 このトレーニングは DevOpsのチーム作りや顧客志向などのプラクティスを体験するワークショップと TDDやCICDなどの技術的なプラクティスを実践する技術演習に大きく分かれています。 初日にチーム分けをして、5日間そのチームでプラクティスに取り組みます。 ワークショップ 実施されたトレーニングルームは、DevOps文化を体験する専用の部屋でした。 広い部屋でしたが講師を含めて30名近い人数がいたので、ちょうど良い大きさでした。 また、壁や窓が広く使える点とホワイトボードがいたるところにあるなど、DevOpsを体験するための環境としてとても工夫されている空間でした。 クラスルーム 2.2 プラクティス編 本トレーニングでは、基本的にワークショップ形式が中心となって進められていきます。 DevOpsのプラクティスの概要について説明をうけて、そのプラクティスを同じチームのメンバーと一緒にワークショップで取り組みます。 どのようなプラクティスを体験したかというと、例えば下記のようなものです。 最高のチームを作るプラクティス 「社会契約」 「チーム感情の評価」 顧客を深く理解するプラクティス 「ユーザーストーリーマッピング」 「共感マッピング」 システム・プロセスを深く理解するプラクティス 「イベントストーミング」 「バリューストリームマッピング」 上は例なので他にもOpenPracticeLibraryにあるようなプラクティスを実際に体験しました。 ちなみに、OpenPracticeLibraryはRedHatがDevOpsで役立つプラクティスについて公開しているサイトです。 openpracticelibrary.com 余談ではありますが、下記サイトにてOpenPracticeLibraryにあるプラクティスを日本語で紹介する取り組みも進めています。 https://tomosuzu.com/practices/ 知識としてプラクティスを理解するだけではなく、 プラクティスを実際に体験するということはとても重要です。 プラクティスの方法を学ぶだけであれば、おそらく5日間もかからないと思いますし、 書籍や上記のサイトを見るだけでも学べます。 自転車がどういうもので、どうやって乗るのかを知識として知ってはいても、 実際に自転車に乗ってみると乗れないのと一緒で、 プラクティスも知識として知っていても使いこなすことは非常に難しいものです。 実際に体験し、どういうものか、どういうメリットがあるのかを体で感じることが大切です。 このトレーニングでは、各プラクティスの紹介時に簡単な問題が用意されており、 その問題をチームで取り組むことでプラクティスを簡単にではありますが体験できます。 この点がこのトレーニングの最大のメリットのひとつであり、 自分自身の成長はもちろん、組織にDevOpsの文化を取り入れていくためにもとても大事なことです。 また、簡単な体験だけではなく、実際の仕事でそのプラクティスがどう生かされているのかを実際のプロジェクトを元に紹介してもらえます。 講師の方々は、いくつものDevOpsのプロジェクト(Open Innovation Labs)を実施してきたコンサルタントがほとんどです。 ◯◯社のプロジェクトでは、このプラクティスをこうやって使っていて、こんな効果があった などのように実例を元に説明をしていただけたので、トレーニングだからうまくいくのではないかという疑念を払拭でき、説得力がありました。 2.3 技術編 DevOpsのカルチャーを学ぶトレーニングですが、技術もその重要な要素です。DevOpsを行うにあたって特に「自動化」や「テスト」は欠かせないポイントです。アジャイルの開発プロセスでは、仮説と検証を繰り返しながらサービスに必要な機能やそのありかたを探っていきます。リリース作業やテストの作業が自動化されていないことには、早いサイクルで仮説検証を行っていくことができなくなってしまうため非常に重要なトピックです。 環境概要 今回のトレーニングではOpenShift(Red Hatの提供するKubernetesディストリビューション)が用意されており、その上に開発・CICDを行うのに必要なGitlabやJenkins、Docker Imageレジストリなどを構築して使っていきました。さらに、実際にシンプルなサンプルアプリケーションを利用して、CICDやTDDなどを体験していくものとなります。サンプルアプリケーションはTODOアプリなのですが、フロントエンドはVue.js、APIサイドはNode.js+mongoDBで動くという構成でした。 以下は、今回実際に触ってみた全体像になります。 全体構成 重要なポイント 技術編では資料に沿って手順をこなしていけば完成はするのですが、いくつか重要なポイントがありました。独断ではあるのですがいくつかピックアップしてまとめてみます。 1. Ansibleを使って、OpenShiftの設定のセットアップを自動化すること OpenShiftそのもののデプロイではなく、利用するネームスペースや共通で使うJenkinsなどのコンポーネントの立ち上げをAnsibleで行いました。Configuration as Codeと呼んでいましたが、他のチームでの構築や再構築が可能なようにするために自動化をしていきました。ツールは何でもいいのですが、再現可能にして環境を複製したり設定の反映の自動化ができるようになることが重要でした。 2. Jenkinsでビルド、テスト、リリースなどを自動化するパイプラインを作ること 基本的なCI/CDですが、アジャイル開発での仮説と検証をすばやく行えるようにするためにJenkinsを利用したビルド、テスト、リリースなどの作業を行いました。 こちらを行うことで、提携の作業をなくして、より開発やチームでのワークを増やすことができます。 3. サンプルアプリケーションで実践すること サンプルのアプリはシンプルではありますが、Front-endアプリケーション(Vue.js)とAPIアプリケーション(Node.js)に別れた構成になっていていました。それぞれでの役割を意識しながら、環境を作っていくことが非常に重要です。パイプラインの作成だけでなく、TDDやE2Eテスト、非機能要件のチェックなどの体験をすることができました。 4. わからないことはペアで助け合いながらすすめること 本トレーニングには必ずしもOpenShiftやCICDに長けたエンジニアだけではなく、プロジェクトマネージャ職など様々な人達が参加していました。わからない部分はチーム内でお互いに助け合いながらすすめていきます。これも、多様性を重視したクロスファンクショナルなチームでものごとをすすめるための重要な要素でした。 3. まとめ DevOpsのカルチャーを学ぶトレーニング、ということで、ただ単によくある手法や技術を学ぶだけでなく、実際にチームを組んで実践していくというを体験してきました。頭だけでの理解ではなく、そのカルチャーの中に5日間身をおいて活動する楽しさがありました。ツールやプロダクトだけでなく、それを使いこなしていく最高のチーム・効率的な開発プロセスを学んでみたい方は多いはずです。日本での提供がいつになるかはまだ未定ですが、検討と準備をしている最中ですので、進捗あり次第またご報告させていただければと思います。
お疲れ様です!Red Hatの鈴木(tomosuzu)です! 今回は「ふりかえり 」です。 個人的な意見ですが、最も導入しやすく、最も効果が高く、最も汎用性の高いプラクティスだと考えています。 とてもシンプルな手法ですが、奥が深く、効果的に運用できれば、 投資以上の成果を得られると思います。 下記のリンクからご覧ください。 tomosuzu.com 次回は「スプリントプランニング」を紹介します。 さいごに Red Hatでは、「アイデアをイノベーションに変える、文化・プロセス・技術・実践の場」として、 Open Innovation Labs を提供しています。 デザイン思考やDevOps、アジャイルやリーン開発などをRed Hatのコンサルタントと一緒に取り組むことができます。 過去記事 アジャイル開発のプラクティス一覧をご紹介いたします - 赤帽エンジニアブログ チームの知識共有とコミュニケーションを増やします「ペアプログラミング 」 - 赤帽エンジニアブログ 速い開発速度を保ち、着実に開発を進める「テスト駆動開発」 - 赤帽エンジニアブログ チームの自律性・自主性を高める「社会契約」 - 赤帽エンジニアブログ チームの健康状態を知り、様々な問題に事前に対処「チーム感情の評価」 - 赤帽エンジニアブログ
こんにちは、Red HatのChristian Hornです。 なぜ、あるシステムが負荷が高いのですか。 昨日、パフォーマンスの問題がありました。原因は何でしたか。 様々なシステムなネットワークのスループットを図で見たいですが、何を使ったらよいですか。 その様な質問がありましたら、RHELに入っているPerformance Co-Pilotを使った方がよいです! 「PCP入門」というスライドがあります。 redhat.slides.com
お疲れ様です!Red Hatの鈴木(tomosuzu)です! 前回に引き続き、チーム作りに効果的なプラクティスをご紹介します。 今回は「チーム感情の評価 」です。 当然ですが、ソフトウェアを開発しているのは人です。機械ではありません。 チームの状態、人の状態を知ることは プロダクトの状態を知るのと同等かそれ以上に大切なことです。 下記、リンクからご覧ください。 tomosuzu.com 次回は「ふりかえり」を紹介します。 さいごに Red Hatでは、「アイデアをイノベーションに変える、文化・プロセス・技術・実践の場」として、 Open Innovation Labs を提供しています。 デザイン思考やDevOps、アジャイルやリーン開発などをRed Hatのコンサルタントと一緒に取り組むことができます。 過去記事 アジャイル開発のプラクティス一覧をご紹介いたします - 赤帽エンジニアブログ チームの知識共有とコミュニケーションを増やします「ペアプログラミング 」 - 赤帽エンジニアブログ 速い開発速度を保ち、着実に開発を進める「テスト駆動開発」 - 赤帽エンジニアブログ チームの自律性・自主性を高める「社会契約」 - 赤帽エンジニアブログ
Red HatでOpenShiftのサポートエンジニアをしている林です。 OpenShift 4をベアメタル、KVMなどで構築(UPI)するには、RHEL CoreOS(RHCOS)のマシンを起動する必要があります。そのための方法は大雑把に言って以下の二つです。 RHCOSのインストーラーイメージを起動し、インストールメニューでIgnitionやカーネルイメージのURLを指定する PXEブートで、Ignitionやカーネルイメージの場所が指定されたブート構成ファイルを使用する どちらにしても、Ignitionやカーネルイメージなどの資源をホストする環境を用意する必要があります。 MatchBoxは、RHCOSをネットワークブートする際に必要なIgnitionやカーネルイメージ、iPXEスクリプトなどをホストしてくれるWebアプリケーションです。 github.com MACアドレスなどのマシン属性を引数にして様々なIgnitionやスクリプトを返してくれるので、例えばKVMでMACアドレスを指定して起動すれば、ユーザー入力なしでRHCOSの起動が完了します。 この記事では、MatchBoxを使って、KVM上でRHCOSを自動インストールして起動する手順を紹介します。 ちなみに、マッチ箱(MatchBox)でRHCOSを点火(Ignition)して起動する、的なアナロジーみたいですね。センスに溢れすぎてて震える。 マシン構成 MatchBox環境のセットアップ MatchBoxのインストール、起動 MatchBoxにRHCOS起動用のリソースを配置する MatchBoxにRHCOS起動用のプロファイルを作成する PXE環境とブートの流れを確認 RHCOSの起動 OpenShift 4でRHCOSを起動する場合 起動しない場合の確認 マシン構成 今回紹介するマシン構成は以下の通りです。 KVMホスト / base.examle.com PXEインフラ用VM / pxe.example.com dnsmasq(tftp) MatchBox RHCOSのVM / coreos.example.com PXE環境としてdnsmasqでDHCP Proxy / tftpサーバーを立てて、さらにMatchBoxも同居している構成ですね。 なお、KVMの各VMはブリッジネットワークで起動しています。 MatchBox環境のセットアップ 大体はGitHub上の手順に従えばセットアップできます。 https://github.com/poseidon/matchbox/blob/v0.8.0/Documentation/deployment.md MatchBoxのインストール、起動 まず、PXEインフラVMにMatchBoxをインストールして起動します。 ## MatchBoxのダウンロードとインストール [cloud-user@pxe.example.com ~]$ curl -OL https://github.com/poseidon/matchbox/releases/download/v0.8.0/matchbox-v0.8.0-linux-amd64.tar.gz [cloud-user@pxe.example.com ~]$ tar -xzf matchbox-v0.8.0-linux-amd64.tar.gz [cloud-user@pxe.example.com ~]$ cd matchbox-v0.8.0-linux-amd6 [cloud-user@pxe.example.com ~]$ sudo cp matchbox /usr/local/bin [cloud-user@pxe.example.com ~]$ sudo cp contrib/systemd/matchbox-local.service /etc/systemd/system/matchbox.service ## MatchBoxの起動に必要なディレクトリとユーザーを作成 [cloud-user@pxe.example.com ~]$ sudo mkdir /etc/matchbox [cloud-user@pxe.example.com ~]$ sudo mkdir -p /var/lib/matchbox/{assets,groups,ignition,profiles} [cloud-user@pxe.example.com ~]$ sudo useradd -U -r matchbox ## MatchBoxの起動 [cloud-user@pxe.example.com ~]$ sudo systemctl daemon-reload [cloud-user@pxe.example.com ~]$ sudo systemctl enable --now matchbox.service [cloud-user@pxe.example.com ~]$ systemctl status matchbox ## ipxeのブートスクリプトを取得できることを確認 [cloud-user@pxe.example.com ~]$ curl http://localhost:8080/boot.ipxe #!ipxe chain ipxe?uuid=${uuid}&mac=${mac:hexhyp}&domain=${domain}&hostname=${hostname}&serial=${serial} MatchBoxにRHCOS起動用のリソースを配置する 次に、MatchBoxでホストする、RHCOSの起動に必要なリソースをダウンロードしてMatchBoxから取得できるようにします。 [cloud-user@pxe.example.com ~]$ sudo su - [root@pxe.example.com ~]# cd /var/lib/matchbox/assets [root@pxe.example.com ~]# RHCOS_BASEURL=https://mirror.openshift.com/pub/openshift-v4/dependencies/rhcos/ [root@pxe.example.com ~]# curl -OL ${RHCOS_BASEURL}/4.1/latest/rhcos-4.1.0-x86_64-installer-initramfs.img [root@pxe.example.com ~]# curl -OL ${RHCOS_BASEURL}/4.1/latest/rhcos-4.1.0-x86_64-installer-kernel [root@pxe.example.com ~]# curl -OL ${RHCOS_BASEURL}/4.1/latest/rhcos-4.1.0-x86_64-metal-bios.raw.gz ## 確認 [root@pxe.example.com ~]# curl -O http://localhost:8080/assets/rhcos-4.1.0-x86_64-installer-initramfs.img [root@pxe.example.com ~]# curl -O http://localhost:8080/assets/rhcos-4.1.0-x86_64-installer-kernel [root@pxe.example.com ~]# curl -O http://localhost:8080/assets/rhcos-4.1.0-x86_64-metal-bios.raw.gz MatchBoxにRHCOS起動用のプロファイルを作成する MatchBoxは、URLの引数にMACアドレスなどを渡すことで、RHCOS起動に必要な様々な情報を返してくれるWebサーバーです。 そのために、まず起動構成やIgnitionの情報などを記載したプロファイルを作成します。 [root@pxe.example.com ~]# cat <<'EOF' > /var/lib/matchbox/profiles/playground.json { "id": "playground", "name": "RHCOS - Playground", "ignition_id": "playground.ign", "boot": { "kernel": "/assets/rhcos-4.1.0-x86_64-installer-kernel", "initrd": [ "/assets/rhcos-4.1.0-x86_64-installer-initramfs.img" ], "args": [ "ip=dhcp", "rd.neednet=1", "console=tty0", "console=ttyS0", "coreos.inst=yes", "coreos.inst.install_dev=vda", "coreos.inst.image_url=http://pxe.example.com:8080/assets/rhcos-4.1.0-x86_64-metal-bios.raw.gz", "coreos.inst.ignition_url=http://pxe.example.com:8080/ignition?mac=${mac:hexhyp}" ] } } EOF そして、上記のプロファイルで指定したIgnitionファイルを、 ignition_id と同名のファイル名で作成します。 [root@pxe.example.com ~]# cat <<EOF > /var/lib/matchbox/ignition/playground.ign { "ignition": { "version": "2.2.0" }, "passwd": { "users": [ { "name": "core", "sshAuthorizedKeys": [ "ssh-rsa AAAAB3NzaC <ログインで使用するSSH公開鍵>" ] } ] }, "storage": { "files": [ { "filesystem": "root", "group": {}, "path": "/etc/hostname", "user": {}, "contents": { "source": "data:text/plain;charset=utf-8,coreos.example.com", "verification": {} }, "mode": 420 } ] } } EOF 最後に、MACアドレスとプロファイルを紐付ける group 設定を作成します。MACアドレスは、KVMでVMを起動する時に任意のものを指定できるので、ここでは 02:1a:4a:16:01:9c を使用することにします。 [root@pxe.example.com ~]# cat <<EOF > /var/lib/matchbox/groups/playground.json { "id": "playground-1", "name": "RHCOS - Playground 1", "profile": "playground", "selector": { "mac": "02:1a:4a:16:01:9c" } } EOF さて、ここまでの設定ができていれば、以下のようにURLを叩くことで必要なIginitionを取得できることを確認できるかと思います。 [cloud-user@pxe.example.com ~]$ curl http://localhost:8080/ignition?mac=02-1a-4a-16-01-9c { "ignition": { "version": "2.2.0" }, "passwd": { "users": [ { "name": "core", "sshAuthorizedKeys": [ "ssh-rsa AAAAB3Nz ..." ] } ] }, "storage": { "files": [ { "filesystem": "root", ... PXE環境とブートの流れを確認 ここでは、PXE環境をdnsmasqで構築しています。dnsmasqで以下のように設定し、tftpサーバーを有効にしています。 私の環境では、DHCPサーバーはネットワーク上に既にあったため、ここでは、DHCP Proxyを使って構成します。 基本的に、以下のGitHubドキュメントに記載されているとおりで問題ないかなと思います。 https://github.com/poseidon/matchbox/blob/v0.8.0/Documentation/network-setup.md#proxy-dhcp [root@pxe.example.com ~]# cat /etc/dnsmasq.conf # 既存のDHCPサーバーが割り当てているIPレンジ dhcp-range=192.168.1.1,proxy,255.255.255.0 # TFTP有効化 enable-tftp tftp-root=/var/lib/tftpboot # PXEリクエストの場合はTFTPでiPXEをロード(chainload)する pxe-service=tag:#ipxe,x86PC,"PXE chainload to iPXE",undionly.kpxe # iPXE user classからのリクエストの場合はipxeタグをセットする dhcp-userclass=set:ipxe,iPXE # ipxeタグ付きのリクエストでmatchboxがホストするスクリプトをロードする pxe-service=tag:ipxe,x86PC,"iPXE",http://pxe.example.com:8080/boot.ipxe # verbose log-queries log-dhcp TFTPからiPXEをロード できるように、 undionly.kpxe をダウンロードしてTFTPのルートに配置します。 [root@pxe.example.com ~]# curl -OL http://boot.ipxe.org/undionly.kpxe [root@pxe.example.com ~]# mv undionly.kpxe /var/lib/tftpboot/ 上記により、iPXEで以下のブートスクリプトが起動するはずです。念のため、ブートスクリプトを確認してみます。 ## iPXEブートスクリプト [root@pxe.example.com ~]# curl http://localhost:8080/boot.ipxe #!ipxe chain ipxe?uuid=${uuid}&mac=${mac:hexhyp}&domain=${domain}&hostname=${hostname}&serial=${serial} ## chain先のスクリプト [root@pxe.example.com ~]# curl http://localhost:8080/ipxe?mac=02-1a-4a-16-01-9c #!ipxe kernel /assets/rhcos-4.1.0-x86_64-installer-kernel ip=dhcp rd.neednet=1 console=tty0 console=ttyS0 coreos.inst=yes coreos.inst.install_dev=vda coreos.inst.image_url=http://pxe.example.com:8080/assets/rhcos-4.1.0-x86_64-metal-bios.raw.gz coreos.inst.ignition_url=http://pxe.example.com:8080/ignition?mac=${mac:hexhyp} initrd /assets/rhcos-4.1.0-x86_64-installer-initramfs.img boot RHCOSの起動 これで、ブートの環境ができたので、後はKVMでVMを立ち上げればRHCOSが起動します。 [root@base.example.com ~]# virt-install --name "rhcos-playground" \ --mac "02:1a:4a:16:01:9c" \ --memory 2046 --disk size=16 \ --network bridge=br0,model=virtio \ --os-variant rhel8.0 \ --debug --pxe ## 起動したVMのIPを確認するためコンソールにアクセスする [root@base.example.com ~]# virsh console rhcos-playground Connected to domain rhcos-playground Escape character is ^] Red Hat Enterprise Linux CoreOS 410.8.20190520.0 (Ootpa) 4.1 ... enp1s0: 192.168.1.xx ... coreos login: ^] ## 上記で確認したIPにSSHでログインする [root@base.example.com ~]# ssh -i <ignitionで指定した鍵> core@192.168.1.xx ... [core@coreos ~]$ OpenShift 4でRHCOSを起動する場合 今回は、RHCOSを単独で起動しましたが、RHCOSは、本来、OpenShift専用のOSですので、実際はOpenShift構成時に起動することになります。 以下の記事で田中さんが詳細について記載されているので、ご確認ください。 rheb.hatenablog.com MatchBoxを使う場合は、 openshift-install コマンドで生成されるIgnitionファイルを、MatchBoxのデータディレクトリ /var/lib/matchbox にコピーして、そのIgnitionファイルを参照するようプロファイルを作成すれば、簡単にOpenShift用のノードを起動することができるわけですね。 起動しない場合の確認 ファイアウォールが正しく設定されているか DHCPサーバーが、適切なDNSサーバーのIPを配っているか virt-installで起動する時にMACアドレスを正しく指定しているか
この記事は Red Hat Developer の Red Hat Enterprise Linux 8 Image Builder: Building custom system images を、許可をうけて翻訳したものです。 ::: 投稿者Ricardo Gerardi 2019年5月8日 ::: Image Builder のインストール Webコンソールを使用してカスタムイメージを作成する イメージのブループリントを作成する イメージ作成を開始する イメージを使用して仮想マシンを作成する CLIを使用してカスタムイメージを作成する イメージをダウンロードして仮想マシンを作成する まとめ Red Hat Enterprise Linux 8にはImage Builderという新しいツールが同梱されており、これを使用してカスタムRed Hat Enterprise Linuxシステムイメージをさまざまな形式で作成できます。これらのイメージは、主要なクラウドプロバイダーおよび仮想化技術と互換性があります。その結果、要件に応じて、さまざまなプラットフォームで新しいRed Hat Enterprise Linux(RHEL)システムを迅速に立ち上げることができます。 この記事では、Red Hat Enterprise Linux 8でImage Builderを設定し、2つのイメージを作成することで機能をためしていきます。Red Hatは、Image Builderを専用の仮想マシンで実行することをお勧めします。 このチュートリアルでは、Image BuilderをインストールするRed Hat Enterprise Linux 8を実行している仮想マシンが必要です。この仮想マシンはサブスクリプションを購読しており、Red Hat Enterprise Linux 8パッケージリポジトリにアクセスできる必要があります。この記事では、Red Hat Enterprise Linux 8のインストールについては説明しません。詳しくは、 製品ドキュメント を参照してください。 Image Builder のインストール Image Builderは、「lorax-composer」パッケージによって提供される機能を利用します。Image Builderには特定のセキュリティ要件があるため、Image Builderを専用の仮想マシンにインストールすることをお勧めします。このチュートリアルでは、Image Builderのインストールに、「rh8comp2」という名前の仮想マシンを使用します。 WebコンソールインターフェースによるGUI、または composer-cli パッケージをインストールすることによりCLIを通して、Image Builderを使用できます。このチュートリアルでは、両方を組み合わせて使用します。 このコマンドを実行して必要なパッケージをインストールします。 # yum install -y lorax-composer composer-cli cockpit-composer cockpit(Webコンソールを有効にするため)がインストールされていない場合は、このコマンドによって依存パッケージもダウンロードされます。 次に、ファイアウォールがWebコンソールへのアクセスを許可していることを確認します。 # firewall-cmd --add-service=cockpit && firewall-cmd --add-service=cockpit --permanent Warning:ALREADY_ENABLED: 'cockpit' already in 'public' success Warning:ALREADY_ENABLED: cockpit success # firewall-cmd --list-services cockpit dhcpv6-client ssh 最後に、「lorax-composer」および「cockpit」サービスを有効にして開始します。 # systemctl enable lorax-composer.socket Created symlink /etc/systemd/system/sockets.target.wants/lorax-composer.socket → /usr/lib/systemd/system/lorax-composer.socket. # systemctl enable cockpit.socket Created symlink /etc/systemd/system/sockets.target.wants/cockpit.socket → /usr/lib/systemd/system/cockpit.socket. # systemctl start lorax-composer # systemctl start cockpit これで、Image Builderを起動して実行できます。次に、WebコンソールのGUIを使用してImage Builderを使用してイメージを作成しましょう。 Webコンソールを使用してカスタムイメージを作成する Image BuilderとWebコンソールをインストールしたら、ブラウザで仮想マシンのホスト名またはIPアドレスのポート9090を指定してシステム管理GUIにアクセスできます。管理者ユーザーの資格情報を使用してログインします。 ログインしたら、メニューの左側にあるImage Builderアイコンをクリックしてください。 イメージのブループリントを作成する 次に、イメージのブループリントを作成します。ブループリントは、あなたのイメージに何を含めるべきかを定義します。WebコンソールのGUIからは、含めるrpmパッケージのみを指定できます。ユーザーを指定するには、このチュートリアルの後半でComposer CLIを使用します。 [ブループリントの作成]ボタンをクリックして新しいブループリントを作成します。その後、ブループリントの名前と説明を指定します。この例では、Goで書かれたプログラムをコンパイルするためのツールを含むイメージを作成しています。イメージの名前はdev-golang-1です。 次の画面で、イメージに含めるパッケージを選択します。左側のフィルタバーを使用すると、パッケージを見つけやすくなります。たとえば、tmuxと入力してEnterキーを押すと、tmuxパッケージが表示されます。名前の横にあるプラス(+)記号をクリックして、パッケージをブループリントに追加します。GUIが自動的にパッケージの依存関係を追加することに注意してください。この例では、ブループリントに次のパッケージを追加しています。 cockpit openssh golang tmux 利用可能なパッケージのバージョンはシステムによって異なる場合があります。 必要なパッケージをすべて含めたら、画面上部の[確定]ボタンをクリックして変更を確定します。ポップアップ画面で変更を確認し、[確定]ボタンをクリックして確定します。 イメージ作成を開始する ブループリントが定義されたので、右上の[Create Image]ボタンをクリックしてイメージ作成プロセスを開始できます。ポップアップ画面で、イメージの種類を選択します。Image Builderは、AWS、Azure、OpenStack、VMwareなどのさまざまなイメージを作成できます。この例では、ローカルKVM仮想マシンをデプロイするため、QEMU QCOW2形式を選択して[作成]をクリックします。 イメージを作成するのに数分かかることがあります。ブループリントに移動して[イメージ]タブをクリックすると、Webコンソールで進行状況を確認できます。 lorax-composerユニットのjournalctlログをチェックして、Image Builder仮想マシンの詳細なログを追跡することもできます。 # journalctl -fu lorax-composer イメージが作成されたら、イメージ名の横にある[ダウンロード]ボタンをクリックしてダウンロードします。 イメージを使用して仮想マシンを作成する これで、このイメージを使ってKVM / Libvirt上に仮想マシンを作成できます。このイメージにはユーザーを指定していないため、virt-customizeなどのlibvirtツールを使用してイメージをさらにカスタマイズしてから使用します。 $ sudo virt-customize -a f492077c-6d4b-458a-8ac6-bfbc49fae499-disk.qcow2 --root-password password:test123 --hostname dev01 [ 0.0] Examining the guest ... [ 5.8] Setting a random seed [ 5.8] Setting the hostname: dev01 [ 5.8] Setting passwords [ 6.9] Finishing off 最後に、新しいイメージを使って仮想マシンを作成します。 次のステップでは、Image Builder CLIを使用してユーザーをイメージに直接追加します。 CLIを使用してカスタムイメージを作成する Webコンソールを使用するだけでなく、Image Builder CLIを使用してイメージを作成することもできます。CLIを使用すると、ユーザーやグループをイメージに追加するなど、いくつか追加のカスタマイズオプションを指定できます。Image Builder仮想マシンにcomposer-cliパッケージをすでにインストールしているので、それを使用しましょう。 Image Builder仮想マシンrh8comp2にrootとしてログインします。まず、利用可能なすべてのブループリントを一覧表示します。 # composer-cli blueprints list dev-golang-1 example-atlas example-development example-http-server ご覧のとおり、GUIを使用して作成したdev-golang-1ブループリントが利用可能です。このブループリントを編集して、管理者ユーザーを含めます。ブループリントを編集するには、以下のコマンドでファイルに保存します。 # composer-cli blueprints save dev-golang-1 # ls -l dev-golang-1.toml -rw-r--r--.1 root root 295 Mar 15 14:38 dev-golang-1.toml このコマンドは、ブループリントを TOML 形式で保存します。 次に、dev-golang-1.tomlファイルを編集して新しいユーザーを追加します。 # vi dev-golang-1.toml name = "dev-golang-1" description = "Golang Development Image" version = "0.0.2" modules = [] groups = [] [[packages]] name = "cockpit" version = "*" [[packages]] name = "golang" version = "*" [[packages]] name = "openssh" version = "*" [[packages]] name = "tmux" version = "*" ご覧のとおり、このファイルには、GUIを使用してブループリントを作成するときに含めたすべてのパッケージが含まれています。新しいユーザーを追加するために、必要なカスタマイズを含む新しいセクションを含めます。その前に、バージョンを0.0.1上げて、ブループリントに新しいマイナーバージョンを与えます。 version = "0.0.3" ここで、ユーザー名、パスワード、SSHキー、グループなど、作成したいユーザーに関する詳細を含むセクションcustomizations.usersを追加します。 [[customizations.user]] name = "ricardo" description = "Ricardo user account" password = "$6$nt7IeYW0hFj3ShZC$FdBCgZpfIi92q0sdcTozX1z8KnDAxtxy2b4A76fP4.QKy9kyLS82Vci76BjJFIMp3RHJNCBqUq.aihV0icdHf1" key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHmjYFDBCrB1mgacb47t+y8UXSscnJl2WWlJluzqtInpT6At0nwqtdV3niYTHxju7e/As4MX3iwC8ubVp2DH8qXgvorDliV9SsIQTqvNKlwGkxZ5cqfYFlV4SUuS7tVTOg0yIqVSddZ2t0Sjmdp3PF7zrp6ayH7a9BBA0/8HQXU/lpdk76SGYL9L8PHOtMYnxtmr+WduoJ+X9zO9d3SUypX36NleFqhlpr1UfnSSkFO/PfRYUhry6HEmUk3Da7aS9hNS0lX/j6uf9RnSrNSzquVezyVMgsRnJ+5xr7KyhwtEig//Wr/j8TWmqvj645IWXTmj6Jw4uvi26bEORZVM5x ricardo@localhost" home = "/home/ricardo/" shell = "/usr/bin/bash" groups = ["users", "wheel"] このコマンドを使用して、SHA512アルゴリズムを使用してパスワードのハッシュを生成できます。 # python3 -c "import crypt, getpass; print(crypt.crypt(getpass.getpass(), crypt.METHOD_SHA512))" Password: $6$nt7IeYW0hFj3ShZC$FdBCgZpfIi92q0sdcTozX1z8KnDAxtxy2b4A76fP4.QKy9kyLS82Vci76BjJFIMp3RHJNCBqUq.aihV0icdHf1 Python 3が利用できない場合は、yumを使ってインストールします。 # yum -y install python3 ファイルの編集が終わったら、変更を保存して、新しいバージョンのブループリントをImage Builderにプッシュします。 # composer-cli blueprints push dev-golang-1.toml 次に、 QCOW2 形式を指定して、イメージ作成プロセスを開始します。 # composer-cli compose start dev-golang-1 qcow2 Compose e63c7b29-df96-4f8c-aeb4-c2b0a98bd05e added to the queue そして、以下のように作成状況を確認してください。 # composer-cli compose status e63c7b29-df96-4f8c-aeb4-c2b0a98bd05e RUNNING Fri Mar 15 14:43:35 2019 dev-golang-1 0.0.3 qcow2 f492077c-6d4b-458a-8ac6-bfbc49fae499 FINISHED Fri Mar 15 14:14:13 2019 dev-golang-1 0.0.2 qcow2 1990852608 イメージをダウンロードして仮想マシンを作成する 完了するまでステータスを確認し続けることができます。イメージの作成には数分かかります。 # composer-cli compose status f492077c-6d4b-458a-8ac6-bfbc49fae499 FINISHED Fri Mar 15 14:14:13 2019 dev-golang-1 0.0.2 qcow2 1990852608 e63c7b29-df96-4f8c-aeb4-c2b0a98bd05e FINISHED Fri Mar 15 14:49:17 2019 dev-golang-1 0.0.3 qcow2 1990983680 完了したら、CLIを使用してイメージをダウンロードできます。 # composer-cli compose image e63c7b29-df96-4f8c-aeb4-c2b0a98bd05e e63c7b29-df96-4f8c-aeb4-c2b0a98bd05e-disk.qcow2:1898.75 MB または、Webコンソールを使用してイメージの作成ステータスを確認したり、新しいイメージをダウンロードしたりできます。 イメージを取得したら、さきほどと同様に、そのイメージを使用してKVMを使用して新しい仮想マシンを起動できます。前回との違いは、イメージにはすでに管理者ユーザーがいるため、作成する必要がないことです。SSHキーを提供したので、パスワードなしでそれを使って新しいシステムにログインします。 $ ssh ricardo@192.168.122.227 The authenticity of host '192.168.122.227 (192.168.122.227)' can't be established. ECDSA key fingerprint is SHA256:2xZm9mk8qrCUcwVb9dyb9Pvj21T021/wiXrD96nFKgE. Are you sure you want to continue connecting (yes/no)? yes Warning:Permanently added '192.168.122.227' (ECDSA) to the list of known hosts. Activate the web console with: systemctl enable --now cockpit.socket 新しいシステムにログインしたら、Goがブループリントに従ってインストールされていることを確認します。 [ricardo@localhost ~]$ go version go version go1.10.3 linux/amd64 [ricardo@localhost ~]$ Image Builder CLIを使用すると、イメージ作成プロセスを自動化してCI / CDパイプラインと統合できます。 まとめ Image Builderツールは、さまざまなクラウドおよび仮想化プラットフォームで新しいRed Hat Enterprise Linuxシステムを素早く立ち上げることができるカスタムシステムイメージを設定および作成するための多用途のソリューションです。 Image Builderの詳細については、Red Hat Enterprise Linux 8の ドキュメント またはアップストリームの プロジェクトブログ を参照してください。
この記事は Red Hat Developer の Using eXpress Data Path (XDP) maps in RHEL 8: Part 2 を、許可をうけて翻訳したものです。 ::: By Paolo Abeni December 17, 2018 ::: XDPに飛び込もう 車輪を再発明する(しない) ベリファイアにひっかかる カスタムXDPローダー ユーザー空間とのやり取り マップに関する注意事項 この先は XDPに飛び込もう XDPに関するこのシリーズの 最初のパート では、XDPを紹介し、最も単純な例について説明しました。それでは、いくつかのより高度なeBPF機能(マップ)といくつかの一般的な落とし穴を探りながら、もうすこし高度なことをしてみましょう。 XDPはRed Hat Enterprise Linux 8で利用可能で、今すぐダウンロードして実行することができます。 車輪を再発明する(しない) サンプルにパケット解析を追加するところから始めます。このような作業を簡単にするために、XDPプログラムのincludeセクションに以下を追加して、一般的なネットワークプロトコルのカーネル定義を再利用します。 #include <linux/in.h> #include <linux/if_ether.h> #include <linux/if_packet.h> #include <linux/if_vlan.h> #include <linux/ip.h> XDPコンテキストを介してパケットの内容にアクセスする必要があります。その定義を見てみましょう。 struct xdp_md { __u32 data; __u32 data_end; __u32 data_meta; / *以下へのアクセスはstruct xdp_rxq_infoを経由する* / __u32 ingress_ifindex; /* rxq->dev->ifindex */ __u32 rx_queue_index; /* rxq->queue_index */ }; パケットの内容は、 ctx->data と ctx->data_end の間にあります。それで、構文解析コードを追加して、パケットのアドレスを使ってみましょう。この例では、IPv4宛先アドレスがゼロのパケットをドロップします。 / * IPv4パケットを解析してSRC、DST IP、およびプロトコルを取得する* / static inline int parse_ipv4(void *data, __u64 nh_off, void *data_end, __be32 *src, __be32 *dest) { struct iphdr *iph = data + nh_off; *src = iph->saddr; *dest = iph->daddr; return iph->protocol; } SEC("prog") int xdp_drop(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; __be32 dest_ip, src_ip; __u16 h_proto; __u64 nh_off; int ipproto; nh_off = sizeof(*eth); /* parse vlan */ h_proto = eth->h_proto; if (h_proto == __constant_htons(ETH_P_8021Q) || h_proto == __constant_htons(ETH_P_8021AD)) { struct vlan_hdr *vhdr; vhdr = data + nh_off; nh_off += sizeof(struct vlan_hdr); h_proto = vhdr->h_vlan_encapsulated_proto; } if (h_proto != __constant_htons(ETH_P_IP)) goto pass; ipproto = parse_ipv4(data, nh_off, data_end, &src_ip, &dest_ip); if (!dst_ip) return XDP_DROP; pass: return XDP_PASS; } ベリファイアにひっかかる 上記のコードは問題なくコンパイルできますが、 iproute を使用してロードしようとすると、驚くことになります。 Prog section 'prog' rejected:Permission denied (13)! - Type:6 - Instructions:19 (0 over limit) - License: Verifier analysis: 0:(61) r1 = *(u32 *)(r1 +0) 1:(71) r2 = *(u8 *)(r1 +13) invalid access to packet, off=13 size=1, R1(id=0,off=0,r=0) R1 offset is outside of the packet Error fetching program/map! ベリファイアの検証を通過できません。このベリファイアのエラーメッセージは「パケットの最初の数バイトにアクセスしているために発生したかもしれない」と誤解を招くかもしれません。各イーサネットフレームは少なくとも64バイトの長さでなければならないことを私達は知っています、従って、私達は私達がパケットペイロードの中の有効なオフセットにアクセスしていることがわかっています。 代わりに、ベリファイアは明示的なチェックのみに依存します。パケット内のオフセットにアクセスしたり操作したりする前に、そのオフセットがパケット本体の内側にあることを条件式でチェックする必要があります。この例では、各ヘッダーにアクセスする前に、次のようなパッチを追加して、ヘッダーの末尾がパケットの末尾より手前にあることを確認する必要があります。 @@ -17,6 +17,9 @@ static inline int parse_ipv4(void *data, __u64 nh_off, void *data_end, { struct iphdr *iph = data + nh_off; + if (iph + 1 > data_end) + return 0; + *src = iph->saddr; *dest = iph->daddr; return iph->protocol; @@ -34,6 +37,8 @@ int xdp_drop(struct xdp_md *ctx) int ipproto; nh_off = sizeof(*eth); + if (data + nh_off > data_end) + goto pass; /* parse vlan */ h_proto = eth->h_proto; @@ -43,6 +48,8 @@ int xdp_drop(struct xdp_md *ctx) vhdr = data + nh_off; nh_off += sizeof(struct vlan_hdr); + if (data + nh_off > data_end) + goto pass; h_proto = vhdr->h_vlan_encapsulated_proto; } if (h_proto != __constant_htons(ETH_P_IP)) ベリファイアも今回は納得でしょう! カスタムXDPローダー 私たちはすでに パート1 でマップについて話しました。実際にそれらを使用する方法を見てみましょう。XDPプログラムを拡張して、ユーザーが実行時にドロップするアドレスを指定でき、関連する統計を読み取ることができるようにします。 最初のステップとして、 iproute2 ツールをカスタムローダープログラムに置き換える必要があります。これは、このツールではマップの操作ができないためです。XDPプログラムをロードするために使用されるコードは、次のようになります。 #include <bpf/bpf.h> #include <bpf/libbpf.h> #include <error.h> // [ ... ] struct bpf_prog_load_attr prog_load_attr = { .prog_type = BPF_PROG_TYPE_XDP, .file = "xdp_drop_kern.o", }; // [ ... ] if (bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd)) error(1, errno, "can't load %s", prog_load_attr.file); ifindex = if_nametoindex(dev_name); if (!ifindex) error(1, errno, "unknown interface %s\n", dev_name); if (bpf_set_link_xdp_fd(ifindex, prog_fd, 0) < 0) error(1, errno "can't attach to interface %s:%d: " "%d:%s\n", dev_name, ifindex, errno, strerror(errno)); // [ ... ] // cleaning-up bpf_set_link_xdp_fd(ifindex, -1, 0); 我々はLinuxカーネルソースにバンドルされている libbpf ヘルパーライブラリを使用しています。 bpf_prog_load_xattr() は、 prog_load_attr 引数で指定されたeBPFプログラムをロードします。指定されたオブジェクトのすべてのELFセクションを解析して、すべての関連情報を抽出し、それを obj 状況データに入れます。見つかった各プログラム(textセクション)は、新しく割り当てられたファイル記述子( prog_fd )を介してカーネル内にロードされます。 このファイル記述子は、後で bpf_set_link_xdp_fd() 関数を介して、ロードされたプログラムを選択されたデバイスに接続するために使用されます。最後の引数では、既存のXDPプログラムがある場合はそれを置き換えるためのフラグ、ドライバレベルのXDPフックを使用するためのフラグなど、いくつかのフラグを指定できます。デフォルトでは: ドライバレベルのフックを使用しようとし、その後一般的なものにフォールバックします。 指定したデバイスにXDPプログラムがすでにインストールされている場合は失敗します。 最後に、プログラムの終了時に呼び出される最後のヘルパー関数は、XDPプログラムをNICから切り離し、関連するすべてのカーネルリソースを解放します。 ユーザー空間とのやり取り それでは、面白い部分、マップに移りましょう。ユーザー空間とeBPFプログラムの間で共有されるすべてのデータ構造は「マップ」と呼ばれますが、実際にはハッシュマップ、配列、キューなど、さまざまな種類があります。通常、「シンプル」と「CPUごと」の2種類があります。CPUごとのマップでは、各エントリはすべてのローカルで利用可能なCPUに対して複製されます。カーネル内部では、各CPUはそのプライベートコピーにのみアクセスします。CPUごとのマップは、あらゆる種類の競合関連の問題を回避します。eBPFプログラムがパケットごとにデータエントリを変更しなければならない場合は、これが推奨されます。 マップデータは、ユーザー空間とeBPFプログラムの両方からアクセスされます。両側に含まれるヘッダーファイルにデータ型定義を追加すると便利です。この例では、マップを使用してフィルタリングする送信元アドレスを指定し、指定した各アドレスについてドロップされたバイト数とパケット数をカウントします。このようなマップをプログラムに追加するには、以下のような記述が必要です。 // in xdp_drop_common.h struct stats_entry { __u64 packets; __u64 bytes; }; // in xdp_drop_kern.c #include "xdp_drop_common.h" // [ ... ] /* forwarding map */ struct bpf_map_def SEC("maps") egress_map = { .type = BPF_MAP_TYPE_PERCPU_HASH, .key_size = sizeof(__be32), .value_size = sizeof(struct stats_entry), .max_entries = 100, }; // in xdp_drop_user.c struct bpf_map *map; int map_fd; // [ ... ] map = bpf_object__find_map_by_name(obj, "drop_map"); if (!map) error(1, errno, "can't load drop_map"); map_fd = bpf_map__fd(map); if (map_fd < 0) error(1, errno, "can't get drop_map fd"); このマップは実際にはCPUごとのハッシュテーブルであり、定義にはキーと値のサイズだけしか含まれていません。カーネルには割り当てとルックアップの実行、およびエントリの更新を行うために必要なそれらの情報だけがあればよいためです。マップ定義には、そのようなマップ内で許可されている最大エントリ数も含まれています。ハッシュマップは最初は空で、上記の制限を越えて挿入しようとすると失敗します。配列は指定された制限に等しい固定サイズを持ちます。ユーザー空間は、指定されたファイル記述子を介してマップにアクセスできます。 libbpf ヘルパー関数を使用するのはここでは少し複雑すぎるように見えるかもしれませんが、eBPFプログラムが複数のマップを公開するときには本当に役に立ちます。 これでユーザースペースとeBPFのインタラクションを追加する準備が整いました。 // in xdp_drop_kern.c struct stats_entry entry; // [ ... ] stats = bpf_map_lookup_elem(&drop_map, &src_ip); if (!stats) goto pass; stats->packets++ stats->bytes += ctx->data_end - ctx->data; return XDP_DROP; // in xdp_drop_user.c // [ ... ] memset(&entry, 0, sizeof(entry)); if (bpf_map_update_elem(map_fd, &saddr, entry, BPF_ANY)) error(1, errno, "can't add address %s\n", argv[i]); // [ ... ] if (bpf_map_lookup_elem(map_fd, &ipv4_addr, &entry)) error(1, errnom "no stats for rule %x %x\n", ipv4_addr); printf("addr %x drop %ld:%ld\n", ipv4_addr, entry.packets, entry.bytes); これで、eBPFプログラムは、送信元IPアドレスが drop_map ハッシュテーブルで見つかった場合にのみパケットをドロップし、関連する統計情報を更新します。ユーザースペースプログラムは、そのようなマップをゼロ化された統計で埋め、(定期的に)そのようなエントリを調べ、eBPFプログラムによって報告された統計を出力します。 簡潔にするために、ソースアドレス[list]をフェッチしたり、正常終了するための定型的なユーザースペースコードは省略しています。それができれば、私たちはビルドと実行の準備が整っています。 マップに関する注意事項 現在のコードでは、ユーザースペースプログラムのランダムなクラッシュからeBPFフィルターに明らかに何も効果がないことに至るまで、期待外れでしょう。ユーザースペースプログラムが異常終了した場合、XDPプログラムはネットワークデバイスに接続されたままになり、後で実行開始時に失敗します。このような場合、ユーザーは手動で iproute を使ってXDPプログラムをデタッチする必要があります。 ip link set dev <NIC> xdp off いくつかの幸運な場合には、現在のコードはほとんど完璧に動作し、シャットダウン時にXDPプログラムを切り離すことに失敗することがあります。 あなたはすでに問題がどこにあるのかを推測しているかもしれませんが、 xdp_drop_kern に以下を追加することによってXDP / eBPFデバッグ機能を使用してプログラムのステータスをダンプしましょう。 @@ -33,6 +33,13 @@ static inline int parse_ipv4(void *data, __u64 nh_off, void *data_end, return iph->protocol; } + #define bpf_printk(fmt, ...) \ + ({ \ + char ____fmt[] = fmt; \ + bpf_trace_printk(____fmt, sizeof(____fmt), \ + ##__VA_ARGS__); \ + }) + SEC("prog") int xdp_drop(struct xdp_md *ctx) { @@ -45,6 +52,8 @@ int xdp_drop(struct xdp_md *ctx) __u64 nh_off; int ipproto; + bpf_printk("xdp_drop\n"); + nh_off = sizeof(*eth); if (data + nh_off > data_end) goto pass; @@ -72,6 +81,8 @@ int xdp_drop(struct xdp_md *ctx) if (!stats) goto pass; + bpf_printk("xdp_drop pkts %lld:%lld\n", stats->packets, stats->bytes); + stats->packets++; stats->bytes += ctx->data_end - ctx->data; return XDP_DROP; それから例をもう一度実行して、以下のファイルにメッセージが表示されるのを確認します。 /sys/kernel/debug/tracing/trace eBPFヘルパー関数は各入力パケットに対して正しく呼び出されます。 運が良ければ、ユーザースペースによって作成されたマップエントリに関連付けられた統計情報が破損しているように見えることがあります。たとえば、エントリ作成後に最初のパケットを受信した場合でもかなりランダムな値が含まれます。 我々はCPUごとのマップを使っていますので、エントリを設定するとき、カーネルは指定されたデータアドレスから 可能な最大CPU個数分の値を読み取り、それぞれをカーネルマップ内の対応するCPUごとの値へコピーします。この例ではユーザー空間プログラムスタックにあるstats変数以外のデータにアクセスする場合にこれが発生します。 さらに、ユーザースペースプロセスがマップからエントリを読み込もうとすると、カーネルは同じ量のデータを指定されたアドレスにコピーし、再びスタック上のデータにアクセスして、上記のランダムな動作を引き起こします。 解決策は、次のような記述をおこない、マップエントリに十分なストレージを割り当てることです。 int nr_cpus = sysconf(_SC_NPROCESSORS_CONF); struct stats_entry *entry; // [ ... ] entry = calloc(nr_cpus, sizeof(struct stats_entry)); if (!entry) error(1, 0, "can't allocate entry\n"); そして、マップを読むときは、すべての値を調べて集計します。 struct stats_entry all = { 0, 0}; if (bpf_map_lookup_elem(map_fd, &ipv4_addr, entry)) error(1, errno, "no stats for address %x\n", ipv4_addr); for (j = 0; j < nr_cpus; j++) { all.packets += entry[j].packets; all.bytes += entry[j].bytes; } これで、私たちのIPフィルタアプリケーションは完成です。 この先は この記事では、XDP / eBPFによって提供される機能のいくつかを取り上げましたが、もっと多くの機能があります。たとえば、さまざまなタスクに使用する準備ができているeBPFヘルパー関数が他にもたくさんあります。変更後のパケットチェックサムの更新、パケットの転送などです。 良い出発点は、Linuxカーネルソース内のこのヘッダです。これには、実装されたヘルパー関数の公式ドキュメントが含まれています。 include/uapi/linux/bpf.h さらに、カーネルソース内の samples/bpf/ ディレクトリには、さらに複雑なXDPの例がいくつか含まれています。ただし、そこを見る前に関連する背景が必要になります。 例の完全なソースコードは、次の場所にあります。 https://github.com/altoor/xdp_walkthrough_examples Happy hacking!
お疲れ様です!Red Hatの鈴木(tomosuzu)です! チーム作りに関するプラクティスである「社会契約 」をご紹介します。 プラクティスとして、あまり馴染みがないかもしれませんが、 チームの自律性、自主性を高めるために非常に有効な方法となっています。 tomosuzu.com 次回もチーム作りに有効なプラクティスである「チーム感情の評価」を紹介します。 さいごに Red Hatでは、「アイデアをイノベーションに変える、文化・プロセス・技術・実践の場」として、 Open Innovation Labs を提供しています。 デザイン思考やDevOps、アジャイルやリーン開発などをRed Hatのコンサルタントと一緒に取り組むことができます。 過去記事 アジャイル開発のプラクティス一覧をご紹介いたします - 赤帽エンジニアブログ チームの知識共有とコミュニケーションを増やします「ペアプログラミング 」 - 赤帽エンジニアブログ 速い開発速度を保ち、着実に開発を進める「テスト駆動開発」 - 赤帽エンジニアブログ
この記事は Red Hat Developer の Achieving high-performance, low-latency networking with XDP: Part I を、許可をうけて翻訳したものです。 ::: By Paolo Abeni December 6, 2018 ::: XDP:ゼロから14 Mpps*1 XDPの概要 XDPフレーバー "Hello world" 荒削りな点 次回予告 XDP:ゼロから14 Mpps *1 過去何年にもわたり、カーネルコミュニティはさまざまなアプローチを使用して、ネットワークパフォーマンスの改善を追求してきました。いくつかの分野で改善が見られますが、アーキテクチャ関連のセキュリティ問題とその対策がほとんどの利点を打ち消してしまい、パケット処理集中型ワークロードに対する純粋なカーネルソリューションはまだバイパスソリューションよりも遅れています。すなわち、データプレーン開発キット(DPDK)に対して、10倍近い差があります。 しかし、カーネルコミュニティは(ほとんど文字通り)眠ることなく、カーネルベースのネットワーキングパフォーマンスの聖杯を XDP:eXpress Data Path の名の下に発見しました。 XDPは Red Hat Enterprise Linux 8 で利用可能で、今すぐダウンロードして実行することができます。 このテクノロジを使用すると、カーネルパケット処理にカスタム 拡張BPF(eBPF) プログラムをアタッチして新しいネットワーキング機能を実装したり(既存の機能を再実装したり)、ソケットバッファ(SKB)管理やパケット単位のメモリ管理のオーバーヘッドを削減でき、より効率的な一括処理を可能にします。XDP eBPFプログラムは、パケット操作およびパケット転送のためのヘルパー関数にアクセスできるため、新しいカーネル内コードを追加する必要なしに、しかも処理速度を向上しつつ、カーネルの動作を変更および拡張する機会がほぼ無制限に提供されます。XDPをサポートする最新のドライバは、14 Mpps以上を簡単に処理できます。 この機会には興奮しますが、未知のものなので怖いですよね?この記事はあなたが最初のXDPプログラムを書くためのガイドです。ゼロから実用的な例を構築し、あなたが高速ネットワークアプリケーションを構築するための準備をします。 XDPの概要 XDPでは、eBPFプログラムをカーネル内の下位レベルのフックにアタッチすることができます。このようなフックは、SKBが現在のパケットに割り当てられる前に、ingressトラフィック処理機能(通常はNAPI poll() メソッド)内のネットワークデバイスドライバによって実装されます。 プログラムのエントリ関数には引数が1つあります。それは現在のパケットを記述するコンテキストです。プログラムはそのパケットを任意の方法で操作することができますが、eBPFベリファイアによって課される制約に従う必要があります(これについては後でもう少し詳しく説明します)。最後に、そのようなプログラムは、デバイスドライバが処理されたパケットを次にどのように扱うべきかを指定する、制御アクションを返します。例えば上位層の処理に渡すかドロップするかを、所有するデバイスドライバに指示します。 eBPFベリファイアはあなたが書くことができるコードにいくつかの制限を課し、プログラムが以下の性質をもっていることを確認する厳密なチェックを実行します。 ループを含まない 有効なメモリのみにアクセスします(たとえば、パケット境界を超えないようにします)。 限られた数のeBPF命令を使用します。Linux 4.14カーネルでは制限は128Kです。 ベリファイア自体はカーネル内で実行され、eBPFプログラムがeBPFシステムコールを介してロードされたときに実行されます。プログラムが正常にロードされると、任意の数のデバイスのXDPフックにアタッチできます。カーネルソースは、そのような作業を単純化するためのヘルパー関数をもつユーザ空間ライブラリ(libbpf)をバンドルしています。 冒険好きな(または上級)ユーザーは、eBPFアセンブラーを使用して直接eBPFプログラムを書くこともできますが、高級言語を使用し、コンパイラーにコードをeBPFに変換させる方がはるかに簡単です。eBPFコミュニティは、そのタスクにLLVMコンパイラとC言語を選択しました。ベリファイアはコンパイルされたコードを検証するので、コンパイラによって実行される最適化は結果に影響を与える可能性があります。たとえば、定数回のループは、コンパイラによって展開されます。これにより「ループなし」制約を回避できますが、生成されるeBPF命令の数も増えます。入れ子になったループは、128Kの制限に簡単に達する可能性があります。 XDPフレーバー すべてのネットワークデバイスドライバがXDPフックを実装しているわけではありません。そのような場合は、コアカーネルによって実装され、特定のネットワークデバイスドライバ機能に関係なく利用可能な、一般的なXDPフックにフォールバックする場合があります。このようなフックはSKB割り当て後にネットワーキングスタックの後半で行われるため、そこでのパフォーマンスはドライバベースのXDPフックよりもはるかに低くなりますが、それでもXDPを試すことができます。 Linux 4.18以降でXDPフックをサポートするネットワークデバイスドライバは次のとおりです。 bnxt thunder i40e ixgbe mlx4 mlx5 nfp qede tun virtio_net このリストはカーネルのバージョンによって異なる可能性があるので、実行中のカーネルであなた自身のドライバが明示的にサポートされているかチェックする価値があります。その方法については後述します。 最後に、XDP / eBPFプログラムの振る舞いは、プログラムで定義・使用される多数のマップを介してユーザースペースから制御および検査することができます。それらのマップは、libbpfヘルパーを使用してユーザースペースからアクセスや変更も可能です。 "Hello world" XDPはプログラミング言語ではありませんが、プログラミング言語(eBPF)を使用します。プログラミング関連のチュートリアルなので「Hello world」プログラムから始めます。デバッグメッセージをXDP / eBPFプログラムで出力することはでき、後で説明しますが、もっと単純なXDPの例から始めましょう。eBPFプログラムで、処理された各パケットをカーネルのスタックに渡すだけです。 // SPDX-License-Identifier:GPL-2.0 #define KBUILD_MODNAME "xdp_dummy" #include <uapi/linux/bpf.h> #include "bpf_helpers.h" SEC("proc") int xdp_dummy(struct xdp_md *ctx) { return XDP_PASS; } char _license[] SEC("license") = "GPL"; 上のコードについて詳細を見てみましょう。C言語の構文を使用し、2つの外部ヘッダーを含みます。最初のものは、ほとんどのディストリビューションではkernel-headerパッケージで提供され、XDPプログラムのリターンコード(この例ではXDP_PASS)の定義を含みます。これは、カーネルがパケットをネットワーク処理に渡すことを意味します。他の利用可能な値はXDP_DROP(ドロップする)、XDP_TX(それを受け取ったインタフェースからパケットを送り返す)、XDP_REDIRECT(他のインタフェースからパケットを送る)です。 2番目のヘッダは、Linuxカーネルの一部なのですが、ほとんどのディストリビューションでは通常パッケージ化されていません。利用可能なeBPFヘルパー関数のリストと SEC()マクロの定義が含まれています。後者は、コンパイルされたオブジェクトのフラグメントを指定したELFセクションに配置するために使用されます。そのようなセクションはeBPFローダーよって検出・解釈され、たとえば、プログラムによって定義されたマップを検出する(およびそれらへのユーザースペースアクセスを許可する)ために利用されます。 最後の行は、このプログラムに関連するライセンスを正式に指定しています。一部のeBPFヘルパー関数はGPLプログラムでしかアクセスできないので、ベリファイアはこの情報を使用して制限をおこないます。 荒削りな点 ビルドして実行しましょう。LLVM / Clangバージョン3.7以降(この記事の執筆にはバージョン6.0が使用されました。Fedora 28のデフォルトです)古すぎない版の make (ここでは4.2.1)が必要です。以下のmakefileを利用します: KDIR ?= /lib/modules/$(shell uname -r)/source CLANG ?= clang LLC ?= llc ARCH := $(subst x86_64,x86,$(shell arch)) BIN := xdp_dummy.o CLANG_FLAGS = -I. -I$(KDIR)/arch/$(ARCH)/include \ -I$(KDIR)/arch/$(ARCH)/include/generated \ -I$(KDIR)/include \ -I$(KDIR)/arch/$(ARCH)/include/uapi \ -I$(KDIR)/arch/$(ARCH)/include/generated/uapi \ -I$(KDIR)/include/uapi \ -I$(KDIR)/include/generated/uapi \ -include $(KDIR)/include/linux/kconfig.h \ -I$(KDIR)/tools/testing/selftests/bpf/ \ -D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \ -D__TARGET_ARCH_$(ARCH) -Wno-compare-distinct-pointer-types \ -Wno-gnu-variable-sized-type-not-at-end \ -Wno-address-of-packed-member -Wno-tautological-compare \ -Wno-unknown-warning-option \ -O2 -emit-llvm all: $(BIN) xdp_dummy.o: xdp_dummy.c $(CLANG) $(CLANG_FLAGS) -c $< -o - | \ $(LLC) -march=bpf -mcpu=$(CPU) -filetype=obj -o $@ 注意すること: プログラムはカーネルヘッダを使用するので、アーキテクチャ固有のヘッダ依存関係に対応する必要があります。上記のmakefileを使って任意のディストリビューションで make を呼び出すと、次のようになり、期待外れな結果となる場合があります。 clang -nostdinc -I. \ [...明快さのために長い引数リストは省略...] -O2 -emit-llvm -c xdp_dummy.c -o - | \ llc -march=bpf -mcpu= -filetype=obj -o xdp_dummy.o xdp_dummy.c:5:10: fatal error: 'bpf_helpers.h' file not found #include "bpf_helpers.h" ^~~~~~~~~~~~~~~ 1 error generated. ここで必要な bpf_helper.h を含む完全なカーネルソースが含まれているディストリビューションのパッケージはほとんどありません。現在の解決策は、残念なことに、完全なLinuxソースをダウンロードし、あなたのローカルディスクのどこかに解凍し、そして以下のようにmakeを起動することです: KDIR= make これでeBPF / XDPプログラムは完成しましたが、実行するにはカーネル内にロードする必要があります。それほど簡単ではない例では、ロードは通常制御用のユーザ空間プログラムによって行われます。 これは一部のマップを介してXDPプログラムを監視/対話します。簡単のために、代わりにiprouteを使います。 ip link set dev <net device name> xdp object xdp_dummy.o 上記のコードは私達のXDPプログラムを指定されたネットワークデバイスに接続し、デバイスフックを使用しようと試み、そうでなければ一般的なものにフォールバックします。勇敢でサポートされているデバイスドライバを持っている人は、ドライバ固有のフックの使用を強制するよう xdp を xdpdrv に置き換えることもできます。  xdpdrv が利用できない場合、アタッチは失敗します。 次回予告 今回作成したXDPプログラムを使用すると、XDPをサポートする最新のドライバで14 Mppsを容易に処理できるため、(イーサネットケーブルを抜いて全てをフィルタしてしまっているのでなければ)これまでにない速度でのパケットフィルタリングを経験できます。しかし、もうすこし意味があること、特定のパケットをドロップしたり、XDPプログラムのアクティビティに関する統計を収集するなどに興味があることでしょう。この連載の次回の記事では、パケットの解析、デバッグ、およびマップの使用法を紹介することによって、それほど簡単ではない例を扱います。 *1 : 訳注: 10GbEでの最大パケットレート
レッドハットでコンサルティングしているフェンです。 今回は、Red Hatの同僚である Bruno Meseguer の記事「 360° Observability of Camel Integrations in Red Hat OpenShift 」を翻訳して共有します。すみませんが、私の翻訳スキルの関係で、数回に分けて皆さんに届けたいと思います。 - 想像してください - あなたのチームが砂漠にいます... あなたが強風を感じています...(実は砂嵐があなたに近づいてきます)... チームはあなたの状況報告を待っています... そして、あなたは状況確認に焦ています... あなたは最初に、高いところに行けば、遠く見えますので、そうしました。しかし、現実は、塵や砂が強風により激しく吹き上げられ、空高くに舞い上がる砂塵により、見通しが著しく低下してしまいます。 そして、あなたは、360°鳥瞰できる気球を持っていることを思い出しました。 それを使って高度数千キロから砂嵐の進路を観察して、チームにオアシスへ辿るルートを報告できました。 この記事では、 「360°鳥瞰できる気球」 のような 可観測性機能 をどのように 採用 するか、さまざまなオープンソース技術を 組み合わせる 方法、およびそれらを 調和 して利便性を高める方法を示したいです。 この記事の例を説明するために作成された ソースコード は、 GitHub上 で公開していますので、時間があれば、動かして見てください。 目次 1. はじめに 2. 可観測性(Observability)とは 2.1. 関与する「技術」概要 2.2. 監視(Monitoring) 2.3. ログ集約(Log Aggregation) 2.4. トレース(Tracing) 2.5. サービスメッシュに関して 3. ケーススタディ(導入前) 4. 可観測性(Observability)の導入 4.1. トレース情報を生成する 4.2. トレースを可視化する 4.3. ログ集約を強化する 5. ケーススタディ(導入後) 5.1. 調査上の課題の振り返り 5.2. 新しい調査のアプローチ 5.3. 他の調査例 「4. 導入」以降の章は次回以降に掲載させてください 1. はじめに 洗濯機のコース選びボタンを押しても反応しないときや、音楽プレーヤーがなんらかの理由で動かなくなったとき、PCがフリーズしたとき、多くの場合、皆さんはオン/オフボタンを押すという万能な速攻策をよく使いますね。 だから、一部のデータセンターで動いているサーバが、未だにこの方法で、cronジョブを組んで定期的に再起動させています。ただし、予期しない誤動作を少なくしてスムーズに実行できるように、プラットフォームを改善するために、欠陥を見つけて解決することをお勧めします。 1つまたはごく少数のサーバ/システムしか関与していない場合、問題を見つけて修正するのは簡単ですが、多数のサーバ/システムが複雑に絡んでいる場合はそう簡単ではありません。多数のシステムが互いにどのようにつながっているかは、分子レベルで炭素元素がいかに美しく、洗練された構造で形成されていることを思い出させます。 相互接続されたすべてのサーバ/システムのうち、構造内のどこで問題が発生しているかを正確に指摘することは非常に困難になります。この難しさは特に、自明ではない通信インタラクションの使用する分散システムに当てはまります。例えば、あるシステムが特定のエンドポイントへの通知イベントを発行し、複数の非同期で待機したプロセスが反応して新しいイベントを生成して下流のシステムに伝播していくような構成。 監視はサンプリングと統計データ(ヘルスデータやその他の評価指標)の収集に役立ちますが、障害分析データの収集にはあまり役立ちません。特定の問題を解決するための証拠を追跡するには、他の方法も必要です。 例えば、犯罪捜査の分野では、 ルミノール試験 を使って血痕を見つけ出します。また、天文学の分野では、光学望遠鏡は遠く離れた銀河(星雲の下)を見せてくれるが、そこにあるすべてのことを教えてくれないでしょう。だから電波、X線、他のタイプの望遠鏡で撮った画像と組み合わせて、 色々な角度/視点から銀河全体を研究 する必要があります。 「 かに星雲 」の全体像: 私たちのIT分野においても、確かに多くの商用ソフトウェアベンダーは、非常に優れた”望遠鏡”を備えた、非常に魅力的な製品を提供しています。それらを使って、サーバ群/システム群のさまざまな部分に潜り込むができます。 この記事は、皆さんが気軽に利用できる、 オープンソース技術 を駆使し、 インテグレーション(Integration) 、 サービス(Service) 、 メッセージング(Messaging) のアプリケーション領域に焦点を当て、 可観測性(Observability) を紹介します。 2. 可観測性(Observability)とは 顧客の技術支援するときでも、トラブル発生したあとにエンジニアが適切な診断を行うことが困難であることを度々目にしてきました。その理由はまず、運用チームから報告した障害事象の説明が不十分であること。そして、重要な情報が欠落した状況下で、障害分析するエンジニアは無力を感じます。このため、障害解決には、運用チームと洞察力に富んだ知識を持っているエンジニアとの密接な協力が必要です。確証を掴めるためのツールとテクニックがあれば、障害の根本原因が究明され、問題が修正されて根絶されます。そうでなければ、たいていの場合、回避策が適用され、技術的な負債が残り、システムの信頼性が低くなります。 システムに、この 確証を掴めるためのツールとテクニック が備えていれば、システム内部の状態が取得できる訳ですので、この機能は可観測性(Observability)機能と呼びます。即ち、 可観測性 とは、 システムが本番環境でどのように動作しているかを取得できるの特性 であります。 可観測性機能は、ほとんど、プラグアンドプレイの形で導入できます。プラグアンドプレイ (Plug and Play, PnP) は、パーソナルコンピュータ(パソコン)に周辺機器や拡張カード等を接続した際にハードウェアとファームウェア、ドライバ、オペレーティングシステム、およびアプリケーション間が自動的に協調し、機器の組み込みと設定を自動的に行う仕組みのことである。 しかしながら、可観測性機能は、すべての障害原因を一発で突き止めるような、Out-of-box(取り出してすぐ使える)な道具ではありません。この記事では、あちこちで補足を取り入れながら、可観測性を最大にするためのいくつかのアプローチを議論します。 まず、次の図はシステムの可観測性を実現するために有効な組み合わせを示しています。 以降では、これに使用されるテクノロジについて概説し、それぞれの観点について詳しく説明します。 最初に要約すると、可観測性機能の3つの要素とは、 ヘルスチェックを実行するための 監視(Monitoring) ログ調査するための統合した ログ集約(Log Aggregation) サービス間のデータ流れを追跡するための トレース(Tracing) 2.1. 関与する「技術」概要 この記事で取り上げた概念と原則は、ベアメタル、仮想化、またはコンテナーで実行したシステムに対しても適用できます。この記事の目的は、可観測性情報の生成、分析、および可視化するための戦略を説明することです。 ただし、この記事は、戦略の一般論を避けるため、インテグレーションとコンテナーを中心にさせてください。なぜなら、インテグレーションとはシステムを容易に相互連携するための基盤、コンテナーが俊敏性と自動化を提供する基盤であり、この2つは社内システムでよく使用している技術基盤です。皆さんがこの記事を読んだあと、ここに書かれた「技術」から切り離して、「戦略」を自分たちのシステムに適用できるようになることを期待します。 また、この記事では可観測性に関連する以下の「技術」を扱っています。後でそれぞれの技術詳細を説明します。 Apache Camel : インテグレーション用フレームワーク Kubernetes : コンテナの実行環境 Prometheus : 監視用メトリクスの収集ツール OpenTracing : トレース用の技術中立なトレーシング仕様 Jaeger : OpenTracingに準拠した1つの実装 EFK : ログ収集ツール ElasticSearch + Fluentd + Kibana (EFK) 後程の"導入編"では、 サンプルコード やスニペットなどを使って、これらの「技術」の実践的な導入方法を示します。 便利上、この記事では予め2つのレッドハット製品を前提にしました。( すみませんが、仕事の関係上で ) - Red Hat Fuse : Apache Camel を拡張した、オープンソースのシステム統合のソリューション。システム統合の開発と運用は、Camelフレームワークよりも簡単になります。 - Red Hat OpenShift : Kubernetes を拡張した、オープンソースのコンテナ実行環境。コンテナーのデプロイと設定は、 Vanilla Kubernetes 環境よりもはるかに容易 になります。 それでは可観測性のさまざまな領域をカバーし、それぞれの役割を果たしているかを確認しに行きましょう。 2.2. 監視(Monitoring) 監視について、 別の記事 で詳しく取り上げたので、ここでは省略させてください。 しかし、次のログ集約の話に入る前に、監視分野で大活躍している Prometheus だけを再度紹介したい。Prometheus は Kubernetesに上手く統合されたため、あらゆるソースからメトリックを収集するために最適な選択肢でしょう。 さらに OpenShift 上、 Prometheus がOut-of-boxで提供されています。 Camel インテグレーションを Fuse にデプロイすれば、Fuseが自動的にJVMのメトリクスと Camel のメトリクスをPrometheusに公開してくれます。 下の図では、Grafanaダッシュボードの画面で、Camelインテグレーションから取得した情報を表示しています。 JVMのメトリクスとCamelのメトリクスをPrometheusに公開する方法は、 この記事 をご参照ください。 2.3. ログ集約(Log Aggregation) ログ集約を有効にすると、すべての出力ログの収集が可能になり、統合コンソールから自由に参照できるようになります。そうでなければ、プラットフォーム内に散在する数百のログファイルの中から必要なログを見つけるのは困難でしょう。OpenShift上なら定番のEFK組み合わせ(ElasticSearch、FluentdとKibana)を使用できます。これらはすべて自由に利用できるオープンソースプロジェクトです。EFKについて、たくさんの記事がありますので、ここでは概説だけにします。 上図のように、ログ収集のアイデアは、軽量なエージェント( Fluentd )を使って実行中のすべてのコンテナからログを収集/解析/変換し、それらを ElasticSearch に送信します。 ElasticSearch はログを格納し、解析したフィールドをインデックスすることで、後で簡単に検索可能の状態にします。ユーザは Kibana というユーザインタフェースを介して、すべてのコンテナのログを照会/検索が可能になります。 このアプローチはまた、アプリケーションの簡素化をもたらします。アプリケーションの運用設計時、ログローテーションやディスク使用量を減らすためのログ圧縮が不要となります。アプリケーションはログをコンソールに送信するだけでよく、 Fluentd はそれらを収集して ElasticSearch で処理しやいJSON形式へ変換します。 ダウンストリームログ形式はJSONとなるため、アプリケーション自体がJSONエンコーダーを内包すればさらに合理的です(必須ではありません)。JSONエンコーダーにより、 ElasticSearch のインデックス作成およびキー/値アクセス戦略に従うことができます。 ログの例を見てみましょう。仮に、システムに入ってくる要求ヘッダの clientID をロギングすると仮定すると、コード行は次のようになります。 log( "got request from client" ) JSON形式のログは次のようになります。 { " message ":" got request from client " , " clientID ":" YnJ1bm8wMQ== " } JSONエンコーダーを使用すると、重要な情報を事前定義済みJSONフィールドにマッピングができます。 ElasticSearch は、インデックスフィールドとしてJSONのフィールド名を使用して、ユーザーが Kibana のWebコンソールを使用して『filter='clientID'』のクエリで検索できるようになります。 注意事項として、多数のコンテナが稼働しているので、大量のログが収集されます。 ElasticSearch は、管理者がストレージ使用量を確実に制御できるように、 インデックス管理ポリシー も提供されています。これを忘れずに検討しましょう。 EFKはログデータへのアクセス方法に優れたアプローチを提供してくれていますが、大量のログを集めた後、必要な正確な情報を分類するためのフィルタ設定(即ちログ分析)は依然難しいです。ログ分析は主にアプリケーションに依存し、システムごとに異なるからです。 2.4. トレース(Tracing) アプリケーションがトレーサビリティ情報を生成できるようにすることで、システム間でデータ受け渡し時のメタデータ、例えば、タイムスタンプやシステム名など、が分析の際に役に立つ情報となります。もっと重要なのは、これらの情報によって、データ処理フローがどのシステムまで横断したかが分かるようになります。下図のように、個々の処理おいて共通の”trace-id”もメタデータに含まれているからです。 しかし、トレーサビリティ情報が可視化されてないと、誰も盲目感があります。アプリケーションのソースコードには、入力データに対して後続処理の経路を明確していますが、実際の実行時では、分岐が起きたら、個別のデータの処理経路を追跡する手段が失います。 また、トレーサビリティの最大の課題は、プログラミング言語とテクノロジに依存ぜず、共通に利用可能なトレーシング情報を生成することです。この課題は、自由に実行エンジン(プログラミング言語とテクノロジ)の選べるコンテナ環境上でさらに深刻になります。 可視化 と 言語非依存 の課題に対する答えは、 OpenTracingプロジェクト にあります。 OpenTracingプロジェクト は、テクノロジ、開発言語に依存しない、トレース情報を生成するための共通仕様とフレームワークを策定しています。 インテグレーションのアーキテクチャ上、トレース情報はHTTPに限らず、他の通信プロトコルでも行き渡る必要があります。メッセージングはこの​​良い例です。あるインテグレーションがHTTPエントリポイントとして公開する必要があるが、トレースに関心あるのは、疎結合されたアプリケーション同士の非同期メッセージ送受信の部分であるかもしれません。トレースの目的は、個別のデータ処理に関連するすべてのシステムの全体像を把握することです。この目的を達成するには、トレース情報をメッセージングの通信プロトコルでも通過できるでなければなりません。 2.5. サービスメッシュに関して サービスメッシュという用語は最近非常に人気があります。もちろん、一連のサービスは、厳密に言えば、独立したエンティティとして展開されて、関連サービスの集合体に他なりません。 しかし、ここでの説明は、サービスの集合体についてのではなく、サービス集合を管理しやすく、開発者や運用者の負担を軽減するために、サービス集合の下のレイヤに存在する”wired”サービスメッシュを対象にします。これは、サービスの数が増えるにつれて、トレースがより重要になります。 サービスメッシュの重要な機能の1つは可観測性であり、したがってこの説明と非常に関連性があります。おそらく最も有名なサービスメッシュ実装例は Istio プロジェクトである、Istioの中にも前述のオープンソース技術( OpenTracing と Jaeger )が含まれています。 しかし、2つの主な理由から、ここでは Istio を深堀りしません。 - 1つ目の理由は、この記事の目的は、一般に可観測性機能を最大限に活用する方法にフォーカスしたいことです。 - 2つ目の理由は、現在のIstioの場合、主にHTTP通信に焦点を当てており、マルチプロトコル通信が容易に適応できません。 IstioプロジェクトはHTTPプロトコル以外の領域にも推し進めようとしています。その一例が、Red Hatの同僚である Alain Conway の仕事です。彼は AMQP エンドポイントとの統合に取り組んでいます。これにより、多くのユースケースが適応できるようになります。詳細については、 envoy-amqp を参照してください。 3. ケーススタディ(導入前) 可観測性がもたらすメリットを説明するために、実際の経験に基づいたケーススタディを紹介しましょう。 今回のケースは、未知の障害が収益損失を引き起こし、それによってビジネスに影響を及ぼしているような重大な状況の1つ例です。 パートナー(サービスの利用者)からクレームが上がってきました。クレームの内容は、システムに送信した「支払い」リクエストに対し、一部だけ、正常に「支払い処理」されていませんでした。ところで、「支払い処理」の行うシステム上では、遅延もエラーも発生したことなく、エンドツーエンドでも正常な状態です。 このようなシナリオでは、すべてのダッシュボードは正常に見え、エラーコードが返されず、エラーログも生成されません。この動作は、複数チームや複数製品の管理者に属するさまざまなサービスが混在する本番環境だけで見られるため、解決するのが難しい問題です。運用担当者がクレームを受けたとき、最初に、「支払い処理」のバックエンドシステムの運用チームに連絡しましたが、やはりそこではエラーもアラートも見つけられませんでした。 あなたはおそらくこう思います - お客様がシステム運用者でさえ分からない異常を報告している。こんなことであり得るか? - 。 それに答えるため、背景をもっと説明ましょう。 何年も前に、メインのバックエンドの「支払い処理」サービスはすでに存在していました。すでに多くのパートナー(サービス利用者)が利用していました。そして、新しい機能追加の業務要件がありました。設計時は、既存の「支払い処理」サービスを変更せず、コンポジットサービスで新機能対応で既存サービスの前段に増設しました。この設計により、同社は既存パートナーや顧客がコード変更せず、サービスを継続利用できるようにもなりました。 当時の設計は正しいです。上図のアニメーションから、コンポジットサービスを追加しても元のインタフェースが影響されないことを理解できます。コンポジットサービスでは、バックエンドが常にHTTPコード200(HTTP成功)で応答するパススルーとして実装されました。しかし、本当の '処理結果'の識別は、応答ペイロード内の <payment>/<statuts> フィールドであり、 'success'または 'failure'の値で判別する必要があります。バックエンドは両方の値が正常なシナリオと見なしています。つまり、'failure'は異常ではなく、「残高不足」が原因かもしれません。 システムエラーは一般的に何かが予想通りにうまくいかなかったことを示しますが、「支払い」の成功または失敗はどちらも予想されるユースケースです。他の例で、システムがディスクへの書き込みしようとしたとき、領域不足のためにこの操作が失敗した場合、例外が発生し、これはシステム上のエラーとして扱われます。 この考え方を念頭に、例のシナリオに戻ると、サービス利用者は「支払い処理」成功を期待していましたが、その代わりにコンポジットサービスから返ってきた応答は次のとおりでした。 HTTP/1.1 200 OK Content-Length: 47 <payment> <status> failure </status> </payment> 取得したレスポンスはペイロードを含む正常なHTTP通信データです。これで、「支払い処理」されてないと報告されても、システム上にアラームや赤信号も発しなかったことは理解できます。 システムの運用担当者にとって、入り口で受けたHTTP通信が健全のように見え、バックエンドシステムも正常に働いているように見えます。さらに、残念ながら、セキュリティ上の理由から、システム上に通過したデータ(IDフィールドなど)は暗号化されており、不正データを特定することも不可能であるため、サービス利用者とシステム運用側はこれ以上の連携ができませんでした。 このような状況でチームが詳細に調査できるようには、優れた可観測性戦略が必要です。このため、いくつかの技術的な説明を続けていきます。そのあと、解決の道筋を見つけるためにこの問題シナリオを再度取り上げます。 <続きは次回以降に掲載させてください> 4. 可観測性(Observability)の導入 4.1. トレース情報を生成する 4.2. トレースを可視化する 4.3. ログ集約を強化する 5. ケーススタディ(導入後) 5.1. 調査上の課題の振り返り 5.2. 新しい調査のアプローチ 5.3. 他の調査例
レッドハットでインテグレーションのためのミドルウェアのテクニカルサポートを担当している山下です。 最近はマイクロサービスでシステムを開発しているという話もよく聞くようになってきました。ではそこでメッセージング、そしてKafkaを使ってますでしょうか?マイクロサービスでは何故かRESTばかりが世の中に注目されてしまうことも多いために、今回はメッセージング推しの内容にしています。 マイクロサービスではメッセージングを用いたコマンドやイベントこそ中心であって不可欠です。マイクロサービスの中でメッセージングはどのように利用され、そしてなぜ必要なのでしょうか。今回は「マイクロサービスとメッセージングのなぜ [概要編]」と題してそれを概観していきます。 Kafkaの簡単なおさらい どこでメッセージングは利用されるのか? RESTはお手軽な解決策? なぜマイクロサービスにメッセージング(Kafka)が必要なのか? 安定したシステムを支えるメッセージング イベントは履歴になる(イベントは状態の変化を表し、イベントの蓄積が状態を再生する) 様々なサービスやシステムと連携したり情報を統合するためのデータハブ イベントを利用することでサービス間の秩序ある関係を維持できる マイクロサービスでの責務の明確化が促進される コマンド実行順の保証と並列実行 まとめ Special Thanks 参考文献 [PR] Kafkaの簡単なおさらい ストリーム処理のためのデファクト、といえるとても優秀なプロダクトです。クラスタリング前提の設計で、高スループットで水平にスケールします。それから、データはレプリカにコピーされ高い可用性と耐障害性を持ちます。そして、ページキャッシュやSendfileといったLinuxの機能を利用して効率よく処理を実現します。 どこでメッセージングは利用されるのか? それでは、そもそもマイクロサービスによるシステムのどこでメッセージングが利用されるのでしょう。マイクロサービスでは、各サービスが独立して稼働します。そしてそのサービス間では、データの問い合わせを行うクエリ、何らかの指示を行うコマンド、状態変化を通知するイベントという3種類の連携が行われます(CQRS+ES)。 しかしRESTを用いるのは、クエリと一部のコマンドのみです。残る多くのコマンドとイベントはメッセージング(つまりはKafkaですよ)を用いて実現します。 次のマイクロサービスの全体図を見てどこでメッセージングが利用されているかみてみましょう。なお説明のために単純化したマイクロサービスの一例に過ぎないことに注意してください。 クライアントへの表示を担当するビューサービス(query-side service 後述)、そしてその背後に各サブドメインごとのサービスが存在します。RESTによる通信は、主としてシステムのエッジであるクライアントからのクエリとコマンドで利用します。一方でその背後で行われるサービス間の通信は、Kafkaを用いたメッセージングによる、コマンドやイベントを利用します。 マイクロサービスで利用される最も基本的なKafkaのトピックは、各サービスごと(正確にはサービス内の集約ごと)に作成する、コマンドのためのトピックと、イベントのためのトピックです。 メモ: 実際にはこれ以外にもシステムの要件に応じて様々なトピックが作成されることがあります。例として、システムの境界に配置してイベント形式の差異を吸収するトピックや、コマンドの実行結果を専用に受けるトピックや、複数のトピックをジョインするためのトピックや、一時的なデータ変換のためのトピックなどです。しかしまずはこの2つの基本のトピックを抑えることが大切です。 サービスはコマンドのトピック①を順次処理して②、ドメインオブジェクトの状態変化を起こし③、そしてイベントを発行します④。そして、そのイベントを購読している他のサービス⑤は、イベントから取り出した情報を自身のサービス内にキャッシュしたり、あるいはそれをトリガとして何らかの処理を行うことを繰り返しながら、全体としてビジネスプロセスが実現されます(ビジネスプロセスについては別記事で説明)。 このように、クライアントからの通信ではRESTを使う一方で、サービス間の通信ではメッセージングを利用し、各サービスはメッセージングを介して全体としてビジネスプロセスを実現します。 RESTはお手軽な解決策? そもそもメッセージングよりもRESTによる同期通信のほうがお手軽と思えるかもしれません。もちろん各サービス間でRESTを利用した通信を利用できないわけではなく、実態として多用されている現実もあります。しかし本当にお手軽な解決策といえるのでしょうか? RESTでは通信先サービスやさらにその先すべてのサービスが正常に稼働していなければ処理は行えません。そしてこのためにどこかで障害が発生すると連鎖的に機能不全のサービスが下流に向かって生まれてシステム全体にまで問題が波及してしまうことさえあります。さらにはそうしてどこかで取りこぼされてしまったリクエストは単に失われてしまいます。 またRESTベースの構成ではリクエストはダイレクトにWEBサーバーへの負荷となります。高負荷下ではシステムを構成する最も弱いサーバーを狙い撃ちにするでしょう。 こうしたことから信頼性を高めたり高負荷下で障害を広げないための仕組み(クラスタリング、ロードバランサ、サービスメッシュ、サーキットブレーカーなど)や、スパイクアクセスを考慮したリクエスト数やバックエンドの処理時間の予測、リソース使用量の分析、スレッド数やタイムアウト時間の調整、オートスケールの入念な設計、そしてこれらの十分な計画とテストが必要となります。 しかしどれだけ信頼性を高めたとしても、障害が発生しないことを前提としたシステム設計が許されるわけではありませんし、実際に障害やエラーは必ず発生します。データのクエリであればリトライで済むかもしれませんが、処理途中でエラーとなったコマンドをリトライすれば重複した処理やデータの不整合、あるいはタイムアウトなどのさらなるエラーを起こして状況はより複雑になり、その設計は困難かつ高価になります。 結果としてRESTではシステムが複雑になればなるほど、巨大なドメインモデルによるローカルトランザクションとモノリシックなシステム構成とする圧力が高まり、マイクロサービスから遠ざかってしまうこともあります。 なぜマイクロサービスにメッセージング(Kafka)が必要なのか? 一方でメッセージングではどうなのでしょう?メッセージングにはどのようなメリットがあり、なぜ必要とされるのでしょうか? 安定したシステムを支えるメッセージング プロデューサ側(送信側)では、メッセージの実行結果を待つことはないため、応答時間が向上し、少ないサーバで大きなスループットを実現できます。そして処理時間の予測や関連するタイムアウト調整なども最小限の考慮で済みます。 またコンシューマ側(受信側)は、メッセージングによるバッファにより、スパイクアクセスによっても高負荷状態に陥ることはなく、決められたスレッド数の中でメッセージを継続的に処理し続けます。常に安定した状況下でメッセージを処理することができ、リソース利用効率が高く、リソース割当の設計も容易です。そして負荷テストもある程度簡略化することが可能です。さらにメッセージが送られない時間帯に一時的にコンシューマ側(受信側)をすべて停止させてしまうことや、溜まったメッセージの状況に合わせてオートスケールさせることも比較的容易で、それほどシビアな利用予測や設計が求められることもありません。 メッセージングはエラーや障害にも強く、システムの可用性を保つのに役立ちます。サービスの協調の中で発行されるメッセージがKafkaのトピックに保存されていることで、障害やエラーによっても処理の中途状態は常に明確となり、それが失われることもありません。障害に陥っても復旧後にメッセージが処理されて、データの不整合や2重処理なども発生させず(重複配信の対処法は疑問編にて説明)に、引き続きビジネスプロセスが進行します。このため処理途中でサーバーが落ちることにも危険はなくなり、個別サーバーに過度な信頼性をもたせる必要もありません。 またメッセージングを通してサービス間が接続されることで、サービスの障害がシステム全体へと波及することは防がれ、仮に障害下であっても限定的なサービスの提供も可能です(バルクヘッド)。 このようにして、メッセージングによりシステム全体の安定性が向上するのです。 イベントは履歴になる(イベントは状態の変化を表し、イベントの蓄積が状態を再生する) とはいえ、一般的なアプリケーションでは、そもそもイベントなんか使ってないことが多いですね。わざわざイベントをシステムに追加するっていうのは至極面倒に思えます。 しかし、そもそもイベントは他のサービスやシステムへと情報を送るためだけの仕組みではありません。サービスの内部でもイベントは重要です。むしろイベントさえあれば本来はオブジェクトをDBに保存する必要さえありません。 一般的なアプリケーションでは、データベース上のレコードに最新の状態のみを保存します。そしてこれを中心に捉えると、オブジェクト状態の変更(レコードの変更)の結果として、イベントは追加で発行するものと考えがちです。 しかしこれを逆に考えれば、発行されたイベントの蓄積からオブジェクトの状態を復元できるということでもあります(イベントソーシング)。この考え方の場合には、データベース内にイベントのみが保存/蓄積され、オブジェクトそれ自体は保存されません。もしもオブジェクト状態を保存する場合には特定時点のスナップショットとして扱います。 コマンドの実行時など、オブジェクトが必要となる場合には累積されたイベントを順に再生(applyメソッド)しながらオブジェクトの状態を復元します。 そして、コマンドの実行の結果として、(オブジェクトそれ自体の状態変更は行わずに、)新しいイベントを追記します。 サービスの処理は通常、コマンドを受け取ってイベントを返すメソッド(processメソッド)として実装され、(アーキテクチャにもよりますが)データを書き換える処理はありません。イベントによる事実が追記によって積み重ねられていきます。 イベントはビジネス上も重要なデータです。イベントそれぞれの中にはオブジェクト内で何が起こったのかが記録されており、これらすべてが履歴として保存されていることで、各種の分析や監査で利用したり、任意の時点のオブジェクト状態を復元することさえ可能です。 イベントは何らかの付属物ではなく、他のサービスのために発行するものでもありません。むしろイベントさえ存在していれば後のものはどうとでも導出できます(極論ですが)。イベントはそれ単体で存在するべきものです。他のシステムのために追加で設計が必要な面倒なものと考えるのは適切ではありません。 様々なサービスやシステムと連携したり情報を統合するためのデータハブ イベントは単体としても存在するべきものですが、(部分的にでも)それを他のサービスに公開すれば通知やデータ転送の役割を担うようになります。 メッセージングはマイクロサービスのような小さく独立したサービスをイベントを通して連携させるために必要なのはもちろん、データウェアハウスや分析のシステムへのデータ転送のためにも必要です。 それから、現実的な問題として画面表示のデータを提供するのに特化したビューサービス(query-side service)を作成することも多くあります。マイクロサービスでは個別のサービスが分断される一方で、クライアントに対して様々な検索条件やサービスを横断して組み合わされたデータを一度に提供できる必要があります。つまり、(サービス内のドメインモデルとは別に、)表示や検索といったクエリのみに特化した参照用のモデルが必要です(CQRS)。このために、各種のサービスから発行されるイベントを集めて、統合された表示用のデータを作成する専用のビューサービスが必要になるのです。ビューサービスは、ユーザが求める様々な要件に応じて、それに適した複数のデータベースとビューを組み合わせて利用します。 そしてこれらの実現を支えるのがイベントを転送するデータハブとしてのKafkaの役割です。Kafkaは分散システムでのコミットログあるいはトランザクションログと表現される場合もあります。 イベントを利用することでサービス間の秩序ある関係を維持できる イベントを利用しないと、サービス間の秩序ある関係を維持することはできません。RESTによる同期通信は基本的にプル型(順方向の依存)なので、サービスがRESTのみを利用すると通信上の関係がそのままサービスの依存関係となって独立性を阻害します。 各サービスは独立しつつも、上流や下流といった秩序ある関係性を維持しています(コンテキストマップ)。サービスに対してコマンドを送ると、結果としてイベントを出力します。サービスはコマンドを受け取ることによっても、イベントを発行することによっても他サービスに依存することはありません。 しかしそれとは逆に、サービスがコマンドを送る、あるいはイベントを受けるためには、当該のサービスとドメインの知識に対して依存することになります。このため上流や下流の関係によって、通常は各サービス間でクエリやコマンドを送る側、あるいは逆にイベントを発行する側は決まります。 例えば、受注管理サービスからの在庫管理サービスへのコマンド発行は許されても、その逆の方向からのコマンドの発行まで許せば、相互の依存関係となるために独立したサービスの関係を阻害してしまいます。しかし、在庫管理サービスが状態変更に応じてイベントを発行し、受注管理サービス側でイベントを購読することで、依存関係を破綻させることなくサービス間の協調を実現することができます。 RESTによる同期通信は基本的にはプル型(順方向の依存)なので、上流や下流といった秩序あるサービスの関係性を維持するためにも、メッセージングを利用したイベント(逆方向の依存)は必要になります。 ところで、サービス間でコマンドを利用せずに、イベントのみでつないで協調させるという設計も、無秩序にサービスを関係づけることになります。これもまたサービスの独立性を阻害し、分散モノリシックなシステムとなるためにお勧めすることはできません。 後編で説明するように、ステートマシンベースでSAGAを行うプロセスマネージャーを利用すれば、そこで依存関係の調停を行うことができ、適切なサービスの関係を維持したままに協調させて、結果整合性を保ったトランザクションを実現することも可能です。 マイクロサービスでの責務の明確化が促進される 一般的なアプリケーションではオブジェクトをまるごとCRUDによりデータベースに出し入れするような実装(CQRSではない実装)とすることがあります。しかしマイクロサービスではこれにならってコマンドにまるごとオブジェクトを渡してサービスに更新を依頼するような汎用コマンドを設計することはできません。 ユースケース記述やサービス間のインタラクションから適切にそれぞれのコマンドを設計し、実装へと同期させなくてはなりません。適切にコマンドの定義がなされない場合には、それによって発生するイベントのタイプも限定(例えば常にUserUpdatedイベントになる)されて、その後の適切なビジネスロジックの駆動や分析が困難になります。 例えば、一口にユーザオブジェクトを更新するコマンドといっても、メールアドレスを更新する場合、住所を変更する場合、広告メールを停止する場合、退会してアカウントを削除する場合、それぞれで異なるビジネス上の対応や異なる実行権限が必要になるはずです。このために、それらそれぞれのためのコマンドと、その実行結果に対応した適切なイベントが必要です。 コマンドとイベントは適切な設計を行って実装まで正確にマッピングされるべきものです。メッセージングを利用することにより、適切にコマンドとイベントの設計が行われ、マイクロサービスでの責務の明確化が促進されます。 コマンド実行順の保証と並列実行 Kafkaを利用してコマンドを配信することには更なるメリットがあります。並列実行によりスループットが向上し、処理順も保証され、そしてサービス内の排他制御を省略することができます。 一般的なメッセージングシステム(Apache ActiveMQなど)ではコンシューマ(受信側)を1つにすれば1つずつ順番に処理することが可能ですが、複数のコンシューマを利用すると、それらへの配信順自体は順番どおりであっても、並列に処理されるために処理順は前後するのが一般的(MQ次第ですが回避策もあります)です。 一方で、Kafkaではトピック内のパーティションごとにコンシューマは固定されます。このため、トピック全体として見た場合には並列処理させてスループットを向上させる一方で、各パーティションのメッセージは割り当てられたコンシューマの中でシングルスレッドで1つずつ順番に処理(Single Writer)させることができます。 このため、オブジェクトのID(正確には集約ルートのID)をパーティションのキーとして利用すれば、各オブジェクトに対してコマンドは一度に1つずつのみが処理されます。そして複数のスレッドで同時に実行されるとデータの不整合が発生するようなの箇所(クリティカルセクション)での、悲観的ロックや楽観的ロックといったような排他制御を省略することができます。これはサービス実装をシンプルにし、スループットを向上させ、利用可能なデータベースの選択肢(排他制御の弱いNoSQLなどを含む)を増やします。またオブジェクトごとのメッセージ処理順(配信順ではなく)を前提としたシステム設計が可能となります。 まとめ 今回は概要編として、マイクロサービスでなぜメッセージングが必要なのかを説明してきました。まずマイクロサービスによるシステムにおいて、どこでメッセージングが利用されるのかを説明しました。そしてRESTを利用した場合に発生しうる各種の課題を確認する一方で、メッセージングによってシステム全体としての安定性が向上することも説明しました。もちろんマイクロサービスでもRESTは利用するのですが、サービス間ではメッセージングを利用することを検討してください。 またその後、イベントは履歴となり、またサービス間を連携し、秩序ある関係を維持するためにも重要な役割を担っていることを確認しました。そして最後にKafkaによってコマンドの実行順が保証され並列実行にも役立つことを説明しました。マイクロサービスにおいて、メッセージングによるコマンドやイベントは設計上も実装上もなくてはならないものです。 今回は概要編としてメッセージングの良い面ばかりに焦点を当てましたが、次の疑問編ではat least once(一回以上)の配信によるメッセージの重複配信にどう対処するのかとか、データ更新とメッセージ送信をどのようにアトミックに行うのかといったような、メッセージングでよく疑問とされることを説明したいと思います。 「 マイクロサービスとメッセージングのなぜ [疑問編] 」は こちら です。 Special Thanks 勉強会での議論がこの記事の基になっています。参加者の方々、そしてレビューしてくださった方々、どうもありがとうございました! JavaEE勉強会 : Microservices Patterns 読書会 JCUG : Camel In Action 読書会 参考文献 Microservices Patterns Enterprise Integration Patterns Designing Event-Driven Systems 実践ドメイン駆動設計 Exploring CQRS and Event Sourcing UMLによるビジネスモデリング [PR] オープンソースの Apache Kafka をベースとした製品として、Red Hatから AMQ Streams が提供されています。 AMQ Streams にはさらに、KubernetesやOpenShiftの上でApache Kafkaの運用の自動化を実現する、オープンソースの Strimzi をベースとした機能も含まれています。Red Hat製品に興味ない方もKubernetesを利用している方は Strimzi をぜひ使ってみてください。
この記事は Red Hat Developer の What comes after ‘iptables’? Its successor, of course: nftables を、許可をうけて翻訳したものです。 ::: By Florian Westphal October 28, 2016 ::: パフォーマンス: ユーザビリティ: nftablesとは何ですか? 何が置き換えられますか? なぜiptablesを置き換えるのか? nftablesでの高水準な機能 判断マップ(ジャンプテーブル) flow文 inetファミリー はじめる チェーンの追加 NAT 既知の制限 関連記事 nftablesは、既存のiptables、ip6tables、arptables、ebtablesを置き換えることを目指した、新たなパケット分類フレームワークです。これは、長く使われてきた ip/ip6tables ツールに存在する制限の多くを解決することを目指しています。古いiptablesと比較して、nftablesの最も注目すべき機能は以下のとおりです。 パフォーマンス: ルックアップテーブルのサポート - ルールをシーケンシャルに評価する必要がなくなります 暗黙のルールカウンタとアドレス/インターフェース・マッチングのオーバーヘッドを避けることができます ユーザビリティ: トランザクショナルなルール更新 - すべてのルールがアトミックに適用されます アプリケーションはnfnetlinkを購読することで、新しいルールが追加または削除されたときに通知を受信できます nftコマンドラインツールはマッチするルールのライブログを表示することができ、ルールセットのデバッグが容易です nftablesとは何ですか? nftablesは当初、2008年のNetfilter Workshop in Parisで発表された後、netfilterのコアチームメンバーとして長期間活躍していてプロジェクトリーダーでもあるPartic McHardyにより2009年3月にリリースされました。2013年後半にLinuxカーネルにマージされ、2014年にリリースされたカーネル3.13から利用可能です。 接続追跡及びNATファシリティなどのnetfilterフレームワークの多くの部分を再使用しています。 また、テーブル、チェーンやルールなどの命名法と基本的なiptablesのデザインのいくつかの部分を引き継いでいます。 iptablesと同じように、テーブルはチェーンのコンテナとして機能し、チェーンは個別のルールを含んでいます。それぞれのルールはパケットをドロップするなどのアクション実行、次のルールへ移動、新しいチェーンへのジャンプを実行することができます。 何が置き換えられますか? ユーザの観点からは、nftablesはiptables、arptables、ebtablesのすべてのツールを置き換えるnftと呼ばれる新しいツールを追加します。アーキテクチャの観点からは、パケットフィルタリングルールセットの実行時評価を扱うカーネルの部分を置き換えます。 なぜiptablesを置き換えるのか? その成功にもかかわらず、iptablesはいくつかのアーキテクチャ上の制限があり、それらは多くの根本的な設計変更なしには解決できません。ルールセットの表現がiptablesのようなフロントエンドツールとカーネルの間で共有されているという事実から主な問題が発生します。これは複数の欠点をもたらします。例えば、iptablesのルールセットにルールが追加されたとき # インバウンドssh接続を許可します iptables -t filter -A INPUT -p tcp --dport 22 -j ACCEPT iptablesのツールは、まず現在のルールセット(基本的にはさまざまなデータ構造の配列)をダンプする必要があり、その後、このブロブを変更して新しいルールを追加し、カーネルへルールのセット全体を送信します。これは、テーブル全体の処理と新しいものとの交換が必要なため、すべての追加と削除の操作が低速であることを意味しています。 カーネルは、置き換えられたテーブル内のどのルールが修正、追加または削除されたのか理解していません。 カーネルが理解しているのは、新しいテーブルのルールセットがロードされているということだけです。ルールセットのどこが削除または追加されたか、および副作用で影響をうけた(たとえば異なるチェーンへジャンプする際のオフセットが変わった)かはわかりません。 ユーザ空間は、ルールセットが何をしているのかわかりません。 iptablesのツールが行うのは、コマンドライン引数の解析とカーネルが期待するバイナリ表現への変換作業です。ユーザ空間には、「IP」または「TCP」の概念はありません。そのためユーザー空間でルールセットの最適化を行うには、カーネルの評価ロジックを複製する必要があります。 ルールの追加と除去はアトミック操作ではありません。 2つのルールが正確に同じ時間に追加された場合は、各プロセスが独立して、現在のテーブルを操作し、その後両方が、新しいテーブルで古いテーブルを交換するようカーネルを要求します。そのため最後に操作を行うプロセスがテーブルをダンプしてからコミットするまでの間に行われた変更は削除されます。これは、システム管理者のみがパケットフィルタルールを変更することが想定されていたiptablesと、その前身であるipchains(1990年代の終わりに開発された)では、問題ではありませんでした。 今日ではこの状況は変わっています。例えば、dockerまたはlibvirtのようなアプリケーションは、コンテナおよび仮想マシンの接続性を提供するために、NATルールを追加します。 iptablesのルールセットの形式は変更できません。 iptablesでは「リビジョン番号」方式により、マッチとターゲットで提供される機能を拡張するため、ルールのブロブは互換性を破壊せずにフォーマットを変更することができません。現在のフォーマットではいくつかの制限が課せられます。 カーネルはアドレスが全く与えられなかった場合であっても、IP送信元と宛先アドレスのマッチングを行います。この場合カーネルは0.0.0.0/0とパケットの送信元と宛先を比較し必ず成功します。IPアドレスと同様に入出力インタフェースも、各ルールのヘッダに格納されているため、各パケットについて常に照合されます。 ルールがマッチするたびにカウンタがインクリメントされます。 これは、期待通りのルールが評価されているかどうかを確認するための簡単でシンプルな方法になりますので、ユーザの視点からは良いものです。しかし、毎秒数百万パケットを処理する場合にはパフォーマンス上の問題になります。 特殊な機能をもつマッチとターゲットが増え続けています。 iptablesには、与えられたパケットが特定の基準を満たしているかどうかを判断するための様々な機能を実装した、いくつかの「マッチ」があります。 基本的なマッチ、たとえばパケットのペイロードからUDPまたはTCPポート番号を照合するものや、より精巧なチェックを実行するマッチ、たとえば conntrack マッチはコネクション追跡の状態を照合して、あるパケットが以前の逆方向のパケットに対する返信であることなどを確認します。他にも指定されたパケットがIPsec処理の対象となるか、パケットが特定のCPUに処理されているか(ロードバランシング時に有用)のマッチなどがあります。 新しいプロトコルをサポートするには、カーネルとiptablesユーザランドの両方を適宜拡張する必要があります。 iptablesは、さらに新奇なターゲットを有しています。例えばHMARKターゲットは、ハッシュ関数に基づいて、パケットのマークを設定します。これはポリシールーティングを介してロードバランシングをサポートするために追加されました。 もう一つの問題は、iptablesは1つのルールで複数のアクション(ターゲット)を実行することができないことです。 例えば、パケットをログに記録するルールを作成することは可能ですが、同じルールの中でそのパケットをドロップするようにカーネルに指示することはできません。 これは、「マークをつけ許可する」のように複数のアクションが望まれる場合、いくつかのルールを複製する必要があるということです。 基本的なマッチング機能が制限されている iptablesの1つのルールでは1つの送信元アドレス(またはネットワーク)、および1つの宛先アドレス/ネットワークをマッチさせることしかできません。また、送信元または宛先インターフェイスは1つに対してのみ照合できます。アドレスのリストまたは範囲を照合するには、「ipset」ツールなどの外部ファシリティを使用する必要があります。 nftablesは、これらすべての制限を克服するように設計されています。 iptablesのようなシーケンシャルな評価アプローチを強制するのではなく、セットとマップによるルールのコンパクトな表現を可能にします。 ルールの追加及び除去が常にアトミックです。2つのプログラムが同時に追加/削除するときにレース状態になりません。つまりユーザスペースはテーブル全体を変更するのではなく個別のルールを削除/追加します。 カーネルは個々のルールに対して固有の識別番号を付与し、iptablesと比べて、ルールの削除がずっと簡単になります。 アプリケーションは、ルールが追加または削除されたときにカーネルから通知を受け取ることができます。 マッチとターゲットの区別はありません。 nftablesは、機能を実装するために「式」と呼ぶものを使用しています。  例えばパケットにマークを設定し、同じルールで、それを受け入れることが可能です ユーザ空間にルール処理の大部分を移動します。処理はカーネルにも残りますが、プロトコルはユーザスペースで行われます。たとえば、TCP宛先ポート、IP送信元アドレスをロードする方法や、SPIをESPヘッダからロードする方法をカーネルは知りません。代わりに、NFTツールは、カーネルに特定のオフセットから指定したバイト数を読み込むよう依頼します。新しいトランスポートヘッダをサポートするためにカーネルを変更する必要はありません。ヘッダフィールド、名前、サイズ、場所がわかるようにnftツールだけを拡張します。 ルールの更新を監視できます。nft ツールは、ルールセットの変更を監視するために使用できます. たとえば、「nft monitor」は、カーネルに対して追加または削除されたすべてのルールを表示します。また、'trace mode'も提供されています。システム管理者がたとえば『nft add rule mangle prerouting ip saddr 10.2.3.4 meta nftrace set 1』と実行すると、『nft monitor trace』は、『IP アドレス 10.2.3.4 から送信されたパケット』にマッチするすべてのルールを表示します。 nftablesでの高水準な機能 nftables は、式と呼ばれる一連の命令を実装します。式は多数のレジスタにデータを格納、読み込みしてデータを変更できます。言い換えると、nftables コアは一種の仮想マシンとして見ることができます。 nftables フロントエンドである nft などのアプリケーションは、カーネルが提供する式を使用して、柔軟性を高めつつ、従来の iptables マッチを模倣できます。 たとえば、nft コマンドがパケットが指定の TCP 宛先ポートにマッチするかどうかのチェックを要求されると、TCP ヘッダーの開始から 2 番目のオフセットで 2 バイトを特定のレジスタに読み込み、そのレジスタを即値(ポート番号)と比較するようカーネルに指示します。 # nft add rule ip filter input tcp dport 22 内部で何が起きているかをよりよく理解するために、 --debug=netlink オプションを指定できます。上記の文でこれを実行すると、次のようなデバッグ出力が行われます。これは、ルールを構成する個々の式を示しています。 [ payload load 1b @ network header + 9 => reg 1 ] [ cmp eq reg 1 0x00000006 ] [ payload load 2b @ transport header + 2 => reg 1 ] [ cmp eq reg 1 0x00001600 ] したがって、ルールがカーネルで評価されると、最初に IP プロトコルフィールドをレジスタ 1 に読み込み、それを即値 6と比較(トランスポートヘッダーが TCP かどうかの確認)し、次に TCP 宛先ポートを読み込んで、次に、そのポートが即値 22 (ここでは 22 の ネットワークバイトオーダで 0x1600)と一致するかどうかを確認します。カーネルはプロトコルの詳細を知りません。単にデータをレジスタにロードして、比較を行うだけです。 このレジスターのアプローチは、基本的なビルディングブロックを連鎖することで、最も高いレベルの機能を実現することを意味します。 ほとんどの式は、レジスタからのデータを取得するか、またはレジスターに何かを格納する方法を知っているか、あるいは副作用を持つだけです。 レジスタを使用しない式の例:  log:(従来の iptables -j LOG や iptables -j NFLOG のように)カーネルリングバッファーを使用して、カーネルにログエントリーを出力させます。 limit:ログのフラッディングを避けるために、ログ式と組み合わせることが多く、レートポリシーの実装にも使用できます。 レジスタにデータを保存する式の例: payload: パケットペイロードからデータを読み込み、レジスタに格納します。 immediate: 指定のレジスターに数値を保存します。これは、たとえば、カーネルに accept 文および drop 文を実装するために使用されます。ユーザがパケットをドロップするよう指示すると、nftはverdict(判断)レジスタにimmediate式を使って判断コードを保存します。 meta:パケットが到達した入力または出力インターフェースの名前、skb マーク、パケットの長さ/サイズなど、補助的なデータ ct:パケットの方向(オリジナル、応答)、接続追跡カウンタ(これまでに確認したバイト/パケット)、状態(new、established、など)のような接続追跡に関連したデータへのアクセスを提供します。 一部の式では、複数個の属性を設定できます. たとえば、payload式でも逆(レジスタの値を取得してパケット内の特定の場所に配置)の実行が可能です。meta式でパケットマークを設定するようなことができます。 その他の式を使用して、レジスタの値を操作することもできます。 たとえば、ユーザーが特定の IP アドレス 10.0.0.0 ではなく、ネットワーク 10.0.0.0/8 とマッチするように要求すると、nft ツールは IP アドレスをレジスタにロードするように要求し、'bitwise' 式を使用してアドレスの上部 (10) だけを保持し、レジスタを 即値 10 と比較します。 たとえば、特定の接頭辞('ppp'など)で始まるインターフェース名の照合を要求する場合も同様です。 その他のレジスタでテストを実行する式。 cmp: レジスタの値が、指定された即値より小さいか、等しいか、より大きいかをチェックします。これは、カーネルが IP アドレス範囲の照合などに対応する必要がないことを意味します。ユーザランドは単純に2つの比較式を指定します。 reg1 >= 0xc0a80010 and reg1 <= 0xc0a80280 レジスタに保存された IP アドレスがアドレス範囲内であるかどうかを判定します。 lookup:特定のセットの中にレジスタの値が存在するかを確認します。nftables は、データのセット(アドレス、ポート番号、インターフェース名など)をサポートします。 たとえば、nftables では、TCP 宛先ポートが一連の値に含まれる場合にマッチするルールを作成できます。 # nft add rule ip filter input tcp dport { 80, 443, 8080 } nft は内部的に、指定された3つの数字をキーとして含む新しいセットを作成します。そして、nftables マシンに結果があるかどうかを確認させます。 [ payload load 1b @ network header + 9 => reg 1 ] [ cmp eq reg 1 0x00000006 ] [ payload load 2b @ transport header + 2 => reg 1 ] [ lookup reg 1 set __set%d ] これらは、以前とほとんど同じ式ですが、パケットが TCP パケットかどうかを確認し、レジスタを即値と比較するのではなく、レジスタの値がセットに含まれているかどうかを尋ねます。セットは、キーを格納するだけでなく、キーと値のペアを格納することもできます。 lookup 式は、このようなキー/値セットから値を取得することもできます.つまり、ユーザースペースはキーから値へのテーブルマッピングを作成し、そのセットから取得したコンテンツでレジスタを埋めるように要求できます。 これは、ルールセットに分岐(判断マップ)を実装する場合などに使用できます。 判断マップ(ジャンプテーブル) iptables ルールは、次のルールに移動するのではなく、カスタムチェーンに移動するようにカーネルに指示することができますが、分岐を使用することはできません。たとえば、以下の例を考えます。 iptables -N MGMT_PACKETS iptables -N VM1_PACKETS iptables -N VM2_PACKETS iptables -A FORWARD -i eth0 -s 192.168.0.1 -j MGMT_PACKETS iptables -A FORWARD -i tap0 -s 10.0.0.1 -j VM1_PACKETS iptables -A FORWARD -i tap1 -s 10.0.0.2 -j VM2_PACKETS これにより、3つの新しいチェーンが追加され、転送されたパケットがソースアドレスと入力インターフェースの特定の組み合わせから送信されている場合、より具体的な処理のためにチェーンを評価するようにカーネルに指示します。ルールセット評価はシーケンシャルであるため、カーネルはマッチするルールを検出するまでこれらのルールをすべてチェックする必要があります。これは数百のインタフェースとアドレスの組み合わせを考慮する必要がある場合には十分スケールしません。 ここで、lookupテーブルを使用します。 nftables を使用すると、上記のルールを1つのルールで表すことができます。 chain forward { type ip filter hook forward priority 0; policy accept; iif . ip saddr vmap { eth0 .192.168.0.1 : jump mgmt_packets, tap0 .10.0.0.1 : jump vm1_packets, tap1 .10.0.0.2 : jump vm2_packets } } chain vm1_packets {} chain vm2_packets {} chain local_packets {} nftables の「.」構文は、1つのキーを構成するために、2つの異なるソースを連結するために使用されます。 ここでは、入力インターフェースと IP ソースアドレスで構成されるキーを使用して、ジャンプ先を取得するように nftables に指示します。ここで、この例を、数十、あるいは数百の「パケット送信元が特定のインターフェース/アドレスのペアであればこのチェーンへ移動」と考えてみましょう。iptables では、これはスケールしません。 しかしnft では、参照テーブルにジャンプターゲットを追加しても、追加の情報を保存するメモリ消費以外には追加の処理コストがかかりません。 flow文 iptables は「hashlimit」というマッチを提供します。 limitマッチに似ていますが、limitを特定のプロパティをキーとして行うことができる点が異なります。たとえば、送信元IPアドレスや送信元と宛先のペアに対してhashlimitを利用できます。これを行うために、hashlimit マッチは、これらのレート制限カウンターを動的に作成し、それをハッシュテーブルに格納します。 1 対 1 の置き換えを実装する代わりに、nftables は同じコンセプトをより汎用的な方法で実現しています。IP アドレスまたはネットワークごとに制限を動的にインスタンス化するだけではなく、任意の基準のキーに対して式を対応づけます。iptables の hashlimit マッチと同様の効果がある例は以下のようなものです。 nft add rule ip filter input flow table mylimit { ip saddr . tcp dport limit rate 10/second } flow 文には、特定の期間で認識されなかったフローを自動的に削除するタイムアウトオプションもあります。また、ホスト別やサービス別のアカウンティングにも、同じ文を使用できます。 nft add rule ip filter forward flow table ipacct { ip daddr counter } nft add rule ip filter forward flow table portcount { tcp dport counter } 次のコマンドを使用して、フローテーブルを一覧表示できます。 nft list flow tables その内容は以下のようにして確認できます nft list flow table filter ipacct table ip filter { flow table ipacct { type ipv4_addr elements = { 192.168.7.1 : counter packets 4 bytes 336, 192.168.0.7 : counter packets 10 bytes 688, 127.0.0.1 : counter packets 18 bytes 1512} } } このフローテーブルには、認識されている IP アドレスとそのカウンターが一覧表示されます. これにより、ホスト別のトラフィックアカウンティングを簡単に実行できます。 inetファミリー IPv4 および IPv6 の両方を使用するホスト用のルールセットの作成を容易にするため、nftables には IPv4 と IPv6 の両方に適用される 1つのルールセットを作成するのに使用できる新しい「inet」ファミリーが同梱されています。 これは、デフォルトの ip キーワードの代わりに inet キーワードを使用します。 table inet filter { chain input { type filter hook input priority 0; policy accept; tcp dport ssh ip saddr 1.2.3.4 } } これは2つのルールを含んでいます。最初のルールは、共通のプロトコル(この場合は TCP)のみを調べるため、IPv4 と IPv6 の両方のトラフィックとマッチします。2つ目のルールは、IP アドレスをテストします。ルールは ipv4 固有の ip テーブルではなく、ipv4 と ipv6 の組み合わせの inet テーブルにあるため、nftable は、いわゆる「payload dependency」を挿入しています。以下のようなものです inet filter input 5 [ meta load l4proto => reg 1 ] [ cmp eq reg 1 0x00000006 ] [ payload load 2b @ transport header + 2 => reg 1 ] [ cmp eq reg 1 0x00001600 ] inet filter input 6 [ meta load nfproto => reg 1 ] [ cmp eq reg 1 0x00000002 ] [ payload load 4b @ network header + 12 => reg 1 ] [ cmp eq reg 1 0x04030201 ] これは2つのルールを含んでいます。 最初のルールは、共通のプロトコル(この場合は TCP)のみを調べるため、IPv4 と IPv6 の両方のトラフィックとマッチします。2つ目のルールは、IP アドレスをテストします。 ルールは ipv4 固有の ip テーブルではなく、ipv4 と ipv6 の組み合わせの inet テーブルにあるため、nftable は、いわゆる「payload dependency」を挿入しています。最初にパケットが IPv4 (nfprotocol 2) であることを確認し、次にパケットからペイロードを読み込みます。 これは、別のファミリーからのパケットが処理される場合に、誤検出を防止するために必要です。これにより、ipv4 および ipv6 に固有のルールを inet テーブルに追加するか、またはブリッジファミリーへ ipv6 ヘッダーをテストするルールなどを追加できます。 payload dependencyは一般的なものです。 上記の最初のルールにも1つ含まれています。たとえばUDPパケットの22番ポートに対する処理中の誤検出を防ぐため、nftablesは レイヤ4プロトコルが6、つまりTCPであることを確認するルールを挿入しています。 はじめる iptables、ip6tables、ebtables、 arptables、iptables-save、iptables-restore などに慣れているユーザーは、構文が完全に異なることに気付くでしょう。 しかし、概念上の類似点が多数あるため、がっかりすることはありません。nftables と iptables の両方を同時に使用することも可能です。唯一の制限はネットワークアドレス変換です。 iptable_nat もしくは ip6table_natモジュールが読み込まれている場合には、nftablesの「nat」チェーンは効果がありません。 最新のディストリビューションを使用し、'nftables' パッケージをインストールして、'nft' ツールを手に入れましょう。 nft は、ルールを追加/削除するのに必要な唯一のコマンドです。 nft は、コマンドラインから使用することも、対話(シェル風)モードでの使用も、保存されたルールセットをファイルから復元することもできます。nftが利用可能なら、まずは見てまわります。これは、実稼働システムではなく、vm またはテストマシンで行いましょう。 # nft list tables デフォルトではテーブルはありませんので、新規のシステムでは何も出力されません。次に、新しいテーブルを作成します。 # nft add table ip foo # nft list tables table ip foo # nft list table foo table ip foo {} ご覧のとおり、foo という名前の新しい空のテーブルが作成され、これはipv4 パケットのみを扱います。 nftables で利用可能な他のファミリーは、arp、ip6、bridge、inet、および netdev です。 arp ファミリーは ARP パケットのみを表示し、 ip6 ファミリーは IPv6 パケットのみを処理します。 bridgeファミリーは、イーサネットパケットのみを処理し、 inet ファミリーは、IPv4 パケットと IPv6 パケットの両方を受け取ります。 netdev ファミリは、ネットワークインターフェースにテーブルを直接アタッチするのに使用できます。 netdev ファミリーのテーブルには、インターフェースで受信したあらゆるトラフィックのフィルタリングに使用できる新しい「ingress」フックポイントがあります。 ファミリーとして処理するパケットタイプを定義する他には、テーブルに特別な意味はありません。チェーンのコンテナーとしてのみ動作します。テーブルは、個別の名前空間を定義するため、異なるテーブルに存在するものであれば、同じ名前を持つチェーンを作成できます。   チェーンの追加 iptables に精通しているユーザーは、各テーブルに組み込みチェーンがあることをご存知でしょう。iptables では、これらは、PREROUTING(受信パケット用)、INPUT(ローカルマシン向けパケット用)、 FORWARD(転送またはルーティング中のパケット用)、 OUTPUT(ローカルで生成したパケット用)、および POSTROUTING(送信されるすべてのパケット用)と呼ばれます。 nftables には、これらの組み込みチェーンはありません。 ただし、これらとまったく同じフックポイントが定義されます。フックポイントはカーネルネットワークスタック内の特定の場所で、パケットを検査して検査の結果にもとづいてドロップや許可のような判断をおこなう機能を提供します。これらのフックは、 iptables、ip6tables、ebtables、および arptables の内部でも組み込みチェーンを登録するために使用されています。そこで、inputフックポイントでパケットをフックするチェーンを追加してみましょう。 # nft add chain ip foo bar '{ type filter hook input priority 0; }' # nft list table foo table ip foo { chain bar { type filter hook input priority 0; policy accept; } } 何を行ったのでしょうか? カーネルに対して、ローカルマシン向けのパケット(inputフック)を'bar' という名前のチェーンに含まれるルールを介して送信するよう伝えています。 iptables のフィルターテーブルを真似するには、名前をbar から INPUT に変更し、さらに作成していきます。 chain FORWARD { type filter hook forward; policy accept; } ...outputフックについても同様です。 フック名を含むチェーンは、ベースチェーンと呼ばれます。同じフックポイントでも、複数のベースチェーンが存在する可能性があります。この場合、優先度番号が最も小さいチェーン(負の値にもできます)が最初に処理されます。 ベースチェーンの優先順位が同じ場合、順序は未定義になります。 ルールを追加しましょう。 # nft add rule ip foo bar counter # nft list table foo table ip foo { chain bar { type filter hook input priority 0; policy accept; counter packets 2 bytes 168 } } ご覧のとおり、1つのテーブルがinputフックポイントにフックされた1つのチェーンを持ち、検出されたすべてのパケットについてカウンターを増加するだけのルールを持っています。単一のルールを追加するためだけに、この作業をすべて実行する必要がある理由、または「type filter」の意味を知りたいことでしょう。 nftables には組み込みテーブルはありませんが、ip (6)tableの組み込みテーブルのnft版を提供する、事前作成済みのルールファイルが付属しています。まず今まで行った変更を元に戻し、nftables で作成される mangle テーブルを確認しましょう。 # nft delete table foo # cat /etc/nftables/ipv4-mangle #!/sbin/nft -f table mangle { chain output { type route hook output priority -150; } } (ディストリビューションによっては、/usr/etc/nftables などのように、別の場所に配置をしている場合があります。) このテーブルは、次のコマンドで読み込むことができます。 # nft -f /etc/nftables/ipv4-mangle 特別な「route」タイプは、パケットがチェーンを通過した後に新しいルートルックアップを実行するよう netfilter コアに指示します。 これは、ほとんどの場合、ポリシールーティングを実装するために、パケットマークおよび「ip rule」コマンドと共に使用されます。古い iptables 環境では、このプロパティは mangle テーブルにハードコードされますが、nftables ではベースチェーンのプロパティです。ルート変更のためにパケットのプロパティを変更するチェーンは、このキーワードを使用して再参照をトリガーする必要があります。旧世代のツールでは組み込みであった他のテーブルにも、同様のスケルトンルールセットが存在します。 以下に例を示します。 # ls /etc/nftables bridge-filter  ipv4-filter  ipv4-nat  ipv6-filter  ipv6-nat  inet-filter    ipv4-mangle  ipv4-raw  ipv6-mangle それでは、より実用的な、IPv4 および IPv6 の簡単なフィルターセットを追加してみましょう。 # nft -f /etc/nftables/inet-filter # nft add rule inet filter input iif lo accept ...自分自身の接続への応答トラフィックを受け入れます。 # nft add rule inet filter input ct state established,related accept ... ipv6 近隣探索を受け付けます。 # nft add rule inet filter input ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, echo-request, nd-router-advert, nd-neighbor-advert } accept ... 誤ってロックアウトされることがないよう、ドロップなしでカウンターを追加します。 # nft add rule inet filter input counter 新しいルールセットを見てみましょう。 # nft list table inet filter table inet filter { chain input { type filter hook input priority 0; policy accept; iif lo accept ct state established,related accept ip6 nexthdr ipv6-icmp icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert, echo-request} accept counter packets 0 bytes 0 } } 次に、ルールの削除方法を説明します。nftables では、内部の「handle」を使用してルールを削除できます。これはカーネルがルールに付与した番号です。 この番号を確認するには、handle オプションを指定して上記のコマンドを再実行します。 # nft --handle list table inet filter [..] counter packets 0 bytes 0 # handle 10 } } # nft delete rule inet filter input handle 10 上記を実行すると、カウンタールールが無効になります。テーブルを削除して再作成せずに、新しく作りなおしたい場合は、 # nft flush table inet filter とすることですべてのチェーンを維持したままルールを削除します。 NAT nftables で定義されていると、ipv4 nat テーブルは次のようになります。 # cat  /etc/netfilter/ipv4-nat table nat { chain prerouting    { type nat hook prerouting priority -150; } chain postrouting    { type nat hook postrouting priority -150; } } 唯一の違いは、「type nat」です。これはカーネルに新しいコネクションの最初のパケットを指定されたチェーンに渡すよう指示します。 チェーン名およびテーブル名は重要ではないことに注意してください.たとえば、次のようにもできます。 # nft add chain filter myredirects “{ type nat hook prerouting; }” これにより、filterというテーブル内の、チェーン「myredirects」でネットワークアドレス変換を設定できます。 nft add rule filter myredirects ip saddr 192.168.0.024 tcp dport 80 redirect to :8080 nftables は、SNAT およびマスカレードもサポートします。iptables MASQUERADE と同様に、後者は SNAT マッピングの特別なケースで、ネットワークインターフェースのプライマリーアドレスを使用します。 これは、ダイヤルアップ接続などのアドレスを動的に割り当てたインターフェースに適しています。 nft add rule nat postrouting ip saddr 192.168.0.0/24 oif eth0 snat 10.2.3.4 nft add rule nat postrouting ip saddr 192.168.0.0/24 oif eth0 masquerade 既知の制限 ネイティブの nft 置き換えがない iptables マッチおよびターゲットが、いくつか存在します。最も重要なものは、次のとおりです。 パケットを処理するためにIPsec が使用するポリシーを調べるために使用されるポリシーマッチ。 パケットに対して逆パス IP フィルター(逆引きルートルックアップ)のテストを実行するための rpfilter フィルターマッチ TCP 最大セグメントサイズを変更する TCPMSS ターゲット (訳注: 2019年現在では、これらの重要な差分についてそれぞれ、ipsec式、rpfilterに対応するfib式、 tcpmssの指定に利用できるextension header文が整備されています。 ) iptables マッチとネイティブ nft 置き換えの一覧 Ah, esp, dscp, sctp, ecn, hbh, hl, mac, mh, rt (payload 式) Cgroup, cpu, mark, devgroup, length, owner, pkttype,  realm, statistic (meta 式) Connbytes, connlabel, connmark conntrack, helper (ct 式) Comment Hashlimit (flow 文) Limit Iprange, multiport (sets) iptables ターゲットとネイティブ nft 置き換えの一覧 CLASSIFY (meta 式) DNAT, SNAT, MASQUERADE, REDIRECT DSCP, ECN, HL, TTL (payload 式) LOG, NFLOG (log 文) MARK, TRACE (meta 文) NFQUEUE REJECT TEE arptables と ebtables は、機能完全と見なされています。つまり、すべての ebtablesマッチにネイティブ nft置き換えがあり、唯一の例外は ebtables の'arpreply'ターゲットです。 rpfilterとポリシーマッチのためのネイティブ置換が実装中です。tcpmss を強制する機能は、将来追加されるでしょう。 (以下は訳者による追記) 関連記事 関連記事へのリンクです。 nftablesとiptablesのベンチマークによる比較記事です。 developers.redhat.com nftablesを利用する場合、 nftables wikiのドキュメントを読むのがおすすめです。 wiki.nftables.org firewalldはnftables backendに対応していて、RHEL8ではデフォルトで提供されます。 firewalld.org iptables, ip6tables等のコマンドを拡張して、カーネルのバックエンドとしてnftablesを利用できるよう開発されています。RHEL8ではデフォルトで提供されます。 wiki.nftables.org
お疲れ様です!Red Hatの鈴木(tomosuzu)です! 前回に続き、プラクティスを紹介する記事を書かせていただきました。 今回は「テスト駆動開発 」です。 私の経験ですが、テスト駆動開発を忠実に行うようになってから開発状況が劇的に改善しました。 もし、まだやったことがないという方いらっしゃいましたら、 ぜひ、試しにやってみてください。 tomosuzu.com わたしは前職が開発エンジニアでしたので、 開発プロセス寄りのプラクティス紹介が続いていましたが、 次回はチーム作りに関するプラクティスである「社会契約」を紹介する記事を投稿する予定です。 さいごに Red Hatでは、「アイデアをイノベーションに変える、文化・プロセス・技術・実践の場」として、 Open Innovation Labs を提供しています。 デザイン思考やDevOps、アジャイルやリーン開発などをRed Hatのコンサルタントと一緒に取り組むことができます。 過去記事 アジャイル開発のプラクティス一覧をご紹介いたします - 赤帽エンジニアブログ アジャイル開発のプラクティスのご紹介「ペアプログラミング 」 - 赤帽エンジニアブログ
お疲れ様です!Red Hatの鈴木( tomosuzu )です! 前回 に続き、プラクティスを紹介する記事を書かせていただきました。 今回は組織の知識共有を促進させたり、開発の品質を向上させる効果のある「ペアプログラミング 」です。 tomosuzu.com 次回は「テスト駆動開発」を紹介する記事を投稿する予定です。 さいごに Red Hatでは、「アイデアをイノベーションに変える、文化・プロセス・技術・実践の場」として、 Open Innovation Labs を提供しています。 デザイン思考やDevOps、アジャイルやリーン開発などをRed Hatのコンサルタントと一緒に取り組むことができます。 過去記事 アジャイル開発のプラクティス一覧をご紹介いたします - 赤帽エンジニアブログ
お疲れ様です!Red Hatの鈴木( tomosuzu )です! 本日から定期的にアジャイルやDevOpsなどで利用されているプラクティスのご紹介をさせていただきます。 最初となる今回は、最もベースとなる概念である「Outcome Deliveryフレームワーク」に触れつつ、 どんなプラクティスがあるのかを一覧できる記事を書きました。 tomosuzu.com 今後、随時この記事で紹介したプラクティスの詳しい内容の記事を書かせていただきます。 次回は「ペアプログラミング」を紹介する記事を投稿する予定です。 さいごに Red Hatでは、「アイデアをイノベーションに変える、文化・プロセス・技術・実践の場」として、 Open Innovation Labs を提供しています。 デザイン思考やDevOps、アジャイルやリーン開発などをRed Hatのコンサルタントと一緒に取り組むことができます。
こんにちは、Red Hatの森( @mosuke5 )です。 昨日(7/17) にRHCP2019(Red Hat Certified Professional)という、トレーニングなどを担当する部門(Global Learning Service、通称GLS)主催で、「学び」に関するイベントが開催されました。 イベント全体の開催レポートについては別途出ると思いますが、今回このイベントに4月入社の新人3人が出てきたので、新人の観点で簡単に報告します。 events.redhat.com 先日、試用期間を抜けてばかりの3人ですが、いったいどんなトークをしたのでしょうか。 新人だからこそ気づいた、Red Hatのユニークなカルチャーでの学びや、前職や過去の経験から振り返ったことなどについて思いを発しました。 ※業務としてはじめての登壇ということでみんなだいぶ緊張している感じでした(汗) 個人的な感想としては、3人共なんらかで「Red Hatのオープンソースなカルチャー」に共感している点やそこから大きく学んだ点が含まれているのが印象的です。 以下、3人のスライドと一言コメントです。 1. テックブログのすゝめ (森) いまのこの業界、ひとりの人がカバーしなければいけない技術領域が非常に広くなってきています。 最近の自分の関心事の1つに、この進歩が激しく難しい技術をどうやって効率的に人や組織が学んで強くなることができるか、があります。 正直ひとりのひとが単純に頑張るだけでは辛いなあと思います。 学びの方法の1つとして、長く続けてきたテックブログをご紹介しました。 テックブログのすゝめ -アウトプットで知識習得サイクルを回そう- from Shinya Mori (@mosuke5) エンジニアとして学び続ける意義 (鈴木) はじめまして。鈴木(tomosuzu)です。今年の4月からコンサルタントとしてRed Hatに入社しました。 今回の発表では「学び」をテーマに、私の中で改めて振り返り、最も大事だと思う点について、ご紹介させていただきました。 なぜ学習するのかから始まり、学習するために意識したいこと、 最後に私の学習方法について、共有させていただきました。 全体的にゆるめの内容となっていますので、お気軽にご覧ください。 おそらく、スライドを流し読むだけなら、所要時間は1分くらいだと思います。 speakerdeck.com 新米Red Hatterの振り返り(長嶺) はじめまして、長嶺です。 上の2人と同じく今年の4月からコンサルタントとしてRed Hatにjoinしました。 今回のRHCPのテーマである「学び」ですが、皆さんはこれまでどのようにそれを捉え、向き合ってきたでしょうか?私自身も入社3ヶ月ほど経過してタイミングもよかったので、自らの経験を振り返りつつ、資格取得や入社後の取り組みを交えながらお話しさせていただきました。 speakerdeck.com さいごに お忙しい中ご来場いただいたみなさまありがとうございました。 聞いた方が学びとなってくれたか不安に思う部分もあるのですが、われわれ自身も振り返ることで大きな学びありました。 Red Hatterとしてはまだまだ未熟なので、これからも精進していきます。 このようなイベントは、また近い内に実施するよう?なので機会あれば参加してみてください。 すでにレポートを書いていただいた参加者もいたのでリンク掲載しておきます。写真もあるので、様子がよくわかります。 mitsunooon.hatenablog.com
こんにちは、レッドハットでストレージを中心にクラウドインフラを生業にしている宇都宮です。 前回 は、Kubernetes(k8s)のストレージオーケストレーターのRookを紹介しました。 今回は実際にk8sにRookを入れてCephをPersistent Volume(PV)として使ってみるとこをお見せしたいと思います。 ところで『Operator』ってご存知ですか?このブログでも弊社が誇る正義超人達がちょいちょいOperatorに言及してますが、来る7/26(水)に開催予定の OpenShift Meetup Tokyo #5 はなんとこのOperator特集の回なのです! ご存知でない方はこちらに来場していただけるとバシバシガッテン押せると思いますよ!ブログ/Tweet発信枠と大阪のサテライトは空きがあるので是非来てね! 以上番宣でした! 用意するKubernetesクラスター Rookは2019年5月に v1.0 がリリースされ晴れてメジャーデビューしたので、今回もこれを入れます。 Rook v1.0はv1.10以上のk8sが必要で、あとv1.14以上のk8s clientも必要となります。 僕はv1.14以上が必要だったのでkopsで自炊しましたがGKE,AKS,EKSでも全然オッケーです。 ※ minikubeでも問題なく動きます。けれどストレージ的にはマルチノードクラスターの方が面白い実験ができるのでおすすめします。kubeadm-dindやkindを使うのも手かもしれません。 ともかく、僕はAWSで3 masters + 3 workersのクラスターです。ちなみにworkerにはそれぞれ10GBのEBSを3つずつぶら下げています。 [utubo@tutsunom ~]$ kubectl version Client Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.3", ... Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.1", ... [utubo@tutsunom ~]$ kubectl get node NAME STATUS ROLES AGE VERSION ip-172-20-117-7.ec2.internal Ready master 25d v1.14.1 ip-172-20-37-237.ec2.internal Ready node 25d v1.14.1 ip-172-20-54-52.ec2.internal Ready master 25d v1.14.1 ip-172-20-77-127.ec2.internal Ready node 25d v1.14.1 ip-172-20-89-111.ec2.internal Ready master 25d v1.14.1 ip-172-20-99-100.ec2.internal Ready node 25d v1.14.1 これからここにRook-Ceph Operatorを入れて、3台のworker nodeでCephのストレージを作っていきます。 OperatorHubからRook-Ceph Operatorを入れよう operatorhub.io 前回、Rook-Ceph OperatorはOperatorHubで公開されていると書きました。せっかくだから俺は赤い扉をとばかりにここから入れてみましょう。 ↑のリンク先に行くと右側に"Install"ってボタンがあります。このボタンを押したらやるべきことがpop-upで表示されるので、それに従うだけでOK。 pop-up画面。やることはめっちゃシンプル。 Operator Lifecycle Manager(OLM)をインストール    ☆ OpenShift 4はデフォルトでOLMはインストールされているのでこの作業はいりませんよ ☆ OperatorHubからOperatorをインストールするには、k8sにOperator Lifecycle Manager(OLM)がインストールされている必要があります。 OLMって何という説明はここでは置いといて、とりあえずインストールします。pop-upの中の"Show Prerequisite"の中にあるコマンドをそのままコピペして打てばOK。 olmとoperatorsというnamespaceができていて、olmの中でolm-operatorが動いているはずです。 [utubo@tutsunom operatorhub]$ curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/0.10.0/install.sh | bash -s 0.10.0 customresourcedefinition.apiextensions.k8s.io/clusterserviceversions.operators.coreos.com created customresourcedefinition.apiextensions.k8s.io/installplans.operators.coreos.com created customresourcedefinition.apiextensions.k8s.io/subscriptions.operators.coreos.com created customresourcedefinition.apiextensions.k8s.io/catalogsources.operators.coreos.com created customresourcedefinition.apiextensions.k8s.io/operatorgroups.operators.coreos.com created namespace/olm created namespace/operators created clusterrole.rbac.authorization.k8s.io/system:controller:operator-lifecycle-manager created serviceaccount/olm-operator-serviceaccount created clusterrolebinding.rbac.authorization.k8s.io/olm-operator-binding-olm created deployment.apps/olm-operator created deployment.apps/catalog-operator created clusterrole.rbac.authorization.k8s.io/aggregate-olm-edit created clusterrole.rbac.authorization.k8s.io/aggregate-olm-view created configmap/olm-operators created catalogsource.operators.coreos.com/olm-operators created operatorgroup.operators.coreos.com/global-operators created operatorgroup.operators.coreos.com/olm-operators created subscription.operators.coreos.com/packageserver created catalogsource.operators.coreos.com/operatorhubio-catalog created Waiting for deployment "olm-operator" rollout to finish: 0 of 1 updated replicas are available... deployment "olm-operator" successfully rolled out deployment "catalog-operator" successfully rolled out Package server phase: Installing Package server phase: Succeeded deployment "packageserver" successfully rolled out [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl get namespaces NAME STATUS AGE default Active 25d kube-node-lease Active 25d kube-public Active 25d kube-system Active 25d olm Active 3m27s operators Active 3m27s [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl get pod -n olm NAME READY STATUS RESTARTS AGE catalog-operator-6c6dd66dc9-jl8jt 1/1 Running 0 5m31s olm-operator-54bfd7d46c-hrrg7 1/1 Running 0 5m32s olm-operators-n9svq 1/1 Running 0 5m28s operatorhubio-catalog-j8sw4 1/1 Running 0 5m25s packageserver-5c8965964-59dj8 1/1 Running 0 5m19s packageserver-5c8965964-5v26s 1/1 Running 0 5m19s Rook-Ceph Operatorをインストール 次にRook-Ceph Operatorをインストールしましょう。さっきのpop-upに表示された kubectl create コマンドを実行します。 my-rook-ceph というnamespaceができていて、その中で色々なrook系のpodが見えると思います。 今回は、まずはRook-Cephを使ってみようという趣旨なので、各podがどんな役割かなどアーキテクチャー的な部分はひとまず置いておこうと思います。(第4話くらいにアーキの説明をしようかなと思ってます) [utubo@tutsunom operatorhub]$ kubectl create -f https://operatorhub.io/install/rook-ceph.yaml namespace/my-rook-ceph created operatorgroup.operators.coreos.com/operatorgroup created subscription.operators.coreos.com/my-rook-ceph created [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl get ns NAME STATUS AGE default Active 25d kube-node-lease Active 25d kube-public Active 25d kube-system Active 25d my-rook-ceph Active 8s olm Active 14m operators Active 14m [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl get all -n my-rook-ceph NAME READY STATUS RESTARTS AGE pod/rook-ceph-agent-klc8s 1/1 Running 0 5m45s pod/rook-ceph-agent-rfp7f 1/1 Running 0 5m45s pod/rook-ceph-agent-sxqtv 1/1 Running 0 5m45s pod/rook-ceph-operator-54cbc5bf7c-5vmjp 1/1 Running 0 5m46s pod/rook-discover-6mw4s 1/1 Running 0 5m45s pod/rook-discover-7tpbj 1/1 Running 0 5m45s pod/rook-discover-z46hf 1/1 Running 0 5m45s NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE daemonset.apps/rook-ceph-agent 3 3 3 3 3 <none> 5m46s daemonset.apps/rook-discover 3 3 3 3 3 <none> 5m46s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/rook-ceph-operator 1/1 1 1 5m47s NAME DESIRED CURRENT READY AGE replicaset.apps/rook-ceph-operator-54cbc5bf7c 1 1 1 5m47s これでRook-Ceph Operatorはインストール完了です。 Cephをデプロイしてアプリから使ってみよう それではいよいよCephクラスターをデプロイします。大まかな流れとしては、 Cephクラスターを作る Poolを作る StorageClassを作って、PVCでクレームする です。さっそくいってみましょう。 1. Cephクラスターを作る OperatorHubのRook-Cephのページの下の方から、"Ceph Cluster"と書かれたCRDのyamlファイルの例を見ることができます。例の中身はこんな感じです。 apiVersion: ceph.rook.io/v1 kind: CephCluster metadata: name: my-rook-ceph namespace: my-rook-ceph spec: cephVersion: image: 'ceph/ceph:v14.2.1-20190430' dataDirHostPath: /var/lib/rook mon: count: 3 dashboard: enabled: true network: hostNetwork: false rbdMirroring: workers: 0 storage: useAllNodes: true useAllDevices: true 細かい事は考えずに、全部のworkerと全部の利用可能なデバイスを使ってCephクラスターを作るぜ、と書いています。シンプルイズベスト、レスイズモア。男気溢れてステキ。 ひとまずこの内容を create-cluster.yaml というファイルに書いて kubectl create します。 あ!別のターミナルで watch kubectl get all -n my-rook-ceph とかやっておくと、Ceph系のPodがメキメキできていくので面白いですよ☆ [utubo@tutsunom operatorhub]$ kubectl apply -f create-cluster.yaml cephcluster.ceph.rook.io/rook-ceph created ...(5,6分後) [utubo@tutsunom operatorhub]$ kubectl get pod -n my-rook-ceph NAME READY STATUS RESTARTS AGE pod/rook-ceph-agent-klc8s 1/1 Running 0 35m pod/rook-ceph-agent-rfp7f 1/1 Running 0 35m pod/rook-ceph-agent-sxqtv 1/1 Running 0 35m pod/rook-ceph-mgr-a-cf5968ddb-75tgm 1/1 Running 0 9m8s pod/rook-ceph-mon-a-6686b6bd65-pl4h6 1/1 Running 0 9m43s pod/rook-ceph-mon-b-6c8f9cff55-svdm8 1/1 Running 0 9m35s pod/rook-ceph-mon-c-67f4bb647d-dm7c7 1/1 Running 0 9m23s pod/rook-ceph-operator-54cbc5bf7c-5vmjp 1/1 Running 0 35m pod/rook-ceph-osd-0-5666c486c4-k5tq7 1/1 Running 0 6m45s pod/rook-ceph-osd-1-5b6477d48-lxhvq 1/1 Running 0 6m45s pod/rook-ceph-osd-2-b5f68db68-5wndl 1/1 Running 0 6m48s pod/rook-ceph-osd-3-66d7cd8fc8-khglj 1/1 Running 0 6m45s pod/rook-ceph-osd-4-7df5d7d4b6-krcqj 1/1 Running 0 6m48s pod/rook-ceph-osd-5-d554bf64f-swrll 1/1 Running 0 6m45s pod/rook-ceph-osd-6-58654d7bf7-5mnz4 1/1 Running 0 6m45s pod/rook-ceph-osd-7-75cfdc75f9-98q8s 1/1 Running 0 6m48s pod/rook-ceph-osd-8-7676675694-25cj7 1/1 Running 0 6m45s pod/rook-ceph-osd-prepare-ip-172-20-37-237.ec2.internal-lkddk 0/2 Completed 0 8m44s pod/rook-ceph-osd-prepare-ip-172-20-77-127.ec2.internal-np5zc 0/2 Completed 1 8m44s pod/rook-ceph-osd-prepare-ip-172-20-99-100.ec2.internal-cxt9f 0/2 Completed 0 8m44s pod/rook-discover-6mw4s 1/1 Running 0 35m pod/rook-discover-7tpbj 1/1 Running 0 35m pod/rook-discover-z46hf 1/1 Running 0 35m こんな感じでmon,mgr,osdと名付けられたPodがバリバリできました。 ホントにCephとして動いているのぉ??ってなわけでoperator Podで ceph status を打ってみると分かります。 [utubo@tutsunom operatorhub]$ kubectl exec -n my-rook-ceph -it rook-ceph-operator-54cbc5bf7c-5vmjp -- ceph status cluster: id: 65c058be-a8c0-42cd-9aa8-d42294a430e9 health: HEALTH_OK services: mon: 3 daemons, quorum a,b,c (age 17m) mgr: a(active, since 16m) osd: 9 osds: 9 up (since 14m), 9 in (since 14m) data: pools: 0 pools, 0 pgs objects: 0 objects, 0 B usage: 9.0 GiB used, 72 GiB / 81 GiB avail pgs: [utubo@tutsunom operatorhub]$ kubectl exec -n my-rook-ceph -it rook-ceph-operator-54cbc5bf7c-5vmjp -- ceph osd tree ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF -1 0.07910 root default -5 0.02637 host ip-172-20-37-237-ec2-internal 0 ssd 0.00879 osd.0 up 1.00000 1.00000 5 ssd 0.00879 osd.5 up 1.00000 1.00000 8 ssd 0.00879 osd.8 up 1.00000 1.00000 -3 0.02637 host ip-172-20-77-127-ec2-internal 2 ssd 0.00879 osd.2 up 1.00000 1.00000 4 ssd 0.00879 osd.4 up 1.00000 1.00000 7 ssd 0.00879 osd.7 up 1.00000 1.00000 -7 0.02637 host ip-172-20-99-100-ec2-internal 1 ssd 0.00879 osd.1 up 1.00000 1.00000 3 ssd 0.00879 osd.3 up 1.00000 1.00000 6 ssd 0.00879 osd.6 up 1.00000 1.00000 ceph osd tree の出力から、3台のworkerにそれぞれぶら下げた3つのEBSがosdとして全て up で見えています。動いていますね。 2. Poolを作る PoolというのはCeph用語で、論理的に分割されたストレージ領域のことです。Poolごとに冗長性を変えたり利用できるユーザーを限定したりできるので、用途・目的ごとにPoolを作ることが一般的でしょう。 さっきと同様にOperatorHubのページからブロックデバイス用のPoolを作るyamlファイルの例を見ることができます。例の中身はこんな感じです。 apiVersion: ceph.rook.io/v1 kind: CephBlockPool metadata: name: replicapool namespace: my-rook-ceph spec: failureDomain: host replicated: size: 3 annotations: null Poolの名前はreplicapoolで、書き込みデータは全て3台のworkerをまたいで三重でコピーされて保存されます。 これをそのまま create-blockpool.yaml に書いて、 kubectl create します。さっきと同じように ceph df を実行してみましょう。 [utubo@tutsunom operatorhub]$ kubectl create -f create-blockpool.yaml cephblockpool.ceph.rook.io/replicapool created [utubo@tutsunom operatorhub]$ kubectl exec -n my-rook-ceph -it rook-ceph-operator-54cbc5bf7c-5vmjp -- ceph df RAW STORAGE: CLASS SIZE AVAIL USED RAW USED %RAW USED ssd 81 GiB 72 GiB 8.0 MiB 9.0 GiB 11.12 TOTAL 81 GiB 72 GiB 8.0 MiB 9.0 GiB 11.12 POOLS: POOL ID STORED OBJECTS USED %USED MAX AVAIL replicapool 1 0 B 0 0 B 0 23 GiB replicapoolというPoolがありますね。 3. StorageClassを作って、PVCでクレームする ここまできたら後はアプリから使うだけです。default namespaceでアプリを起動してPVとして動くのか見てみましょう。 まずStorageClassを作ります。 [utubo@tutsunom operatorhub]$ more sc.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: rbd provisioner: ceph.rook.io/block parameters: blockPool: replicapool clusterNamespace: my-rook-ceph fstype: xfs [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl create -f sc.yaml storageclass.storage.k8s.io/rbd created [utubo@tutsunom operatorhub]$ kubectl get sc NAME PROVISIONER AGE default kubernetes.io/aws-ebs 28d gp2 (default) kubernetes.io/aws-ebs 28d rbd ceph.rook.io/block 4s fstype: xfs で指定してますが、worker nodeに xfsprogs がインストールされてないと後で失敗する(PVをattachできない)のでご注意を。妙ですねぇ、こんな当たり前の話をここで書くなんて。こんな事に引っかかるとは、ずいぶん迂闊な人のようですねぇ。 次はアプリのPodとPVCです。 別に何でもいいんですけどね、今日は初っ端に「HELLO, PV!!!!」と快哉を叫んだ後、PVに毎秒 date をひたすら出力し続けるという、近年稀に見るuselessでゴキゲンなアプリ起動します。こんな感じ。 [utubo@tutsunom operatorhub]$ more date.yaml apiVersion: apps/v1 kind: Deployment metadata: name: date spec: replicas: 1 selector: matchLabels: app: date strategy: type: Recreate template: metadata: labels: app: date spec: containers: - name: date image: busybox command: ["/bin/sh", "-c", "echo 'HELLO, PV!!!!' >> /mnt/date; while true; do date >> /mnt/date; sleep 1; done"] volumeMounts: - name: date-pv mountPath: "/mnt" volumes: - name: date-pv persistentVolumeClaim: claimName: rbd-pvc --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: rbd-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: rbd はいそれではやってみます。 [utubo@tutsunom operatorhub]$ kubectl create -f date.yaml deployment.apps/date created persistentvolumeclaim/rbd-pvc created [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl get pod,pv,pvc NAME READY STATUS RESTARTS AGE pod/date-6cb7ccf7dc-v89qz 0/1 ContainerCreating 0 3m35s NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE persistentvolume/pvc-896cd11f-a78e-11e9-9777-0a4a204d3aa2 1Gi RWO Delete Bound my-rook-ceph/rbd-pvc rbd 33s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/rbd-pvc Bound pvc-896cd11f-a78e-11e9-9777-0a4a204d3aa2 1Gi RWO rbd 34s あれ?止まってる?イベントを見る。 [utubo@tutsunom operatorhub]$ kubectl describe pod/date-6cb7ccf7dc-v89qz Name: date-6cb7ccf7dc-v89qz Namespace: default ...(中略) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 82s default-scheduler persistentvolumeclaim "rbd-pvc" not found Warning FailedScheduling 81s default-scheduler pod has unbound immediate PersistentVolumeClaims (repeated 3 times) Normal Scheduled 78s default-scheduler Successfully assigned my-rook-ceph/date-pod to ip-172-20-37-237.ec2.internal Warning FailedMount 14s (x8 over 78s) kubelet, ip-172-20-37-237.ec2.internal MountVolume.SetUp failed for volume "pvc-896cd11f-a78e-11e9-9777-0a4a204d3aa2" : mount command failed, status: Failure, reason: Rook: Mount volume failed: failed to create volume CRD pvc-896cd11f-a78e-11e9-9777-0a4a204d3aa2. the server could not find the requested resource (post volumes.rook.io) ファー。どうも"volumes.rook.io"というCustom Resourceがないぞと言っているようです。 [utubo@tutsunom operatorhub]$ kubectl get crd NAME CREATED AT catalogsources.operators.coreos.com 2019-07-16T05:33:49Z cephblockpools.ceph.rook.io 2019-07-16T05:34:56Z cephclusters.ceph.rook.io 2019-07-16T05:34:56Z cephobjectstores.ceph.rook.io 2019-07-16T05:34:56Z cephobjectstoreusers.ceph.rook.io 2019-07-16T05:34:56Z clusterserviceversions.operators.coreos.com 2019-07-16T05:33:48Z installplans.operators.coreos.com 2019-07-16T05:33:48Z operatorgroups.operators.coreos.com 2019-07-16T05:33:50Z subscriptions.operators.coreos.com 2019-07-16T05:33:49Z たしかに。そしたら足しましょう。 [utubo@tutsunom operatorhub]$ cat volumes-rook-crd.yaml apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: volumes.rook.io spec: group: rook.io names: kind: Volume listKind: VolumeList plural: volumes singular: volume shortNames: - rv scope: Namespaced version: v1alpha2 [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl create -f volumes-rook-crd.yaml customresourcedefinition.apiextensions.k8s.io/volumes.rook.io created [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl get crd NAME CREATED AT catalogsources.operators.coreos.com 2019-07-16T05:33:49Z cephblockpools.ceph.rook.io 2019-07-16T05:34:56Z cephclusters.ceph.rook.io 2019-07-16T05:34:56Z cephobjectstores.ceph.rook.io 2019-07-16T05:34:56Z cephobjectstoreusers.ceph.rook.io 2019-07-16T05:34:56Z clusterserviceversions.operators.coreos.com 2019-07-16T05:33:48Z installplans.operators.coreos.com 2019-07-16T05:33:48Z operatorgroups.operators.coreos.com 2019-07-16T05:33:50Z subscriptions.operators.coreos.com 2019-07-16T05:33:49Z volumes.rook.io 2019-07-16T05:47:46Z これでどうよ。 [utubo@tutsunom operatorhub]$ kubectl delete -f date.yaml deployment.apps "date" deleted persistentvolumeclaim "rbd-pvc" deleted [utubo@tutsunom operatorhub]$ kubectl create -f date.yaml deployment.apps/date created persistentvolumeclaim/rbd-pvc created [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl get pod,pv,pvc NAME READY STATUS RESTARTS AGE pod/date-6cb7ccf7dc-bwlq8 1/1 Running 0 30s NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE persistentvolume/pvc-d105d4b5-a7dc-11e9-a140-120aa05f7e3e 1Gi RWO Delete Bound default/rbd-pvc rbd 31s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/rbd-pvc Bound pvc-d105d4b5-a7dc-11e9-a140-120aa05f7e3e 1Gi RWO rbd 31s うひー、よかったよかった。 どうも現状OperatorHubからRook-Cephを入れると"volumes.rook.io"が足りなくてエラーが起こるようですね。上に書いたようなマニフェストで"volumes.rook.io"を後乗せすればいけます。 ※ちなみにgithubの quickstart に沿うと、common.yamlの中でvolumes.rook.ioを定義するのでエラーは起きません。 それじゃPVの中身をのぞいてみましょう。 [utubo@tutsunom operatorhub]$ kubectl exec -it date-cf6b86596-bwlq8 -- more /mnt/date HELLO, PV!!!! Tue Jul 16 05:57:42 UTC 2019 Tue Jul 16 05:57:43 UTC 2019 Tue Jul 16 05:57:44 UTC 2019 Tue Jul 16 05:57:45 UTC 2019 Tue Jul 16 05:57:46 UTC 2019 ...(省略) はい、ゴキゲンですね。 じゃあ、このPod消してみましょう。ReplicaSetなんでPodを削除しても自動で復活してきてPVが付け替えられ、消える前の続きから雄叫びが聴けるはずです。 [utubo@tutsunom operatorhub]$ kubectl delete pod/date-6cb7ccf7dc-bwlq8 pod "date-6cb7ccf7dc-bwlq8" deleted [utubo@tutsunom ceph]$ kubectl get pod NAME READY STATUS RESTARTS AGE date-6cb7ccf7dc-bwlq8 1/1 Terminating 0 78s date-6cb7ccf7dc-drcjp 0/1 ContainerCreating 0 9s [utubo@tutsunom ceph]$ kubectl get pod NAME READY STATUS RESTARTS AGE date-6cb7ccf7dc-drcjp 1/1 Running 0 69s [utubo@tutsunom operatorhub]$ kubectl exec -it date-6cb7ccf7dc-drcjp -- more /mnt/date HELLO, PV!!!! Tue Jul 16 05:57:42 UTC 2019 Tue Jul 16 05:57:43 UTC 2019 Tue Jul 16 05:57:44 UTC 2019 Tue Jul 16 05:57:45 UTC 2019 ...(中略) Tue Jul 16 05:59:00 UTC 2019 Tue Jul 16 05:59:01 UTC 2019 Tue Jul 16 05:59:02 UTC 2019 Tue Jul 16 05:59:03 UTC 2019 HELLO, PV!!!! Tue Jul 16 05:59:08 UTC 2019 Tue Jul 16 05:59:09 UTC 2019 Tue Jul 16 05:59:10 UTC 2019 Tue Jul 16 05:59:11 UTC 2019 Tue Jul 16 05:59:12 UTC 2019 ...(省略) 5カウントでファイティングポーズとってます。タフですね。 最後にCeph側でブロックデバイスがちゃんと存在するのか見てみましょう。 [utubo@tutsunom operatorhub]$ kubectl -n my-rook-ceph exec -it rook-ceph-operator-54cbc5bf7c-5vmjp -- rbd ls -p replicapool pvc-d105d4b5-a7dc-11e9-a140-120aa05f7e3e [utubo@tutsunom operatorhub]$ [utubo@tutsunom operatorhub]$ kubectl -n my-rook-ceph exec -it rook-ceph-operator-54cbc5bf7c-5vmjp -- rbd info pvc-d105d4b5-a7dc-11e9-a140-120aa05f7e3e -p replicapool rbd image 'pvc-d105d4b5-a7dc-11e9-a140-120aa05f7e3e': size 1 GiB in 256 objects order 22 (4 MiB objects) snapshot_count: 0 id: 8524adcffa5a block_name_prefix: rbd_data.8524adcffa5a format: 2 features: layering op_features: flags: create_timestamp: Tue Jul 16 05:57:38 2019 access_timestamp: Tue Jul 16 05:57:38 2019 modify_timestamp: Tue Jul 16 05:57:38 2019 はい、PVの名前と同じ1GiBのブロックデバイスがありますね。イヤァオ! まとめ そういうわけで今回はRook-Ceph OperatorをOperatorHubからインストールしてCephクラスターを作り、CephのブロックデバイスをRWOのPVとして使えるところをお見せしました。 次回は作ったCephクラスターをちょいちょい壊しながら、どんな動きをするのかを見ていきましょう。作ったら少しずつ壊す。それがストレージ屋のジャスティス。 あと、今回はブロックデバイスだったんでRWOでやりましたが、RWXもやりたいと思います。 というわけで、今回はここまで。
この記事は Red Hat Developer の RPM packaging: A simplified guide to creating your first RPM を、許可をうけて翻訳したものです。 RPMパッケージングのコンセプトは、初めての人にとっては学ぶべきことが非常に多い印象をうけ、圧倒的されてしまうかもしれません。 この記事では、最小限の知識と経験でRPMを構築することが可能であることを実際に見ていきます。 この記事は出発点として提供しているもので、RPMパッケージ化の完全なガイドではありません。 ASCIIベースのテトリスゲーム このデモでは、Cで書かれた簡単なASCIIベースのテトリスゲームを選びました、そして比較的簡単なRPMビルドとするためにわずかに調整をします。この記事では ダウンロード可能 な例としてVitetrisと呼ばれるゲームを使います。 root以外のユーザーがRPMを構築できるようにするために、RPMパッケージの作成時にエラーが発生しないように、ファイルのオーナーをrootに変更するMakefile内の指定を削除します。 この修正は以下の箇所です。 $ cat Makefile |grep 'INSTALL ' INSTALL = install #INSTALL = install -oroot -groot # rpmをビルドしている非rootユーザーでは不可能なため、RPMビルドが失敗します。 この変更を加えたら、tarボールを同じ名前( vitetris-0.57.tar.gz )で作成します。 環境を整える Red Hat Enterprise Linux (RHEL)7システム上に開発環境をインストールし、RPMをビルドするためには以下のリポジトリが必要です: rhel-7-server-rpms 、 rhel-7-server-extras-rpms 、 rhel-7-server-optional-rpms 以下のパッケージをインストールする必要があります: # yum install -y rpm* gcc gpg* rng-tools インストール時に覚えておくべきことを減らすため rpm* と gpg* とワイルドカードを使用しています。 ソフトウェアを手動でコンパイルする RPMパッケージ作成を成功させるために大切なことは、対象のソフトウェアを理解することです。 まず、ソフトウェアを手動で(できれば、非rootユーザで)コンパイルします。 この例では、 rpmbuilder ユーザーを使用しています。 tarボール( vitetris-0.57.tar.gz )を展開してからREADMEファイルを調べます。 この場合、READMEファイルには、ソースコードからゲームをコンパイルすることに関する情報は含まれていません。 一般的には、Cで書かれたオープンソースソフトウェアは、 configure 、 make 、 make install の3つのステップでコンパイルできます。 しかし、 make 単独でソフトウェアがビルドされるかどうかを試してみる価値はあります。 非rootユーザーとして、 makeの実行だけで十分かどうかテストします。 [rpmbuilder@rpm vitetris-0.57]$ make generating src/src-conf.mk ./src-conf.sh 'cc' '' '' ... Done. Now run ./tetris (or make install) ゲームをテストするには、単に ./tetris を実行して、ゲームがロードされてプレイできるかどうかを確認します。 ソースコードからビルドした後にTetrisを実行する GPGキーを作成する RPMパッケージに署名することでパッケージにさらなる信頼の層を追加します。 rpmbuilder ユーザーとしてGPGキーを作成するには、 rngd を実行する(生成プロセスを高速化する)ためにrootアクセス権を持つセッションと、 rpmbuilder ユーザーとしてX11転送を行うセッションが必要になります。 以下をrootとして実行します。 # rngd -r /dev/urandom 次のエラーが発生した場合は Failed to init entropy source 2: Intel RDRAND Instruction RNG 以下を試してください: # rngd -r /dev/urandom -o /dev/random -f X11転送を使用して rpmbuilder ユーザーとしてホストにログインします(鍵の生成に必要です)。 $ gpg --gen-key ... Please select what kind of key you want: (1) RSA and RSA (default) (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) Your selection? 1 RSA keys may be between 1024 and 4096 bits long. What keysize do you want? (2048) Requested keysize is 2048 bits Please specify how long the key should be valid. 0 = key does not expire <n> = key expires in n days <n>w = key expires in n weeks <n>m = key expires in n months <n>y = key expires in n years Key is valid for? (0) Key does not expire at all Is this correct? (y/N) y GnuPG needs to construct a user ID to identify your key. Real name: RPM Build User Email address: rpmbuilder@rpm Comment: RPM Builder GPG Signing Key You selected this USER-ID: "RPM Build User (RPM Builder GPG Signing Key) <rpmbuilder@rpm>" Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O You need a Passphrase to protect your secret key. X11転送を有効にしている場合に表示されるプロンプト このプロンプトは、X11転送がオンになっている場合にのみ表示されます。 ... gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u pub 2048R/EEF6D9AD 2019-03-02 Key fingerprint = 6ED1 2456 B7ED EEC6 D0DF B870 444A 40A7 EEF6 D9AD uid RPM Build User (RPM Builder GPG Signing Key) <rpmbuilder@rpm> sub 2048R/D498F883 2019-03-02 カスタムソフトウェア用のRed Hat Satelliteまたはyumリポジトリ設定で使用するためにキーをエクスポートする必要がある場合は、次のコマンドを使用します。 $ gpg --armor --export -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2.0.22 (GNU/Linux) Vitetris用のRPMパッケージを作成する RPMをビルドするには、まずビルドツリーとspecファイルを作成し、ソースコードを配置します。 $ rpmdev-setuptree $ rpmdev-newspec rpmbuild/SPECS/vitetris.spec rpmbuild/SPECS/vitetris.spec created; type minimal, rpm version >= 4.11. $ cp vitetris-0.57.tar.gz rpmbuild/SOURCES/ specファイルに初期詳細を追加する まず以下の詳細を記述します。 Name: vitetris Version: 0.57 Release: 1%{?dist} Summary: ASCII based tetris game License: BSD URL: https://www.victornils.net/tetris/ Source0: vitetris-0.57.tar.gz BuildRequires: gcc 不要なセクションを削除 このソフトウェアには依存関係がないため、 Requires セクションを削除します このソフトウェアはconfigureなしで構築できるので、 %configureセクションを削除します。(makeを実行する前にconfigureが必要な場合は保持します。) descriptionを追加 好きなものを追加できますが、以下のような説明を書きます: %description vitetris is a multiplayer ASCII-based Tetris game 初期ビルドをテストする 表示された情報でビルドをテストして、表示されるエラーを確認します。 $ rpmbuild -ba rpmbuild/SPECS/vitetris.spec ... RPM build errors: Installed (but unpackaged) file(s) found: /usr/local/bin/tetris /usr/local/share/applications/vitetris.desktop /usr/local/share/doc/vitetris/README /usr/local/share/doc/vitetris/licence.txt /usr/local/share/pixmaps/vitetris.xpm 前のステップのファイルのリストをspecファイルに追加する 次のように、前の手順で表示されたファイルのリストを%filesセクションに追加します。 %files /usr/local/bin/tetris /usr/local/share/applications/vitetris.desktop %doc /usr/local/share/doc/vitetris/README /usr/local/share/doc/vitetris/licence.txt /usr/local/share/pixmaps/vitetris.xpm 文書であることを示す %doc がREADMEの前に置かれています。 RPMはこれなしでもビルドできます。 ビルドを再実行 $ rpmbuild -ba rpmbuild/SPECS/vitetris.spec Wrote: /home/rpmbuilder/rpmbuild/SRPMS/vitetris-0.57-1.el7.src.rpm Wrote: /home/rpmbuilder/rpmbuild/RPMS/x86_64/vitetris-0.57-1.el7.x86_64.rpm Wrote: /home/rpmbuilder/rpmbuild/RPMS/x86_64/vitetris-debuginfo-0.57-1.el7.x86_64.rpm Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.AqR4Aq + umask 022 + cd /home/rpmbuilder/rpmbuild/BUILD + cd vitetris-0.57 + /usr/bin/rm -rf /home/rpmbuilder/rpmbuild/BUILDROOT/vitetris-0.57-1.el7.x86_64 + exit 0 RPMパッケージに署名する RPMへの署名は以下のようにするだけです: $ rpmsign --addsign /home/rpmbuilder/rpmbuild/RPMS/x86_64/vitetris-0.57-1.el7.x86_64.rpm Enter pass phrase: Pass phrase is good. /home/rpmbuilder/rpmbuild/RPMS/x86_64/vitetris-0.57-1.el7.x86_64.rpm: このエラーが発生した場合: You must set "%_gpg_name" in your macro file 公開鍵の値を%_gpg_name で指定します。 $ gpg --list-keys /home/rpmbuilder/.gnupg/pubring.gpg ----------------------------------- pub 2048R/EEF6D9AD 2019-03-02 uid RPM Build User (RPM Builder GPG Signing Key) <rpmbuilder@rpm> sub 2048R/D498F883 2019-03-02 $ echo "%_gpg_name EEF6D9AD" >> .rpmmacros 再試行します: $ rpmsign --addsign /home/rpmbuilder/rpmbuild/RPMS/x86_64/vitetris-0.57-1.el7.x86_64.rpm Enter pass phrase: Pass phrase is good. /home/rpmbuilder/rpmbuild/RPMS/x86_64/vitetris-0.57-1.el7.x86_64.rpm: rootユーザーとしてRPMのインストール/アンインストールをテストします。 # rpm -i /home/rpmbuilder/rpmbuild/RPMS/x86_64/vitetris-0.57-1.el7.x86_64.rpm warning: /home/rpmbuilder/rpmbuild/RPMS/x86_64/vitetris-0.57-1.el7.x86_64.rpm: Header V4 RSA/SHA1 Signature, key ID eef6d9ad: NOKEY # /usr/local/bin/tetris RPMをアンインストールします。 # rpm -qa |grep tetris vitetris-0.57-1.el7.x86_64 =# rpm -e vitetris-0.57-1.el7.x86_64 # /usr/local/bin/tetris -bash: /usr/local/bin/tetris: No such file or directory おわりに パッケージ化しようとしているソフトウェアによっては、カスタムRPMパッケージ作成は困難な場合があります。 この記事では、多くの場合にデフォルト設定で十分RPMを構築できることをデモンストレーションするため、できる限り作業を少なくしています。 もっと詳しく知りたい方は、 Red HatのRPM Packaging guide を参照してください。
こんにちは。Red Hatの森 ( @mosuke5 ) です。 今日はRuby on RailsのOpenShift上での開発について少しみていきたいと思います。 自分がまさにそうなのですが、生のKubernetesを先に学んだあとにOpenShiftにはいると、結構違う部分もあってはじめ理解が難しい部分もあります。 皆さん馴染みあるRubyで(?) 簡単な開発環境を作ってみて確認していきます。 4月に入社した身として、触っていく中でよくわからなかった部分、気になったを中心に書いているので、 OpenShift初心者の方に少しでも参考になればと思います。 minishiftを起動しよう OpenShiftの環境は、一番用意しやすいであろうminishift(v1.33.0+ba29431)を使いました。 minikubeのopenshift版と思っていただければ大丈夫です。ローカル端末の仮想マシンの上にOpenShiftが動く環境を立ち上げられるものです。 本記事ではminishiftのインストールについては割愛しますが、下記を参考にインストールしてみてください。 Overview - Getting Started | Minishift | OKD Latest ocクライアントのバージョンは下記のとおりです。 $ oc version oc v3.11.107 kubernetes v1.11.0+d4cacc0 features: Basic-Auth Server https://192.168.64.7:8443 kubernetes v1.11.0+d4cacc RailsをOpenShift上で動作させる ドキュメントを探していると、OpenShift上で動かせるRailsのアプリの サンプル を見つけました。 しかし、この手順の通り試しても抽象度が高すぎて 「動いたけどよくわからん!」 みたいな状態になったので、 ポイントを解説しながらみていきたいと思います。それがこの記事を書こうと思ったモチベ。 github.com oc new-appというコマンド OpenShiftには oc new-app というただのPodやDeploymentだけでなくアプリケーションを作るためのコマンドが用意されています。 あんまり実運用では利用されることは少ないかもしれませんが、今回のような開発環境のセットアップなどでは便利です。 しかし、このコマンドは良くも悪くもいろんな動きをするので理解が難しい一面もあります。 1つとして、OpenShiftなどで用意しているテンプレートからアプリケーションを実行することができます。 どんなテンプレートがあるかは下記のように確認できます。Rails+PostgreSQLのセットになったもの、Jenkinsなどいくつかあります。 $ oc get template -n openshift NAME DESCRIPTION PARAMETERS OBJECTS cakephp-mysql-persistent An example CakePHP application with a MySQL database. For more information ab... 20 (4 blank) 9 dancer-mysql-persistent An example Dancer application with a MySQL database. For more information abo... 17 (5 blank) 9 django-psql-persistent An example Django application with a PostgreSQL database. For more informatio... 20 (5 blank) 9 ... テンプレートの実体は、kubernetesのマニフェストファイルでリソースの集合体です。OpenShiftにはTemplateというリソースがあります。 マニフェストファイルの中のパラメータなどを変数化することが可能で、データベースなどのシークレット情報をパラメータ化して配布することなど可能です。 access.redhat.com 実際にテンプレートの中身は下記のようにして見ることができます。 試しにpostgresqlのテンプレートをみてみますが、SecretやDeploymentConfig、Serviceのリソースが記載されているのがわかるかと思います。 また、テンプレートを使って起動する際にどんなパラメータを指定できるかなど確認できるので便利です。 $ oc get template -n openshift postgresql-persistent -o yaml | less Railsアプリの起動 今回は上で紹介したRailsのサンプルのアプリケーションのレポジトリがあったので、こちらでRailsアプリの方の起動を行っていきます。 レポジトリをfork & cloneして、テンプレートで起動してみます。一旦この場は思考停止で実行してみます。 $ git clone git@github.com:mosuke5/rails-ex.git $ oc new-project rails-dev $ oc new-app openshift/templates/rails-postgresql.json -p SOURCE_REPOSITORY_URL=https://github.com/mosuke5/rails-ex --> Deploying template "rails-dev/rails-postgresql-example" for "openshift/templates/rails-postgresql.json" to project rails-dev Rails + PostgreSQL (Ephemeral) --------- (略) --> Creating resources ... secret "rails-postgresql-example" created service "rails-postgresql-example" created route.route.openshift.io "rails-postgresql-example" created imagestream.image.openshift.io "rails-postgresql-example" created buildconfig.build.openshift.io "rails-postgresql-example" created deploymentconfig.apps.openshift.io "rails-postgresql-example" created service "postgresql" created deploymentconfig.apps.openshift.io "postgresql" created --> Success Access your application via route 'rails-postgresql-example-rails-dev.192.168.64.7.nip.io' Build scheduled, use 'oc logs -f bc/rails-postgresql-example' to track its progress. Run 'oc status' to view your app. 実行後にpodの状態をみてみると、rails-postgresql-example-1-buildというbuildのpodが動いているのがわかります。 こちらは後ほど説明しますが、いまRailsのアプリケーションのイメージを作っている最中です。 $ oc get pod NAME READY STATUS RESTARTS AGE postgresql-1-mwm94 1/1 Running 0 55s rails-postgresql-example-1-build 1/1 Running 0 58s // 終わるとアプリケーション本体のPodが起動する $ oc get pod NAME READY STATUS RESTARTS AGE postgresql-1-mwm94 1/1 Running 0 2m rails-postgresql-example-1-build 0/1 Completed 0 3m rails-postgresql-example-1-md8bx 1/1 Running 0 17s 起動が終わったあとに、routeを確認してアクセスしてみると。アプリケーションが動作しているのがわかります。 route は ingress に相当するような概念で、外部からPodへのアクセスの経路を確保します。 rails-postgresql-example-rails-dev.192.168.64.7.nip.io は仮のドメイン名なので、/etc/hostsに 192.168.64.7 rails-postgresql-example-rails-dev.192.168.64.7.nip.io と書いてあげればアクセスできますね。 もちろん、 oc port-forward service/rails-postgresql-example 8080:8080 のようにport-forwardを使ってアクセスしても問題ありません。 $ oc get route NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD rails-postgresql-example rails-postgresql-example-rails-dev.192.168.64.7.nip.io rails-postgresql-example <all> None /articles などアクセスすれば、記事の登録もできDB連携もできていることが確認できます。 さて、まあここまでやれば「なんか動いた!」という感じなわけですが、何が起きたのか少し追っていきたいと思います。 まずは作成されたリソースをみてみます。 NAME READY STATUS RESTARTS AGE pod/postgresql-1-mwm94 1/1 Running 0 9m pod/rails-postgresql-example-1-build 0/1 Completed 0 9m pod/rails-postgresql-example-1-md8bx 1/1 Running 0 7m NAME DESIRED CURRENT READY AGE replicationcontroller/postgresql-1 1 1 1 9m replicationcontroller/rails-postgresql-example-1 1 1 1 7m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/postgresql ClusterIP 172.30.66.38 <none> 5432/TCP 9m service/rails-postgresql-example ClusterIP 172.30.72.44 <none> 8080/TCP 9m NAME REVISION DESIRED CURRENT TRIGGERED BY deploymentconfig.apps.openshift.io/postgresql 1 1 1 config,image(postgresql:10) deploymentconfig.apps.openshift.io/rails-postgresql-example 1 1 1 config,image(rails-postgresql-example:latest) NAME TYPE FROM LATEST buildconfig.build.openshift.io/rails-postgresql-example Source Git 1 NAME TYPE FROM STATUS STARTED DURATION build.build.openshift.io/rails-postgresql-example-1 Source Git@67d882b Complete 9 minutes ago 2m39s NAME DOCKER REPO TAGS UPDATED imagestream.image.openshift.io/rails-postgresql-example 172.30.1.1:5000/rails-dev/rails-postgresql-example latest 7 minutes ago NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD route.route.openshift.io/rails-postgresql-example rails-postgresql-example-rails-dev.192.168.64.7.nip.io rails-postgresql-example <all> None なにやらいろいろと作成されていますが、取り上げてみていくのが、deploymentconfigとbuildconfigとimagestreamです。 DeploymentConfig まずは DeploymentConfig をみていきます。DeploymentConfig(dcと略せる)はOpenShift特有のリソースで、 KubernetesのDeploymentに該当するような機能で、trigger機能などがいくつかの機能が拡充されています。 気になるポイントの1つ目としては、 strategy のなかの pre です。 Railsアプリケーションではデータベースを使う場合に、 bundle exec rails db:migrate でテーブルのスキーマを作っていきますが、今回それを実行することなくアプリケーションの動作までいけていました。どこで実行されていたのか?というとここです。 migrate-database.sh が用意されていて、Podが新しくできるときにシェルを実行していました。 次に気になるポイントは、 triggers の部分です。この設定では、イメージの変更をトリガーに更新できる設定が入っています。 後述するBuildConfigが新しいイメージを作成すると、それをトリガーにDeploymentConfigがPodを入れ替えてくれるわけです。 $ oc get dc NAME REVISION DESIRED CURRENT TRIGGERED BY postgresql 1 1 1 config,image(postgresql:10) rails-app 1 1 1 config,image(rails-app:latest) $ oc get dc rrails-postgresql-example -o yaml | less ... strategy: activeDeadlineSeconds: 21600 recreateParams: pre: execNewPod: command: - ./migrate-database.sh containerName: rails-postgresql-example failurePolicy: Abort timeoutSeconds: 600 resources: {} type: Recreate (略) triggers: - type: ConfigChange - imageChangeParams: automatic: true containerNames: - rails-app from: kind: ImageStreamTag name: rails-app:latest namespace: rails-dev lastTriggeredImage: 172.30.1.1:5000/rails-dev/rails-app@sha256:487b72449aa58cd0ef56252a72d26e2aac98e2c63699042becd5eb2ae3d88053 type: ImageChange BuildConfig 続いて BuildConfig (bcと略せる)をみてみます。 今回稼働させたRailsアプリケーションのコンテナイメージを作成するためのビルドの設定がこちらです。 生のKubernetesではKubernetesとは別のプロセスとしてコンテナイメージを作成することが一般的かと思います。 OpenShiftでは、S2I (Source to Image)という機能があり、開発者がコンテナイメージを作らなくても、書いたコードをOpenShiftで動かすためのワークロードが用意されています。 そのビルドの設定がBuildConfigです。 oc new-app の時に指定した SOURCE_REPOSITORY_URL の中身をみてアプリケーションの種別(RubyなのかJavaなのか)を判別して、イメージをビルドしてくれました。 似ているものとしては、buildpack, herokuなどが近いでしょうか。 $ oc get bc NAME TYPE FROM LATEST rails-app Source Git@blog-sample 1 $ oc get bc rails-app -o yaml | less source: git: ref: blog-sample uri: https://github.com/mosuke5/rails-ex.git type: Git strategy: sourceStrategy: from: kind: ImageStreamTag name: ruby:2.5 namespace: openshift type: Source S2Iについてもっと知りたい方向けリンク GitHub - openshift/source-to-image: A tool for building/building artifacts from source and injecting into docker images GitHub - sclorg/s2i-ruby-container: Ruby container images based on Red Hat Software Collections and intended for OpenShift and general usage, that provide a platform for building and running Ruby applications. Users can choose between Red Hat Enterprise Linux, Fedora, and CentOS based images. ImageStream 続いて ImageStream (isと略せる)ですが、こちらも生のKubernetesにはないリソースです。ImageStreamは概ねDocker Registryと考えていただいて大丈夫ですが、 コンテナイメージを抽象化した概念になります。 特徴としては、コンテナイメージをプッシュした履歴が残る点です。今回、2回ビルドを行い、どちらも latest タグでプッシュしましたが、イメージのハッシュ値で別々に管理されているのがわかります。そのため、今回のようにイメージのタグを特に考えずlatestのままでしたが、適切にPodの入れ替えが行われたということです。 $ oc get is rails-postgresql-example -o yaml | less ... status: dockerImageRepository: 172.30.1.1:5000/rails-dev/rails-postgresql-example tags: - items: - created: 2019-07-09T08:40:53Z dockerImageReference: 172.30.1.1:5000/rails-dev/rails-postgresql-example@sha256:1d42310b4182199b471839108b278c646b99e3e731af783a1d0f5e4fca22e1d7 generation: 1 image: sha256:1d42310b4182199b471839108b278c646b99e3e731af783a1d0f5e4fca22e1d7 - created: 2019-07-09T07:42:45Z dockerImageReference: 172.30.1.1:5000/rails-dev/rails-postgresql-example@sha256:ca0141e090f7fd82ff665b6f87a7e099b920fa6a04d8dcf5263ddfccfad9f9ec generation: 1 image: sha256:ca0141e090f7fd82ff665b6f87a7e099b920fa6a04d8dcf5263ddfccfad9f9ec tag: latest アプリケーションの変更 適当にアプリケーションを変更してgithubへpushします。 せっかくなので、データベースのスキーマも変更するような修正を入れたいと思います。 scaffoldを使ってユーザモデルとコントローラを作ります。 $ bundle exec rails generate scaffold user name:string age:integer $ bundle exec rake routes bundle exec rake routes Prefix Verb URI Pattern Controller#Action users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update PUT /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroy $ git add . $ git commit -m 'update' $ git push origin master それではこの変更をOpenShift上のRailsアプリケーションに反映させたいと思います。 oc new-app で作られたBuildConfigなどはもちろんあとから実行が可能です。 oc start-build で実行してみます。 rails-postgresql-example-2-build という2回目の実行のbuildコンテナが動いているのがわかります。 $ oc start-build rails-postgresql-example $ oc get pod -w NAME READY STATUS RESTARTS AGE postgresql-1-mwm94 1/1 Running 0 49m rails-postgresql-example-1-build 0/1 Completed 0 49m rails-postgresql-example-1-md8bx 1/1 Running 0 46m rails-postgresql-example-2-build 1/1 Running 0 <invalid> buildが終わるとImageStreamが更新され、それをトリガーにDeploymentConfigが新しいイメージでPodを入れ替えてくれました。 実際に、 http://rails-postgresql-example-rails-dev.192.168.64.7.nip.io/users にアクセスするとユーザ情報を操作できる画面がでてきました。 データベースのスキーマも無事に変更してくれています。 まとめ OpenShiftを触り始めて、チュートリアル的に行うnew-appコマンドを使ったアプリケーションの構築でしたが、 裏では様々な動きが行われていて、奥深さを感じるものでした。 また、OpenShiftを使った開発のフローや戦略がみえるものでもあるなあと感じます。 今日見てきたところはほんのごく一部の基礎的なところではありますが、これからもOpenShiftを使ったアプリケーション開発について情報発信できたらと思います。
はじめまして。Red Hatの森( @mosuke5 ) です。 4月15日からRed Hatにジョインしていて初めての投稿になります。 わたしは前職では、クラウドプロバイダーとして働いていて、中国のアリババが展開するAlibaba Cloud(パブリッククラウド)の日本リージョンの立ち上げなどを行っていました。 そんな、Alibaba Cloudも7月1日からRHEL8 (Red Hat Enterprise Linux 8)の提供をはじめました!RHEL8 on Alibaba Cloudについてまだ誰も日本語で情報発信をしていなかったので、先陣をきって発信していきます。 Alibaba Cloudってなんだよ、という方も多いと思うので、そのあたりも少しだけ説明します。 Alibaba Cloudってなんだよ あまり書くと長くなってしまうので少しだけ。。 中国のアリババ社が展開している パブリッククラウドのサービス です。 どうしても日本にいると西側の国の情報ばかり入ってきますが、近場の中国では独自のインターネット環境でテクノロジー急成長していて、ここ数年ではクラウドについてもグローバルにでてきています。 サービスのラインナップも仮想サーバをはじめとして、ロードバランサやマネージドKubernetes、機械学習など多岐にわたり、中国本土だけでいうとあのAWSを凌駕するほどの種類のサービスラインナップをもっています。 日本でこそあまり利用事例を聞かないかもですが、日本でも有名なショート動画SNSや東南アジアの有名なECサイトなど、中国や東南アジア初のグローバルサービスでも利用されていたりします。日本でも着々と展開が進んでいます。 サービスの概要と現状的なものはむかしに下記でしゃべっていてまとまっているのでよかったら参考程度にみてください。 logmi.jp RHELを動かす さて、そんなAlibaba Cloudですが、Red Hatからも認められたクラウドプロバイダーでRHELを動かすことができます。(認定クラウド&サービスプロバイダーは こちら よりご確認いただけます) AWSと同様で、Red Hatと直接に契約せずともRHELを仮想マシンとして動かすことが可能です。サポートについても、サービスプロバイダーの方で一次問い合わせを行うことができます。 先日、RHEL8についても対応し利用できるようになりました。 www.alibabacloud.com 動かす方法としては大きく2つの方法があります。 Alibaba Cloudが提供するイメージを利用する すでに購入済みのサブスクリプションを利用してイメージを持ち込む ECS(仮想サーバ)を購入する画面で、パブリックイメージとしてRHELが準備されているのでこちらを選択すればすぐに利用できます。 この際、料金画面の値段を確認するとわかるのですが(下図参照)、インスタンスの利用料金+Marketplace利用料金の2つが表示されています。このイメージを選ぶと単にインスタンスの料金だけではなくRHELを利用するための料金も含まれている状態になります。 もう一つは、すでにRHELのサブスクリプションをお持ちの方向けに、イメージを持ち込みできるサービスです。 Alibaba Cloudにも制限はあるもののイメージを外部から持ち込む機能をもっています。そちらを利用して、RHELを持ち込むこともできます。( イメージ持ち込みについての公式ドキュメント ) また、Red Hatの過去のナレッジ情報を確認すると手順があがっていました。カスタマーポータルのアカウントお持ちの方のみ閲覧できます。 access.redhat.com RHEL8 on Alibaba Cloudを選ぶ場面 Alibaba Cloudが選ばれるケースはいくつかあるのですが、一番多いのは「中国を含めたグローバルへのビジネス進出」です。 アカウントを作ってくれればわかるのですが、中国のリージョンでもサーバを簡単にたてられます。他のクラウドプロバイダーでは中国だけ別アカウントだったり提供がそもそもなかったりの状況です。日本、中国、それ以外のグローバルへ展開しているエンタープライズ企業で、統一的にRHELを使いたいことは多くあります。 Alibaba CloudでRHELを動かす際の注意点 Alibaba Cloud上でRHELを利用する際にはいくつか機能的に注意する点があります。 1. rootパスワードが購入時には設定できません ECSを購入する際にログインのための認証として、SSH鍵の指定かrootユーザへのパスワード設定が「通常なら」可能です。 しかしRHELについてはrootユーザへのパスワード設定が購入時はできません。セキュリティの観点から原則はrootにパスワードを付与しないポリシーに基づいているものと見えます。いずれにせよ、SSHのパスワードログインはおすすめできませんのでしっかり鍵認証で使いましょう。 2. Alibaba Cloud特有のエージェントがデフォルトでは入りません Alibaba Cloudではセキュリティ関連のエージェントをプリインストールすることが通常であればできます。 RHELでは、お客さんの方で任意に入れることはできますが、イメージとしてはデフォルトでは含まれていません。 下記はRHEL8とCentOS7.6の起動後にpstreeをみたものですが、 AliYunDun や aliyun-service といったプロセスがないことがわかります。 任意ではインストールできるので、運用の要件などに応じていれるかどうかを検討すると良さそうです。 # RHEL8 pstree systemd─┬─NetworkManager───2*[{NetworkManager}] ├─2*[agetty] ├─atd ├─auditd─┬─sedispatch │ └─2*[{auditd}] ├─chronyd ├─crond ├─dbus-daemon───{dbus-daemon} ├─lsmd ├─mcelog ├─polkitd───8*[{polkitd}] ├─qemu-ga ├─rhsmcertd ├─rhsmd ├─rngd───{rngd} ├─rsyslogd───2*[{rsyslogd}] ├─smartd ├─sshd───sshd───sshd───bash───pstree ├─sssd─┬─sssd_be │ └─sssd_nss ├─systemd───(sd-pam) ├─systemd-journal ├─systemd-logind ├─systemd-udevd └─tuned───3*[{tuned}] # CentOS 7.6 pstree systemd─┬─AliYunDun───18*[{AliYunDun}] ├─AliYunDunUpdate───3*[{AliYunDunUpdate}] ├─2*[agetty] ├─aliyun-service───2*[{aliyun-service}] ├─anacron ├─atd ├─auditd───{auditd} ├─chronyd ├─crond ├─dbus-daemon ├─dhclient ├─polkitd───6*[{polkitd}] ├─rsyslogd───2*[{rsyslogd}] ├─sshd───sshd───bash───pstree ├─systemd-journal ├─systemd-logind ├─systemd-udevd └─tuned───4*[{tuned}] さいごに というわけで、RHEL8 on Alibaba Cloudについてみてきました。 全世界グローバルでRHEL8が使えるのはいい話ですね。基本的にはAWSで利用する場合と変わらないのですが、いくつか特有の注意点などありました。利用する際はご注意を。 ぜひ選択肢の1つとして頭の片隅に残ってくれると嬉しいです。 次あたりはRed Hat製品についてのテクニカルな情報についてもう少し発信できればと思っています。