はじめに
OSS活用による開発生産性向上とアプリケーションの安定動作の両立が求められる中、「アプリケーションのコンテナ化」は不可欠なものになりつつあります。しかしながら、コンテナ化されたアプリケーションを合理的に運用するには、種々の問題があります。 それらの問題を解決してくれるプラットフォームである Kubernetes (K8s) の概要を Kubernetes のマネージドサービスである Amazon Elastic Kubernetes Service (Amazon EKS) に触れながら確認していきます。 なお、この記事は、実運用経験のない、K8s 初心者が調べた結果に基づき作成していることをご了承ください。
目次
前提知識
- コンテナ開発の基本についての知識:Docker / Docker Compose を知っている
目標
- Kubernetes (K8s) とは何か、何が嬉しいのかをざっくり理解する
- Amazon EKS を使って外部に公開されたサービスをデプロイする
Kubernethes とは
- 公式サイト
- 一言で言うと、コンテナ化されたアプリケーションを合理的に運用するために設計されたOSSプラットフォームです。
- 大きな機能は以下の 4 つです;
- 計画に従ってアプリケーションを迅速にデプロイする
- 稼働中のアプリケーションのスケールをする
- アプリケーションのバージョンアップの際に、無停止でロールアウトする
- CPU時間などの資源管理を厳格にし、ハードウェアの稼働率を高めて無駄をなくす
仮想化からコンテナ開発の登場まで
- 可搬性と高速な開発サイクルが重要視される中で、仮想化ソフトウェアを用いた仮想マシン(VM)環境での開発が登場しました。
- さらに簡便で優れたアプリケーション実行基盤としてコンテナを用いた開発が登場しました。
- コンテナを利用することの価値は以下のとおりです:
- 基盤利用の効率向上
- 迅速な利用開始が可能
- 不変の実行基盤
- 仮想サーバとコンテナの違いは、以下の図のように表せます。
コンテナを本番で運用する際の問題点
- コンテナを本番運用で利用しようとすると以下のような問題点が出てきます;
- 複数のDockerホストの管理
- コンテナのスケジューリング
- ローリングアップデート
- スケーリング / オートスケーリング
- コンテナの死活監視
- 障害時のセルフヒーリング
- サービスディスカバリ
- ロードバランシング
- データの管理
- ワークロードの管理
- ログの管理
- Infrastructure as Code
- その他エコシステムとの連携や拡張
- K8s では、このコンテナ管理の問題を人間が悩まなくて良いようによしなにやってくれるプラットフォームです。
K8s のアーキテクチャ
- 上の図は、K8s の基本的な構成を表したものです。
- K8s はマスターとノードと呼ばれるサーバーから構成されます。
- マスターとノードから構成される単位を「クラスタ」と呼びます。
- マスターは、クラスタの管理を担当します。
- ノードはアプリケーションの実行を担当します。
- コンテナの実行環境を提供します。
- ユーザーからのアクセス負荷増大に対応するために処理能力を補強する際は、ノード数を増やす必要があります。
- ノードの増設や削除は、アプリケーションの稼働中でも実施できます。その際、コンテナの配置先はマスターが自動的に決め、最適なノードへデプロイします。
- ポッドは K8s におけるコンテナの最小実行単位です。
- 1 つまたは複数のコンテナを含んだ 1 つのグループです。
kubectl
からのリクエストはkube-apiserver
で受け付け、適切なプロセスに引き渡されます。kube-scheduler
は、検出したポッドを適切なノードに割り当てる役割を担っており、可用性、性能、キャパシティに重要な影響を及ぼします。kube-controller
はモニタリングした現在状態から希望状態への遷移を実行します。etcd
には K8s クラスタのすべての管理データが保存されています。
kube-proxy
は各ノードで動作し、効果用生活低オーバーヘッドのロードバランシングを提供します。- サービスとポッドの変更を監視し、構成を最新状態に保ち、ポッド間とノード間の通信を確実にします。
- 対応するポッドへのパケットをリダイレクトできるよう、iptables のルールを操作します。
kubelet
は各ノードで動作します。- ポッドとコンテナの実行を担います。
- ポッドとコンテナの状態を API サーバへ集約します。
- コンテナを検査するためのプログラムを実行します。
K8s の API オブジェクト
- K8s の「オブジェクト」とは、クラスタ内部のエンティティを指します。
- ポッドやコントローラ、サービスなど
- K8s を操作する際は、「あるべき姿」を書いたマニフェストファイルを API サーバに送信して、オブジェクトの作成・変更・削除と状態の取得をすることによって行います。
- マニフェストファイルは宣言的設定(<-> 命令的設定)が特徴です。
サービスの役割
コントローラの役割
- コントローラは、ポッドの実行を制御します。
- ポッドに課せられるワークロードの種類(処理の種類)に応じて、適切なコントローラが選択できます。
- 主なコントローラとその役割は以下のとおりです:
コントローラのタイプ | 役割 |
---|---|
デプロイメント(Deployment) | ポッドと レプリカセットの宣言的なアップデートを行います。ポッドの起動と停止を迅速に実行するよう振る舞い、稼働中ポッドの順次置き換え、オートスケーラーとの連動、高可用性構成が可能という特徴があります。 |
レプリカセット(ReplicaSet) | デプロイメントと連動して、ポッドのレプリカ数を管理します。デプロイメントを通して利用することが推奨されています。 |
ステートフルセット(StatefulSet) | 永続データの処理に適したコントローラです。ポッドをシリアル番号で管理し、永続ボリュームとポッドの対応関係を維持します。 |
ジョブ(Job) | バッチ処理のコンテナが正常終了するまで、再試行を繰り返すコントローラです。ポッドの実行回数、同時実行数、試行回数の上限を設定して実行することが可能です。また、削除されるまでログを保持する特徴もあります。 |
クーロンジョブ(CronJob) | 時刻指定で定期的にジョブを生成します。UNIX の cron と同じ形式で、ジョブの生成時刻を設定できます。 |
デーモンセット(DaemonSet) | K8s クラスタの全ノードで同じポッドを実行するためのコントローラです。ノードがクラスターに追加されるときは、ポッドがノード上に追加されます。ノードがクラスターから削除されたときは、それらのポッドはガーベージコレクターにより除去されます。システム運用の自動化に適しています。 |
- ワークロードの種類とそれに適したコントローラの目安の対応関係は以下のとおりです:
ワークロードの種類 | コントローラの種類 |
---|---|
フロントエンド処理 | デプロイメント |
バックエンド処理 | ステートフルセット |
バッチ処理 | ジョブ、クーロンジョブ |
システム運用 | デーモンセット |
Amazon EKS を使う
- 公式サイト
- K8s を動かす環境として[Docker for Desktop]や[Minikube]のように手元で動かす環境を選択することも可能ですが、ここでは、Amazon Elastic Container Service for Kubernetes (EKS) を利用します。
- 利用する際にかかる費用は、EKS の利用料金の他に、EC2 の費用は別途必要です。
準備するもの
- 以下のものが準備されていることを確認します。
クラスタの作成
- 以下のコマンドでクラスタを作成します。
node-type
では要件に応じた EC2 インスタンスを設定します。- 今回は、そこまでの要件は必要ないので、
t2.micro
を指定しています。
- 今回は、そこまでの要件は必要ないので、
ssh-public-key
を指定すると、マネージド型ノードグループ内のノードへの SSH アクセスが有効になります。- 今回は、EC2 のキーペア作成メニューを用いて作成しました。
- キーペアの作成方法は Amazon EC2 のキーペア を参考にしました。
$ eksctl create cluster \ --name [クラスタ名] \ --region [リージョン名] \ --node-type t2.micro \ --nodes 3 \ --nodes-min 1 \ --nodes-max 4 \ --ssh-access \ --ssh-public-key path_to_ssh-key/cluster-key.pub
- 上記コマンドが完了した後の構成は以下の図のようなイメージです:
外部に公開されたサービスをデプロイする
デプロイメントの作成
- 以下の内容の
deployment.yml
を作成します。
deployment.yml
の内容と説明
apiVersion: apps/v1 kind: Deployment metadata: name: sample-deployment spec: selector: matchLabels: app: sample-app replicas: 1 template: metadata: labels: app: sample-app spec: containers: - name: nginx-container image: nginx:1.7.9 ports: - containerPort: 80
- 以下のコマンドで、クラスタにデプロイメントを作成します。
$ kubectl apply -f deployment.yml
- 作成が完了したことを確認します。
$ kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE sample-deployment 1/1 1 1 169m
- デプロイメントの詳細は
kubectl describe deploy [デプロイメント名]
で確認できます。
デプロイメントの詳細確認
$ kubectl describe deploy sample-deployment Name: sample-deployment Namespace: default CreationTimestamp: Wed, 22 Apr 2020 15:09:46 +0900 Labels: <none> Annotations: deployment.kubernetes.io/revision: 1 Selector: app=sample-app Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=sample-app Containers: nginx-container: Image: nginx:1.7.9 Port: 80/TCP Host Port: 0/TCP Environment: <none> Mounts: <none> Volumes: <none> Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable Progressing True NewReplicaSetAvailable OldReplicaSets: <none> NewReplicaSet: sample-deployment-6fd5d47fc6 (1/1 replicas created) Events: <none>
- ここまでで出来たものは先ほどの図の以下の箇所です。
サービスの作成
- ここまででアプリケーションを実行する環境は整いましたが、外部からのアクセスを受け付けられる状態にはなっていません。
- 外部からのアクセスを受け付けるために、
LoadBalancer
のサービスを追加します。
- デプロイメントのときと同様に、マニフェストファイル
service.yml
を以下の内容で作成します。
service.yml
の内容と説明
apiVersion: v1 kind: Service metadata: name: sample-service spec: type: LoadBalancer selector: app: sample-app ports: - protocol: TCP port: 80 targetPort: 80
- 以下のコマンドで、クラスタにサービスを作成します。
$ kubectl apply -f service.yml
- 作成が完了したことを確認します。
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 4h46m sample-service LoadBalancer 10.100.239.155 XXX.XXX.elb.amazonaws.com 80:30627/TCP 119m
- サービスの詳細は
kubectl describe svc [サービス名]
で確認できます。
サービスの詳細確認
$ kubectl describe svc sample-service Name: sample-service Namespace: default Labels: <none> Annotations: Selector: app=sample-app Type: LoadBalancer IP: 10.100.239.155 LoadBalancer Ingress: XXX.XXX.elb.amazonaws.com Port: <unset> 80/TCP TargetPort: 80/TCP NodePort: <unset> 30627/TCP Endpoints: 192.168.59.181:80 Session Affinity: None External Traffic Policy: Cluster Events: <none>
- EKS で LoadBalancer のサービスを作成した際、AWS の機能として外部からのリクエストを受け付けるための Elastic Load Balancing (ELB) が自動で作成されます。
- 上記で確認した
EXTERNAL-IP
にアクセスすると、ポッドのアプリケーションにアクセスできるようになります。
- 上記で確認した
- 実際に、ブラウザに
EXTERNAL-IP
のアドレスにアクセスしてみると、Nginx の画面が表示されます。
サービス稼働中にマニフェストを書き換えてみる
ポッドを増やしてみる
deployment.yml
のreplicas
を 1 から 3 に変更して適用してみます。
// 適用前の確認 $ kubectl get pods NAME READY STATUS RESTARTS AGE sample-deployment-6fd5d47fc6-64njf 1/1 Running 0 3h48m // マニフェストを変更して適用 $ kubectl apply -f deployment.yml deployment.apps/sample-deployment configured // 適用後の確認 $ kubectl get pods NAME READY STATUS RESTARTS AGE sample-deployment-6fd5d47fc6-64njf 1/1 Running 0 3h51m sample-deployment-6fd5d47fc6-dx6cm 1/1 Running 0 12s sample-deployment-6fd5d47fc6-qk4mk 1/1 Running 0 12s
- 稼働中のポッドはそのままに、2 つのポッドが新たに作成され、以下の図のようになったことがわかります。
サービスを受け付けるポートを変更してみる
service.yml
を変更し、サービスへのアクセスを受け付けるポートを 80 から 8080 に変更します。変更を保存したら適用します。
service.yml
の変更内容の説明
$ kubectl apply -f service.yml $ kubectl describe svc sample-service Name: sample-service Namespace: default Labels: <none> Annotations: Selector: app=sample-app Type: LoadBalancer IP: 10.100.239.155 LoadBalancer Ingress: XXX.XXX.elb.amazonaws.com Port: <unset> 8080/TCP TargetPort: 80/TCP NodePort: <unset> 30839/TCP Endpoints: 192.168.20.94:80,192.168.59.181:80,192.168.76.119:80 Session Affinity: None External Traffic Policy: Cluster Events: Type Reason Age From Message ---- ------ ---- ---- ——— Normal EnsuringLoadBalancer 87s (x8 over 158m) service-controller Ensuring load balancer Normal EnsuredLoadBalancer 86s (x8 over 158m) service-controller Ensured load balancer
- アドレスはそのままでポートを 8080 に変更してアクセスすると、問題なくアクセスできることがわかります。
利用するコンテナイメージを変更し、ポッドの数も減らしてみる
- 再び、
deployment.yml
の内容を以下のように変更します。変更点は以下の2箇所です。replicas
の数を 3 から 1 に変更するtemplate.spec.containers
のimage
を nginx から httpd に変更する
deployment.yml
の変更内容
- 変更を保存したら適用します。
// 適用前の確認 $ kubectl get pod NAME READY STATUS RESTARTS AGE sample-deployment-6fd5d47fc6-tdzrd 1/1 Running 0 2m44s sample-deployment-6fd5d47fc6-vl7pf 1/1 Running 0 72s sample-deployment-6fd5d47fc6-w4wft 1/1 Running 0 72s // マニフェストを変更して適用 $ kubectl apply -f deployment.yml deployment.apps/sample-deployment configured
- 適用直後に何度か
kubectl get pod
を実行すると、ポッドの切り替わりの様子がわかります。
// 適用 $ kubectl apply -f deployment.yml deployment.apps/sample-deployment configured // 適用直後 $ kubectl get pod NAME READY STATUS RESTARTS AGE sample-deployment-6fd5d47fc6-tdzrd 1/1 Running 0 4m46s sample-deployment-6fd5d47fc6-w4wft 0/1 Terminating 0 3m14s sample-deployment-85f65bf6f4-nhq4x 0/1 ContainerCreating 0 4s // ちょっと時間経過 $ kubectl get pod NAME READY STATUS RESTARTS AGE sample-deployment-6fd5d47fc6-tdzrd 1/1 Running 0 5m2s sample-deployment-85f65bf6f4-nhq4x 0/1 ContainerCreating 0 20s // 適用完了 $ kubectl get pod NAME READY STATUS RESTARTS AGE sample-deployment-6fd5d47fc6-tdzrd 0/1 Terminating 0 5m17s sample-deployment-85f65bf6f4-nhq4x 1/1 Running 0 35s
- 上の変遷で何が起こっているかを順に追っていくと、こういうことになります。
- ポッドを減らすのと新たなイメージを展開するのを同時進行で行っている
- 新たなイメージのポッドが稼働状態になるまで、変更前のポッドが稼働し続け、サービスが停止しないようになっている
- 新しいイメージのポッドが稼働し始めると、古いポッドは消去される
- 稼働中のポッドの詳細を確認すると、イメージが変更されていることがわかります。
$ kubectl describe deploy sample-deployment Name: sample-deployment Namespace: default CreationTimestamp: Wed, 22 Apr 2020 15:09:46 +0900 Labels: <none> Annotations: deployment.kubernetes.io/revision: 2 Selector: app=sample-app Replicas: 3 desired | 1 updated | 4 total | 3 available | 1 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=sample-app Containers: nginx-container: Image: httpd:latest Port: 80/TCP Host Port: 0/TCP Environment: <none> Mounts: <none> Volumes: <none> Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable Progressing True ReplicaSetUpdated OldReplicaSets: sample-deployment-6fd5d47fc6 (3/3 replicas created) NewReplicaSet: sample-deployment-85f65bf6f4 (1/1 replicas created) Events:(省略)
- 先ほどと同じアドレスにアクセスすると、ブラウザで表示される画面が Apache の初期画面に変更されていることがわかります。
お片付け
- EKS の料金は、クラスターを置いてある時間単位で課金されるため、使わずにそのまま放置してしまうと、無駄に費用がかかってしまいます。
- また、EKS と一緒に作成した EC2 インスタンスも時間単位で課金されます。
- そのため、使い終わったらインスタンスも一緒にクラスターを削除しておけば、無駄な費用がかかりません。
- まず、以下のコマンドで、外部に公開されたサービスがないかを確認します。
$ kubectl get svc --all-namespaces
- 上記コマンドで出た一覧のうち、
EXTERNAL-IP
が付与されているものは下記コマンドで削除します。
$ kubectl delete svc [サービス名]
- 最後に、下記のコマンドでクラスターごと削除して完了です。
$ eksctl delete cluster --name [クラスター名]
まとめ
- Kubernetes の基礎を確認し、実際に EKS 上で動かし、その基本的な動きを確認しました。
- コンテナの更新や削除を行ってもサービスが停止しないよう、K8s でよしなに動作してくれることがわかりました。
- K8s の各オブジェクトの動作や EKS の機能の詳細については、より深く確認していく必要があります。
参考資料
- 高良(2019)「15Stepで習得 Dockerから入るKubernetes」リックテレコム
- なぜKubernetesが必要なのか? - ThinkIt ( https://thinkit.co.jp/article/13289 )
- K8s の概要について参考にしました。
- 数時間で完全理解!わりとゴツいKubernetesハンズオン!! - Qiita ( https://qiita.com/Kta-M/items/ce475c0063d3d3f36d5d )
- 前半の「なぜ K8s が必要なのか」についての説明を特に参考にしました。