これから、レガシーからモダンへというテーマで、記事を書いていきたいと思います。
ここ5年(あるいは10年?)ぐらいの間に、Webシステムに携わる人間の環境は大きく変わってきています。
それまではデベロッパーとして、何らかの言語を身につけ、ミドルウエアを設定し、TCP/IPを理解し、DBを操作する、こういったスキルで生きてきて、今後も一生それで十分と思えたものの、ここ最近の環境は様変わりし、それだけでは生きていくのが難しくなってきていると感じます。
SIerは廃れ、この業界での存在感が低下し、GAFAと言われるようなネット企業がテック世界を席巻し、クラウドに関する用語が飛び交い、これまでレガシーな技術で十分満足していた人でも戸惑いを感じることもあるのではないでしょうか。
新しい技術になったからと言って、古い技術が必要なくなるかというとそうではなく、結局何か問題に当たった場合は、結局古い技術が必要になります。最近の若い人たちを見て、すごいなと思うのは、新しい技術に追随しながら、古い技術も押さえている点です。
本記事はコンセプトについて長々書いているので、実際のWebアプリケーションサンプルをすぐ見たい場合は、ここはスキップして、次回の記事を参照してください。
ソフトウェア界隈の最近のキーワード
最近のキーワードとしてはこんなところでしょうか。
- 関数型言語
- フロントエンドの進化
- クラウド
- Infrastructure as Code
- コンテナ
- サーバレス
関数型言語がWebシステムに採用される例はまだそこまで多くないでしょうが、関数型言語の思想はJavascript界隈をはじめとして、色々な所に影響していると思います。
この記事は、レガシーな技術を持っている人を対象に書きます。
ここで言っているレガシーとは、ホスト機COBOLの時代ではなく、Linux・オープン系Web・オブジェクト指向のことを指しています。
物理マシンから仮想マシン、コンテナへ
仮想マシン
2000年代の大きな変化は仮想化の広がりではないでしょうか。それまで、複数台のサーバを構成するには、それだけの物理マシンを用意する必要がありました。開発サイドにとって本番環境同様の構成を作ろうとすると、かなりコストが掛かり、そのため、同じマシン内で、起動するプロセス、ポートを分けて「仮想的」に複数台構成をエミュレートしていたものでした。それがVMWareやXen、HyperVといった仮想環境(Virtual Machine)の登場で、大きく変わり、1台の物理マシンの中で、数十台のOS環境を構築でき、スペック以外は本番環境同様の環境で検証できるようになりました。仮想化はサーバ/OSのみならず、ネットワーク、ストレージにも広がっていきました。
技術者にとっては、物理マシンからの移行はスムースで、覚えることはハイパーバイザー等ホスト機の制御のみで、その上にある仮想環境は、物理OSと操作上は変わりがないため、運用の利便性等からも一気に広がっていきました。
レンタルサーバ業者も、仮想環境を利用することで、以前の一台の物理マシンで非rootユーザで複数人が利用する形から、各利用者に仮想環境を割り当ててroot権限を渡すVPSなどのサービスを始め、さらにクラウドの登場により、時間単位で仮想マシンを利用する形に変わってきました。
問題点
仮想環境は便利なものでしたが、問題がありました。
-
リソースの非効率化
- CPU、メモリ、ストレージなどを固定で仮想環境に割り当ててしまうため、あまり使われていないサーバに対して、通常運用では十分すぎるリソースを当ててしまい、全体としてリソース利用状況が効率的ではない。
-
仮想イメージが巨大
- 仮想環境のスナップショットを用意に取れ、それをもとに他のサーバを構築するなどの容易さがあるものの、OS全体のイメージを取得するため、サイズが大きく可搬性に難あり。
-
OSのオーバーヘッド
- ハイパーバイザーの登場によってホストOSの影響はほぼなくなったものの、それぞれの環境でゲストOSが動作しているため、サーバで1プロセスだけ必要な場合でも、OSの諸々のプロセスも動作させる必要があり、これは各環境を完全に分離している点で利点である一方、欠点でもあります。
コンテナ
そこでコンテナ技術の登場となります。
コンテナといえば、ほぼイコールDockerというぐらい、Dockerがデファクトスタンダードになっていますが、上記の3つの問題点を克服しています。
コンテナは論理上、それぞれのコンテナ環境がOS、ストレージ(ファイルシステム)、ネットワークを別々に持っているかのように振る舞うことができるものの、実際には同じホスト環境の上で動作している1プロセスに過ぎません。こうすることで、リソースは共有され、コンテナのイメージはプログラム部分だけとなるため小さくなり、またOSカーネルが共有されているためオーバーヘッドが小さくなります。
環境の生成の自動化
まとめると、環境を容易に作れるようになったと言っていいでしょう。
従来:サーバを購入・セットアップ、レンタルサーバを契約
中間:クラウドで必要な時にサーバを生成、生成を自動化
最近:コンテナを自由に自動的に生成・破棄
サーバ・プロビジョニングの従来との比較
近代のインフラ構築(サーバ・プロビジョニング)においては、従来方式と比べて以下のような特徴があります。
- デプロイ自動化(Infrastructure as Code) vs ドキュメント(構成図・手順書)に従った手作業
- 依存性自動解決
- ビルド自動化(Continuous Delivery)
- Immutable(作っては壊す)
- 再現性(冪等性)
3つのレイヤーへの対応
サーバ・プロビジョニングは、以下の3つの段階で語られます。
- Bootstrap
- Configuration
- Orchestration
簡単に言ってしまえば、BootstrapはOS部分、Configurationはミドルウエア部分、Orchestrationは複数サーバの協調動作となります。前2つが単体のサーバ構築であるのに対して、Orchestrationは、複数サーバを使用して、冗長化・負荷分散、サーバ間通信などを対象にしています。この部分は、AWSをはじめとした、クラウドデザインパターンでよく語られます。
このコンセプト自体は昔からあるものを整理しただけで目新しいものはありませんが、ここに自動化が絡んできます。
以前(今も?)、インフラ構築といえば手作業で行っていました。マシンを調達して設置し、OSをインストールし、ネットワークの設定を行い、ミドルウエアをインストール(rpmやyum, apt等でインストールもしくはソースからコンパイル)および設定、アプリケーションのデプロイ、疎通確認、動作検証といったことを、手順書をもとに手動で行っていました。ところどころは、シェルスクリプトを用いて自動化は行っていましたが、全体としてはやはり手作業に分類されます。
Infrastructure as Code vs ドキュメント(構成図・手順書)
そこで、Infrastructure as Codeというコンセプトが出てきます。
ここで言うコードとは、ソースコードの意味ではなく、宣言的なコード、つまり構築内容を設定ファイルの記述です。その中でシェルスクリプトを含めることもできますが、ここでは、そのような手続き型ではなく、どのようにあるべきかを記載する宣言型のコード(それに適した言語=DSL)が使用されます。Vagrant、Ansible、Chef、Puppet、Teraformなどのツールがよく使われます。
とはいえ、Orchestrationについては、これらのツールだけで行うのが難しいです。
コンテナ環境(Docker)についてはどうかというと、同じく、Bootstrap、ConfigurationについてはDockerfileで記述します。
Orchestrationについては、やはり敷居が高く、Docker Swarmが登場しましたが、運用には不十分な点も多く、そこでKubernetesの登場となります。コンテナのオーケストレーションについては、AWSのECSやFargateも有名ですが、コンセプト的には、複雑ではあるもののKubernetesが網羅されているので、しっかり学ぶにはKubernetesがいいと思います。
シェルスクリプトを使用した自動化の場合、標準化されていないため、環境依存、その環境のみになりますが、Infrastracture as Codeの場合、ターゲットを記載するため、環境の違いはツールが吸収する形になります。またシェルスクリプトの場合、Configurationの自動化は難しいです。
イメージの再利用
ミドルウエアやアプリケーションのデプロイの歴史を振り返ると、自動化が進んでいることがわかります。
- ソースコード
- configure, make, make install
- RPM インストーラ
- バイナリのDL、インストール
- yum, apt-get
- 依存関係を自動的に解決
- インターネットからすべて取得
- image
- すでに全て揃ったOSを含めたイメージ・テンプレート
- imageを元に別のimageを作る
コンテナ化においては出来合いのイメージを持ってくるだけで、あとで設定を注入することで完了です。コンテナは必要なものしか入っていないので軽量化でき、しかもすでに実行環境が含まれているところが凄いところです。
とはいえ、すべて自動化できるかというとそうでもなく、相変わらずソースコードからのビルドやエラーメッセージをもとに依存関係を試行錯誤で解決しなければならない場面も残念ながら多々あります。またImageを作成する場合は、それ以前の知識がすべて必要になります。
依存性の自動解決
ミドルウエアのインストールだけではなく、各開発言語にもパッケージマネージャが使用され、人が依存性を解決するのではなく、各パッケージにさせる方向に進んでおり、依存モジュールを自前でインストールするのではなく自動化します。ソースとしては、何のどのバージョンのパッケージを使うかだけで良く、ソース管理にはライブラリはコミットしません。そのため、保持するファイルサイズは小さくて済むようになります。
ビルドサーバ(CI/CD, DevOps)
以前は、ローカルでビルド→実行環境へアップロード・配置という形でしたが、現在は、Git等のソース管理にコミット→ ビルド(CI/CD)サーバでビルド&デプロイ、さらにプラスして、イメージの作成&デプロイという形で、自動化され、DevOpsというテーマで語られるようになっています。
Immutable:不変環境、ソースコードドリブン、実行環境を直接触らない
従来の方式では何か問題があったとき、
- 実行環境に直接入って調査
- 実行環境に直接入って修正
- ソースコード管理には後でコミットして整合性を保つ
- 実行環境で、tcpdumpやJMXを使用して調査
というふうに直接実行環境にアクセスしていました。
この長所としては、細かい調整・小回りが効く点、即修正可能、影響範囲が明確になる、トライ&エラー(特に実行環境だけでしか問題が起きない場合必要)が可能という点が挙げられますが、欠点としては、ソースコード管理と同期が取れなくなる可能性が高く、今あるソースをデプロイしても再現の保証がない、実行環境が多いと全てにパッチ当てが必要という問題がげられます。
最近の方式では、サーバの状況は監視経由で行い、ログも別の管理サーバに飛ばし、実行環境を直接いじらないようにします。修正はソースコードを修正して、ビルド&リリースをします。
このとき、実行環境自体はステートレスに保ちます。永続化データは実行環境の外側に置き、分離します。実行環境は状態を持たないようにします。そうすることで、実行環境はソースコードと常に一致します。もし再現性が完全であれば、ソースコード比較により、より影響範囲は把握しやすくなります。
この欠点としては、永続化領域を別にする手間、ビルドサーバ経由のため、修正のスピードが落ちる、トライ&エラーに時間を要する、中身がブラックボックス化し、再現しにくい問題の調査が困難となります。現実的には、今でも問題が起きたときはコンテナに入って調査することもあります。極力、実行環境に入らなくても済むようなロギングや監視体制を整えるようにします。
再現性(冪等性)
従来は、細かい調整、サーバ単位でのチューニング・カスタマイズを直接サーバに対して行っていましたが、この問題点は、同じ構成が欲しい時に簡単にできない、何を設定したかを全て調査する必要がある、イメージのバックアップは時間を要するといった点が挙げられます。
同じ筐体のVM環境でのスナップショットは速いものの、クラウドではバックアップとなるため、時間がかかります。そんな実行環境の保存に時間をかけるのなら、永続データを分離して、環境を再現できるようにすればスナップショットの必要性もなくなります。
ここで、Infrastructure as Code、Immutableの考え方で、同じ環境を容易に再現させることができるようになります。元(設計)が同じであれば、同じ環境が何回でも容易に再現可能で、大量のサーバがあっても自動的にアップデートできるようになります。そして、容易に再現できるので壊してもいい。不要なものは全て削除する。元となる情報さえあれば巨大なディスク消費をするスナップショットは不要となります。
このImmutableの考えは、関数型言語(副作用がない)、フロントエンドで使われているFluxアーキテクチャーに通じています。ソースコードが元で、方向が一方通行、途中での修正はありません。
とはいえ、欠点としては、リソースの無駄遣い(一箇所直せば済むものも全体のプロセスを回す)という点が挙げられます。とはいえ、ここはソフトウエアのいいところで、壊して作り直すと言っても、実際の物が壊れるわけではなく、せいぜいビルド作業の電気代とディスクの書き込みやデータ転送が増えることぐらいでしょうか。
元データとして持つもの
従来に比べて、ソースは最小限になります。Git等のソースコードに管理される容量は小さくなり、管理しやすくなります。
- 使用ライブラリのリスト(maven, gradle, npm等で解決)
- ソースコード
- 構成記述 (Dockerfile, vagrant, ansible, docker-compose, k8s-yaml, etc)
- OSや依存するサービス(DBやストレージ等)
- 環境変数
- ちょっとした環境の違いへの対応は環境変数で設定を切り替えられるようにする
- 環境変数に設定する内容はソースとして保持しておく必要がある。
それ以外のもの(依存ライブラリ、OSイメージ)は全てインターネット上から拾ってくる形になります。
環境を内包、可搬性
可搬性とは簡単に持ち運べることを意味し、あるプログラムを簡単に別の環境でも動作させられることです。ここでの課題は、通常プログラムは何らかの環境(CPU、OS、ミドルウエア、ライブラリ等)に依存しており、簡単に持ち運びはできませんでした。これまでにも多くの試みがありました。
- JVM
- Java Virtual Machineと仮想マシンの名が付いています。
- Write Once, Run Anywhereのコンセプトで、同じソースコードからコンパイルした中間言語をJVMがインストールされている環境であればどこでも動くというもの。
- しかし、VMと名の付いているものの、あくまでもJava上の話で、OSまで行かないので不十分で、範囲が限定されている。APサーバでは、複数のアプリケーションをホストできるものの、Javaに限定されてしまう。
- 可搬性というと、JVM上で動くSpringBootはAPサーバを内包しているため、コンテナ環境にはマッチしている。
- 仮想環境
- Xen, VMWare, HyperV, VirtualBoxそれぞれの環境でのイメージの可搬性はある。
- 軽量ではないし、ディスク領域も含むため容量が大きくなる。
これに対して、Dockerは実行環境を内包したプログラムであり、軽量であるため可搬性に優れています。ただし、Dockerがインストールされている環境であればどこでも動くわけではなく、CPUやホストOSによっては動作せず、ここはまだ課題が残っています。
これらの方式の技術背景と利点
従来方式と違うこれらの方式には、前提となる技術背景があり、以下のような変革がもたらしたもので、これらなくして成立はしなかったと言えます。
- インターネット接続
- 仮想環境、コンテナ技術
- クラウド、PaaS
- vs クローズド環境(極端なオンプレミス)
- GitHub/GitLab等のソースコード管理
- 自社のソースもクラウド経由で取得
- オープンソース、Public Repositoryによるパッケージ管理(apt-get, yum, Maven, npm, DockerHub等の充実化)
- 従来、ネットにあるものは直ぐに消えてしまう。
- →オープンソース化等でエコシステムができている
利点としては、
- 運用の労力削減
- シンプル化
- ソースコードを軽量に保てる
- 本当に必要な情報だけで良い
というのが大きいと思います。
コンテナの利用原則
### Dockerの間違った使用方法
以前私は、初めてDockerを使用した際、コンテナを仮想環境の代わりに使っており、便利で、軽量な仮想環境として使っていました。もちろん、こういう使い方もでき、一概に間違いとは言えないものの、本来の用途からは逸脱していました、
CentOSのイメージをpullしてきて起動し、コンテナに入ってインストール作業を行い、作ったらイメージをSaveしてという感じでした。当時のサーバ環境は、内部でたくさんのプロセスが動作する形で、本番環境では複数サーバに分散していたものの、検証環境ではオールインワンで全部を1仮想環境に入れるのが便利でした。顧客ごとにサーバを立てていたので、アプリケーションの各バージョンの検証環境ごとに複数サーバ構成にするのは面倒だったためです。コンテナを利用するようになっても同様の方式をとって、コンテナにIPアドレスを振って、そのIPにアクセスする形にしていました。何かあれば、コンテナに入って操作です。
ここでの問題点は、以下の通りです。
1. Dockerfileを書かずにコンテナ内で手動でインストール
理由としては、Dockerfileがこけるのでトライアンドエラーでインストールしていたためです。
とはいえ、Dockerfileの利点としては、履歴が残り、再現性がある点です。手動で行ってしまうと、あとでhistoryから正しい手順を抽出することになり、再現性という点では不安が残ります。またDockerfileで記述していると、すでに構築済みのところまではイメージは中間イメージとして保存されているため、追加した分のみの作業となるため、トライアンドエラーでもさほど時間は変わりません。
Dockerのregistryにpushする際も、CentOSのイメージが巨大だったため、時間がかかりましたが、Dockerfileで作っていれば、一度Pushされている中間イメージは再度Pushされず、変更点だけがPushされるため時間も短縮できます。
2. 沢山のプロセスを一つに詰め込む
可搬性そして容易に破棄できるようにするため、基本1コンテナ1プロセス(一つの関心事)にすることです。とはいえ、上記の環境では、DockerやDocker-composeでは難しく、Kubernetesが必要だと思います。
1プロセスのみを動かす場合、DockerfileのCMDに起動時に実行するコマンドを書きますが、このプロセスとコンテナのプロセスは一体で、プロセスが終了するとコンテナも終了します。なので、tomcatの起動を指定した場合、コンテナを起動したまま、tomcatの再起動はできません。ここがコンテナが、仮想環境と違うところです。CentOS等のコンテナで、あとからtomcat起動の場合はshを起動するため、shのプロセスを終了させない限り、その上で動くtomcatを終了させてもコンテナは終了しません。
3. ログをコンテナ内に保存
永続化するデータ(DBファイルやアップロードされたファイルなど)は、コンテナ内に保存すべきではありません。確かにdocker stopでコンテナを停止させてもデータは残っており、/var/lib/docker以下にコンテナ内のファイルはありますが、コンテナは容易に破棄されるもので、永続化はされないと認識する必要があります。
ホスト側をコンテナからマウントして、ホスト側に保存されるようにします。
ログについても、もし重要なものであれば同様にホスト側をマウントして保存するか、fluentdなどを利用して、ログ集約サーバに送るべきです。
4. コンテナにIPを割り振って、そのIPにアクセス
コンテナは容易に破棄・再作成され、そのためIPは固定ではなくDHCPにして、ホストからポートフォワードするのが望ましいです。
その他、Dockerを使用する上でのベストプラクティスは以下で紹介されています。
コンテナは、サーバや仮想環境ではなく、見かけ上仮想環境っぽく見せているが、実体はホスト上で動くプロセスにすぎません。ただし、そのプロセスには環境もセットでついているような感じで、実行環境付きのプロセスと言えるでしょう。
サーバレス、FaaS(Function as a Service)との違い
コンテナは十分に軽量であるものの、やはりサーバ上で動作します。それに対してAWS Lambdaに代表されるFaaS(サーバレス)は、運用者がサーバを意識せず(実際にはサーバ上で動作さしているが)運用でき、CPU等のリソースを使用した分のみ課金され、大量トラフィックに対して自動的にスケールするため、さらに進化したものと言えるでしょう。
ただし、あくまでもクラウド業者が提供したプラットフォームを前提としており、自前で構築するには、コストが掛かりすぎます。
また、できることが限られ、実行時間が限定されるため、常駐サービスには向きません。
また、何らかのトリガーを元に起動し、終わってしばらくリクエストが来ない場合は停止するため、Springbootのように起動に時間がかかるものはFaasには向きません。Java関連は概して時間がかかるため、GraalVMなどでネイティブコンパイルの必要があります。
Kubernetes
私が最初にKubernetesを知ったのは数年前のデベロッパサミットでGoogleのエンジニアがテーマとして取り上げたのがきっかけですが、その当時はあまりまとまった情報がなく、サンプルはnginxを使ったものばかりで、どうやって既存のアプリケーションサーバ環境を移行して運用したらいいのか、さっぱりわからずにいました。
今は、すでに学習するのに良い書籍や記事が十分にあり、運用のベストプラクティスもまとまってきています。なので、この記事では、内容の詳細は述べず、考え方とあとサンプルの紹介をしたいと思います
Kubernetesは、GoogleがBorgという、10年以上に渡って社内のサービス管理に用いた十分に実績のあるコンテナの管理システムをオープンソース化したもので、今やサーバの管理のデファクトスタンダードにあり、インフラに携わる人間、さらにはそのプラットフォームを利用する開発者にとっても避けては通れないものになるでしょう。
カバーされるもの
Kubernetesのすごいと思う点は、サーバ運用に関するノウハウが、Google社内で運用された実績があるだけあって、ほぼ全て網羅されているという点です。
- クラスタリング(冗長化、負荷分散)
- 自動復旧
- オートスケール
- 死活監視・ロギング
- 自動アップデート
これらは全て技術としては何ら目新しいものではなく、Kubernetesが新しいものを発明したわけではなく、単に既存の考え方をまとめたというのが正解ですが、それでもこれまでは、こういうインフラの構築は、いろんな技術を組み合わせながら自前でシステム設計を行なっていたのですが、そららが最初から組み入れられている点が重要なところです。
それまでは、インフラにおけるオーケストレーションは概念としてはあったものの、なかなか標準化されたシステムとして出てくることはありませんでした。
十分にカバーされないもの
とはいえ、基本的にプロセスを管理するのが中心であるため、以下の部分は、別途用意する必要があります。特に自前で環境を構築する場合ですが、GKE等のマネージドサービスを利用する場合も、考慮が必要になります。
- 永続化
- データベース
- NAS
- バックアップ
- インターネットからのアクセス
- 自前で構築する場合、グローバルIP、ルータ、ロードバランサ等の設定が必要になる。
- DNS、SSL証明書等
- ロギング、監視
補うための周辺環境はいろいろとあり、周辺エコシステムは豊富で、組み合わせが必要になります。
学習コスト
また、Kubernetesが守備範囲が広いこともあるのですが、かなり取っ付きにくいと感じるのも事実です。その要因としては、用語が取っ付き難いという点です。これはあえて敷居を高くしているのではないかと思うぐらいです。
プログラムを書く人間にとって、変数やメソッド、クラス名の命名は非常に重要で、これ次第で可読性が大きく変化します。開発者であれば命名の難しさは知っているので、致し方ないという部分はありますが、これはちょっとと思うところも少なくないです。Googleの天才が作ったものだから正しいと受け入れている人がほとんどでだとは思いますが。
リソース、Deployment、サービス、PersistentVolumeClaim、taint等々何でこんな名前がついているのか不思議で、不適切な言葉に引きづられるが微妙にニュアンスが違う。
とはいえ、これらの用語は、前世代の用語や考え方を継承していたり、前世代の問題点を解消していたりするので、どうしても前世代を意識した用語や作りになっているので、深い意味があるかもしれません。前の概念を踏襲していて、その説明は当然の前提としてなかったりします。いずれにしても前世代の知識がないと、要点を深く理解できないでしょう。
用語だけでなく、設定される値の統一性のなさも問題があります。yamlの構造も不要な冗長さがあり、version等の値の不統一さもひどいです。
学習の前提としては、以下の知識が必要になるでしょう。
- Docker
- Infrastructure as Code
- Linux
- 仮想環境
- クラウド
きちんと理解するためには、Linuxの基本的なレベルの知識は必要になります。
また、特殊な仕組みではなく、単にNode(サーバ)上で動作しているプロセスに過ぎないこと、Dockerにオーケストレーションを追加したものと捉えたほうがいいでしょう。物理サーバは意識する必要はないですが、仮想サーバまでは意識する必要があります。
残念ながら誰でも容易に使えるようにはなっておらず、Herokuほどまで抽象化はされていません。インフラを十分わかっていない人にとっては、Fargateの方がわかりやすいかもしれません。
最初わからなかったこと
私が最初わからなかったことは以下のことでした。
- Dockerだとコンテナ単位にIPが設定されるが、KubernetesではPod単位に設定される。
- Podはコンテナの集合体で、Kubernetesの管理はPod単位で行われる。
- 通常のプラクティスとしては、1Pod1コンテナで、複数コンテナを入れる場合は、メインのコンテナのサブとして入れるもののみ。
- WebサーバとAPサーバとあったら、通常はPodを分ける。
- ただしファイルシステムは別。Pod内のコンテナ間でファイル共有したければそれぞれのコンテナでマウント。
- どうやって作成したアプリをデプロイするのか
- サンプルにあるのは、出来合いのミドルウエアのイメージばかり
- どうやってソースあるいはビルドしたjarを入れればいいのか不明だった。
- →DockerイメージをCIでビルドするのとセットであることを理解していなかった。
- →MWとアプリ(ビルドソース)と設定を分離。アプリは別コンテナに入れる。
- WebサーバからAPサーバへの接続はどのようにして行うのか
- ミドルウエアの設定ファイル、起動オプションはどうやって設定するのか
- 環境変数はあるが、設定ファイル丸ごと入れ替えたい場合はどうするのか
- postgresの場合、postgres.conf、pg_hba.confなど。
- DBやNASはどう配置すればいいのか
- 一部の箇所だけ修正してリリースしたい場合どうすればいいか。
今回のサンプルを作った目的も、初学者がもし上記のような私と同じ疑問をもった場合に解決できるようにするためです。
仕組み
-
Kubernetes Object
- Kubernetesで管理されるもの(コンテナやそれを格納するPod、それらをまとめたDeployment等やネットワーク、設定を含めて)は全てリソースもしくはKubernetes Objectと呼ばれています。リソースとObjectの違いは、リソースがAPIの視点から見たもので、Objectは生成されたオブジェクトというようですが、この違いに拘る必要はないでしょう。
- 以下のカテゴリにまとめられています。
- Workloads
- Container
- Pod
- Job
- Deployment
- Network
- Service
- Storage&Configuration
- Cluster
- namespace
- Workloads
-
Node
- KuernetesがインストールされているLinuxサーバ(仮想・物理どちらも可)
- Kubernetesの本体である管理システムの入ったマスタノード
- 実際にアプリケーションを動作させるコンテナ管理が入ったワーカーノード
- クラウド業者のマネージドサービスを使用した場合は、マスタノードは普段意識せず、ワーカーノードのみ対象
-
コンテナ管理(コンテナランタイム)
- 各NodeにDockerがインストールされているイメージ
- そこにkubeletなどのマスタノードと通信し、コンテナの管理をするプロセスもインストール
- 一部のKubernetesのモジュールは、他のワーカーノード上のコンテナ同様に、Podとして動作している。
-
pull方式
- 各NodeのkubeletがRegistryからdockerイメージをプルしてローカルに保管
- そのイメージからコンテナを生成・実行
- docker pull, docker start, docker rmのようなコンテナ管理コマンドを実行するのがkubelet
-
全てAPIサーバ経由で間接的に行われる。
- 要求を受けたら直接指示するのではなく、それぞれが状態をAPIサーバ経由でetcdに書き込み、それぞれがAPIサーバ経由で状態を見て実行する。
- 中央コントローラが指示したり、ばら撒いたりする形ではない。
-
コンテナの生成・破棄
- クライアント(kubectl)からリソースがどうあるべきかymlに書いて、applyするとAPIサーバ経由でetcdに書き込む。
- 各Nodeのkubeletがコンテナの状態を監視し、APIサーバ経由でetcdに書き込む。
- スケジューラがetcdから定義とコンテナの状態を見て、どこに何を起動するか決定し、APIサーバ経由でetcdに書き込む。
-
通信
- プロセス間通信などはなく、Pod間はTCP/IPポートで通信
多くのコンポーネントで構成されているが、直接Kubernetesのコンポーネント間で通信したりせず、etcd(KVのデータベース)経由で間接的に行います。この間接性がKubernetesの設計の特徴といっていいいでしょう。etcdへの参照、書き込みはAPIServerが行います。
流れとしては以下のようになります。
- マニフェストやコマンドを使用してリソースをetcdに登録。
- Deployment Controlerなどのコントローラが、現在の稼働状況を参照した上で、Podの作成を決定。
- スケジューラが、Affinityの設定やNodeのリソースの空き状況等を考慮して、Podをどのノードに割り当てるかを決定する。
- 各ノードのkubeletが、スケジューラによるPodの割当を見て、Podを起動する。必要であればImageをPullする。
- kubeletは、Podの稼働状況を監視し、ステータスをetcdへ更新する。
- kubeproxy(DaemonSetとして各ノードで起動)が、Serviceの設定とPodの稼働状況を見て、iptables等の設定を行い、ClusterIPやNodePortへのアクセスでPodに振り分けられるようにする。
構築パターン
1. ミドルウエア(+設定)のみ
このパターンは、キャッシュサーバ(Redis)、プロキシサーバなどを用いる場合で、コンテナのイメージは出来合いのものを使い、設定は環境変数か、Configmapを使用して渡します。
DBについては、コンテナを用いるべきではなく、コンテナ外部のPaaSを使用すべきです。コンテナを使用する場合は、ストレージ(PVC)をマウントしてデータファイルはそこに保存し、ステートフルセットを用いて、マスタDBが落ちた際に自動的に起動して同じ名前で、同じファイルを参照するようにする必要があります。
2. ミドルウエア(+設定)+データのみ
Webサーバのように静的コンテンツからなる場合で、ビルドサーバにて、ベースとなるnginxなどのWebサーバのDockerイメージにファイルを追加して、ビルドのたびにイメージを更新します。nginx.confなどの設定ファイルは、ConfigMap経由で渡します。
3. ミドルウエア+アプリケーション+設定
2同様にビルドサーバにて、ビルドしたバイナリ等をDockerイメージに追加します。環境ごとに設定を変更する場合は、あまり変更点が多くないなら、環境変数を使います。
サンプル
次回で、先の構築パターンに沿って、実運用に使えるサンプルを紹介します。