このThe Twelve-Factor App ( https://12factor.net/ja/ ) は、Heroku創業メンバーの一人である Adam Wiggins によって書かれたウェブ・アプリケーションやSaaSを作り上げるためのメソッドです。 これは著者の様々な実務経験をもとに、まとめられたもので、プログラミング言語、データベースなどのサービスの種類に依存せず応用することができる優れたメソッドです。 多くのデベロッパーが、このメソッドの参照を推奨しています。
もともと、The Twelve-Factor Appは、アプリケーションをHerokuのプラットフォームに適合するためのメソッドとも見なせますが、コンテナ技術を利用したアプリケーションにも共通点が多く、Kubernetesでアプリケーションを運用するケースでも有用と考えます。
そこで、このメソッドをK8sへ適用するために、Twelve-Factorの項目を要約し、K8s対応をコメントと図でまとめました。
1.コードベース
一つのコードベースから、複数のデプロイを作る
- コードベースとは、GitやSubversionなどのコードのバージョン管理ツール
- デプロイは、開発者のPC、テスト環境、本番環境などで実行されるアプリのインスタンス
- それぞれのデプロイは、一つのコードベースからビルドされたものであるべき
k8s適応のコメント
k8sではコンテナを、一つのレジストリからデプロイするため、依存ソフトウェアも同じにする事が容易である。
- レジストリに、アプリの実行形式とそれが依存するOSやライブラリを含んだコンテナを登録
- レジストリに登録されたコンテナから、各環境へデプロイする
コンテナのレジストリ、リポジトリの関係
レジストリとリポジトリをあまり区別されずに記述される事が多いので、図で整理する。
- レジストリは、複数のリポジトリを収容するサービスであり、リポジトリ名とアプリが対応する
- アプリケーション名とリポジトリ名を対応させ、コンテナのバージョン管理はタグを利用する。
Docker & K8s 参考資料
- Docker Docs, Understanding Registry, https://docs.docker.com/registry/introduction/
- Docker Docs, Overview of Docker Hub, https://docs.docker.com/docker-hub/
- Kubernetes Documentaion, Concepts images, https://kubernetes.io/docs/concepts/containers/images/
- Kubernetes Documentaion, Tasks Pull an Image from a Private Registry, https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
- Kubernetes API, secret docker-registry, https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-em-secret-docker-registry-em-
2.依存関係
依存関係を明示的に宣言し分離する
- OSとそのパッケージを含めたすべての依存関係を 依存関係宣言 マニフェストで完全かつ厳密に宣言
- 依存関係分離 ツール(vituralenv,pip,gemなど)を使って、暗黙の依存関係が“漏れ出ない”ことを保証
- この指定は、本番環境と開発環境の両方に対して同様に適用
k8s適応のコメント
Dockerfileに、依存関係分離ツールの実行を記述することで、アプリが依存する全てのパッケージをコンテナへインストールする
Docker & K8s 参考資料
- Docker Docs, Dockerfile reference, https://docs.docker.com/engine/reference/builder/#usage
- Docker Docs, Best practices for writing Dockerfiles, https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
- Docker Docs, Push images to Docker Cloud, https://docs.docker.com/docker-cloud/builds/push-images/
3.設定
設定を環境変数に格納する
- アプリ設定は、デプロイ(ステージング、本番、開発環境など)の間で異なり得る唯一のモノとする。
- 環境変数は、コードを変更することなくデプロイごとに簡単に変更できる。
- 設定ファイルとは異なり、誤ってリポジトリにチェックインされる可能性はほとんどない。
k8s適応のコメント
全ての設定を環境変数にすると逆に作業が増えるので、設定ファイルを永続ボリュームとしてマウントできる機能や、参照制限を持たせる事ができるSecretを有効に利用する。
- ConfigMap : 設定ファイルをk8sのネームスペースに保存する
- Secret : ユーザーID/パスワード、証明書など資格情報を、他者に見られない様にk8sのネームスペースに保存する
- Service : k8sサービスとしてデプロイすることで、それ以降のポッドの環境変数で、エンドポイントを環境変数で参照可能
- ポッド・テンプレート : ConfigMapやSecretの情報を、コンテナの環境変数、または、ボリュームとしてマウントして参照可能
Docker & K8s 参考資料
- Kubernetes Documentaion, Tasks Configure a Pod to Use a ConfigMap, https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/
- Kubernetes Blog, Configuration management with Containers, https://kubernetes.io/blog/2016/04/configuration-management-with-containers/
- Kubernetes Documentaion, Concepts Secrets, https://kubernetes.io/docs/concepts/configuration/secret/
- Kubernetes Documentaion, Concepts Service, https://kubernetes.io/docs/concepts/services-networking/service/
- Kubernetes Documentaion, Concepts DNS for Services and Pods, https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
- Kubernetes Documentaion, Concepts Writing a Deployment Spec, https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#writing-a-deployment-spec
4.バックエンドサービス
バックエンドサービスをアタッチされたリソースとして扱う
- バックエンドサービス はアプリが通常の動作の中でネットワーク越しに利用するすべてのサービスを言う。
- 例としては、データストア(例:MySQL や CouchDB)、メッセージキューイングシステム(例:RabbitMQ や Beanstalkd)、電子メールを送信するためのSMTPサービス(例:Postfix)、キャッシュシステム(例:Memcached)などがある。
k8s適応のコメント
参照は同じ様に、k8sのDNSを利用してアドレス解決できるので、サービスの作成方法に注目すると以下2点がある。
- 内部サービスの場合、サービスタイプにClusterIPを選択して、セレクタのラベルを設定して、対応するポッドのラベルとマッチさせる。
- 外部サービスの場合、サービスタイプにExternalNameを設定して、外部サービスのIPアドレスやポート番号を設定する
Docker & K8s 参考資料
- Kubernetes Documentaion, Concepts Connecting Applications with Services, https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/
- Kubernetes Documentaion, Concepts Headless services, https://kubernetes.io/docs/concepts/services-networking/service/#headless-services
5.ビルド、リリース、実行
ビルド、リリース、実行の3つのステージを厳密に分離する
- ビルド・ステージ : 依存するファイルを集め実行形式を生成する
- リリース・ステージ : 実行形式を受け取り、デプロイの現在の設定と結合する
- 実行・ステージ : プロセスを起動して、アプリケーションを開始する
k8s適応のコメント
もともとHerokuのビルド・パックの3つの段階が書かれている様なので、k8sに合わせると以下になる。
- ビルド : アプリコードと依存パッケージをまとめて、Dockerfileを使ってビルドして、コンテナとしてレジストリへ登録する
- リリース: 設定ファイル、資格情報を環境に登録、k8s DeploymentやStatefulSetのマニフェストをk8sクラスタへ適用する
- 実行: kubectl apply -f マニフェストで、◯◯環境でアプリの実行を開始する
Docker & K8s 参考資料
- Docker Docs, docker build, https://docs.docker.com/engine/reference/commandline/build/
- Kubernetes Documentaion, Concepts Configuration Best Practices, https://kubernetes.io/docs/concepts/configuration/overview/
- Kubernetes Documentaion, Concepts Pod Lifecycle, https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/
6.プロセス
アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する
- ステートレスとは、ウェブサーバーの様に、一回毎のリクエストとレスポンスだけで処理する事を意味している。つまり、ロードバランサーのステッキーセッションやセッション・アフィニティなどと呼ばれる機能を使って、セッションを確立したコンテナへ固定的に振り向ける事は、コンテナ上のアプリがセッションの状態を持つことになり、12Factor に違反することになります。
- セッションの情報は、外にあるキャッシュ・サービスなどを利用して、アプリ・コンテナの突然の停止に備える必要があります。
- リクエストを受けてから応答する間の一時的なメモリ上のデータは対象外
k8s適応のコメント
k8sでは、ステートレスなアプリケーション用に、Deploymentコントローラーがある。また、ステートフルなアプリケーション用に StatefulSet コントローラーがあるが、リクエストを特定のポッドへ振り分けるものではない。 このため、セッション情報などは、外部のキャッシュやDBサービス、または、永続ボリュームに保存しなければなりません。
- サービスを作成すると生成されるClusterIPは、kube-proxyによって、リクエストをランダムにポッドへ振り分ける。セッションを開始したポッドへ降り先を固定する機能はない。
- セッションを開始したアプリケーションのポッドに、継続してリクエストを向けるには、Ingressを利用できる。しかし、このIngressは外部への公開用なので、k8sクラスタ内のアクセスに適用できない。
Docker & K8s 参考資料
- Kubernetes Documentaion, Tasks Run a Stateless Application Using a Deployment, https://kubernetes.io/docs/tasks/run-application/run-stateless-application-deployment/
- Kubernetes Documentaion, Concepts Deployments, https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
- Kubernetes Documentaion, Concepts StatefulSets, https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
- Kubernetes Documentaion, Concepts Ingress, https://kubernetes.io/docs/concepts/services-networking/ingress/
7.ポートバインディング
ポートバインディングを通してサービスを公開する
- HerokuではWebアプリとして、PHP、Python、Rubyなどプログラミング言語のフレームワーク等のサーバーポートをバインドして、サービスを公開することを勧めており、公開のためにHTTPサーバーの利用を推奨していません。
k8s適応のコメント
- K8sでは、アプリのポートを公開するために、Nginx等のHTTPサーバーのコンテナと組み合わせ事もでき、次の機能によってコンテナのポート番号を公開用ポート番号に対応づける事ができる。
- NodePort : ノードのIPアドレスでポートを公開、ポードフォワーディングと負荷分散
- Ingress : URL変換、TLS暗号化、負荷分散等
Docker & K8s 参考資料
- Kubernetes Documentaion, Concepts Service, https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
- Kubernetes Documentaion, Concepts Ingress, https://kubernetes.io/docs/concepts/services-networking/ingress/
8.並行性
プロセスモデルによってスケールアウトする
- 個々のワークロードの種類を処理タイプに割り当てることで、開発者はアプリケーションが多様なワークロードを扱えるように設計する
- これが真価を発揮するのはスケールアウトが必要になったときである。シェアードナッシングで水平分割可能なTwelve-Factor Appプロセスの性質は、並行性を高める操作が単純かつ確実なものである。
k8s適応のコメント
処理タイプでコンテナを分けて、水平分割可能な並列度の高い設計を目指すべきは、k8sでも同じである。
Docker & K8s 参考資料
- Kubernetes Documentaion, Tasks Horizontal Pod Autoscaler, https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
- Kubernetes Blog, Principles of Container-based Application Design, https://kubernetes.io/blog/2018/03/principles-of-container-app-design/
9.廃棄容易性
高速な起動とグレースフルシャットダウンで堅牢性を最大化する
- アプリの起動時間を最小化する努力は常に必要である。この素早い起動は、設定条件の変更、ハード障害からの回復、ワークロードの急増に迅速に対応するためである。
- 終了要求シグナル(SIGTERM)を受け取ると、速やかに終了処理を実行して、プロセスを終了することを推奨している。
k8s適応のコメント
- k8sがノードの一つを保守作業のために停止させる場合、ポッドをライブマイグレーションするのではなく、SIGTERMを送ってポッドが停止することを要求する。このためコンテナのSIGTERMの処理の実装は必須である。
- コンテナのサイズが大きいと、レジストリからのダウンロードに時間を要し、起動が遅くなる。特にコンテナは無駄なパッケージが含まない様に注意を払うべきである。
Docker & K8s 参考資料
- Kubernetes Blog, Principles of Container-based Application Design, https://kubernetes.io/blog/2018/03/principles-of-container-app-design/
- Kubernetes Documentaion, Concepts Pods, https://kubernetes.io/docs/concepts/workloads/pods/pod/
10.開発/本番一致
開発、ステージング、本番環境をできるだけ一致させた状態を保つ
- 開発者が書いたコードは、出来るだけ早くデプロイする。
- コードを書いた開発者は、デプロイ作業に関わり、その後、モニタリングする。
- 開発環境と本番環境をできだけ一致した状態に保つ
k8s適応のコメント
K8sクラスタをネームスペースで分割して、同じハードウェア環境上で、影響なく、開発〜本番環境を共存する事ができる。
-
ネームスペースで実現できる分割
- CPUとメモリの利用上限、ネットワークのアクセス範囲
- kubectlコマンドの有効範囲
- RBACベースのサービスアカウントにより、開発者やSREのアクセス範囲を限定
Docker & K8s 参考資料
- Kubernetes Documentaion, Concepts Namespaces, https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
- Kubernetes Documentaion, Tasks Namespaces Walkthrough, https://kubernetes.io/docs/tasks/administer-cluster/namespaces-walkthrough/
- Kubernetes Documentaion, Concepts Configure Default Memory Requests and Limits for a Namespace, https://kubernetes.io/docs/tasks/administer-cluster/manage-resources/memory-default-namespace/
- Kubernetes Documentaion, Tasks Use Calico for NetworkPolicy, https://kubernetes.io/docs/tasks/administer-cluster/network-policy-provider/calico-network-policy/
11.ログ
ログをイベントストリームとして扱う
- アプリケーションはログファイルに書き込んだり、ログをローテション管理しない。
- それぞれの実行中のコンテナは、イベントストリームを標準出力にバッファリングせずに書きだす
- ローカルでの開発中、このストリームをターミナルで見ることで、アプリケーションのデバッグを実施
k8s適応のコメント
K8sではコンテナから標準出力に書き出されるイベントストレームは、Logstashでフィルタと転送されElasticSearchに集約される。 Kibanaを利用して、ポッドやコンテナ横断的に、イベントの発生状況を把握できる。
- コンテナ内のログファイルに書き出さず、標準出力へ書き出す事は同じである。
Docker & K8s 参考資料
- Kubernetes Documentaion, Tasks Logging Using Elasticsearch and Kibana, https://kubernetes.io/docs/tasks/debug-application-cluster/logging-elasticsearch-kibana/
- Kubernetes Documentaion, Concepts Logging Architecture, https://kubernetes.io/docs/concepts/cluster-administration/logging/
12.管理プロセス
管理タスクを1回限りのプロセスとして実行する
- アプリケーションのメンテナンスのための一回限りの処理の管理用コードは、同じアプリケーションのコンテナ内で実行されるべき
- 同じコードベース(Gitリポジトリ等)に含まれるべき
- アプリと同じコミットの世代が利用できる様に、同じコンテナに含まれるべき
k8s適応のコメント
- kubectl exec pod -c コンテナ名 実行コマンド を利用して、一回限りの処理を実行する。
Docker & K8s 参考資料
- Kubernetes Documentaion, Tasks Get a Shell to a Running Container, https://kubernetes.io/docs/tasks/debug-application-cluster/get-shell-running-container/
IBM Cloud Kuberntes Service https://www.ibm.com/cloud/container-service