Amazon EKS を触りながら Kubernetes に入門する

はじめに

OSS活用による開発生産性向上とアプリケーションの安定動作の両立が求められる中、「アプリケーションのコンテナ化」は不可欠なものになりつつあります。しかしながら、コンテナ化されたアプリケーションを合理的に運用するには、種々の問題があります。 それらの問題を解決してくれるプラットフォームである Kubernetes (K8s) の概要を Kubernetes のマネージドサービスである Amazon Elastic Kubernetes Service (Amazon EKS) に触れながら確認していきます。 なお、この記事は、実運用経験のない、K8s 初心者が調べた結果に基づき作成していることをご了承ください。

目次

前提知識

  • コンテナ開発の基本についての知識:Docker / Docker Compose を知っている

目標

  • Kubernetes (K8s) とは何か、何が嬉しいのかをざっくり理解する
  • Amazon EKS を使って外部に公開されたサービスをデプロイする

Kubernethes とは

f:id:linkode-okazaki:20200424094901p:plain

  • 公式サイト
  • 一言で言うと、コンテナ化されたアプリケーションを合理的に運用するために設計されたOSSプラットフォームです。
  • 大きな機能は以下の 4 つです;
    1. 計画に従ってアプリケーションを迅速にデプロイする
    2. 稼働中のアプリケーションのスケールをする
    3. アプリケーションのバージョンアップの際に、無停止でロールアウトする
    4. CPU時間などの資源管理を厳格にし、ハードウェアの稼働率を高めて無駄をなくす

仮想化からコンテナ開発の登場まで

  • 可搬性と高速な開発サイクルが重要視される中で、仮想化ソフトウェアを用いた仮想マシンVM)環境での開発が登場しました。
  • さらに簡便で優れたアプリケーション実行基盤としてコンテナを用いた開発が登場しました。
  • コンテナを利用することの価値は以下のとおりです:
    1. 基盤利用の効率向上
    2. 迅速な利用開始が可能
    3. 不変の実行基
  • 仮想サーバとコンテナの違いは、以下の図のように表せます。

f:id:linkode-okazaki:20200424095131p:plain

コンテナを本番で運用する際の問題点

f:id:linkode-okazaki:20200424094856p:plain

  • コンテナを本番運用で利用しようとすると以下のような問題点が出てきます;
    • 複数のDockerホストの管理
    • コンテナのスケジューリング
    • ローリングアップデート
    • スケーリング / オートスケーリング
    • コンテナの死活監視
    • 障害時のセルフヒーリング
    • サービスディスカバリ
    • ロードバランシング
    • データの管理
    • ワークロードの管理
    • ログの管理
    • Infrastructure as Code
    • その他エコシステムとの連携や拡張
  • K8s では、このコンテナ管理の問題を人間が悩まなくて良いようによしなにやってくれるプラットフォームです。

K8sアーキテクチャ

f:id:linkode-okazaki:20200424095013p:plain

  • 上の図は、K8s の基本的な構成を表したものです。
  • K8sマスターノードと呼ばれるサーバーから構成されます。
    • マスターとノードから構成される単位を「クラスタ」と呼びます。
  • マスターは、クラスタの管理を担当します。
    • 管理コマンドkubectlなどの API クライアントからのリクエストを受け、アプリケーションのデプロイ、スケール、コンテナのバージョナップなどのすべての要求に対応します。
  • ノードはアプリケーションの実行を担当します。
    • コンテナの実行環境を提供します。
    • ユーザーからのアクセス負荷増大に対応するために処理能力を補強する際は、ノード数を増やす必要があります。
    • ノードの増設や削除は、アプリケーションの稼働中でも実施できます。その際、コンテナの配置先はマスターが自動的に決め、最適なノードへデプロイします。
  • ポッドK8s におけるコンテナの最小実行単位です。
    • 1 つまたは複数のコンテナを含んだ 1 つのグループです。

f:id:linkode-okazaki:20200424095212j:plain

  • kubectlからのリクエストはkube-apiserverで受け付け、適切なプロセスに引き渡されます。
  • kube-schedulerは、検出したポッドを適切なノードに割り当てる役割を担っており、可用性、性能、キャパシティに重要な影響を及ぼします。
  • kube-controllerはモニタリングした現在状態から希望状態への遷移を実行します。
  • etcdには K8s クラスタのすべての管理データが保存されています。

f:id:linkode-okazaki:20200424094839p:plain

  • kube-proxyは各ノードで動作し、効果用生活低オーバーヘッドのロードバランシングを提供します。
    • サービスとポッドの変更を監視し、構成を最新状態に保ち、ポッド間とノード間の通信を確実にします。
    • 対応するポッドへのパケットをリダイレクトできるよう、iptables のルールを操作します。
  • kubeletは各ノードで動作します。
    • ポッドとコンテナの実行を担います。
    • ポッドとコンテナの状態を API サーバへ集約します。
    • コンテナを検査するためのプログラムを実行します。

f:id:linkode-okazaki:20200424095059p:plain

K8sAPI オブジェクト

  • K8s の「オブジェクト」とは、クラスタ内部のエンティティを指します。
    • ポッドやコントローラ、サービスなど
  • K8s を操作する際は、「あるべき姿」を書いたマニフェストファイルを API サーバに送信して、オブジェクトの作成・変更・削除と状態の取得をすることによって行います。
    • マニフェストファイルは宣言的設定(<-> 命令的設定)が特徴です。

サービスの役割

f:id:linkode-okazaki:20200424094851p:plain

  • サービスはクライアントとのリクエストのトラフィックをポッドへ転送する役割を果たします。
    • サービスのセレクターにセットされたラベルと一致するポッドを etcd から探して選び出します。

コントローラの役割

  • コントローラは、ポッドの実行を制御します。
  • ポッドに課せられるワークロードの種類(処理の種類)に応じて、適切なコントローラが選択できます。
  • 主なコントローラとその役割は以下のとおりです:
コントローラのタイプ 役割
デプロイメント(Deployment) ポッドと レプリカセットの宣言的なアップデートを行います。ポッドの起動と停止を迅速に実行するよう振る舞い、稼働中ポッドの順次置き換え、オートスケーラーとの連動、高可用性構成が可能という特徴があります。
レプリカセット(ReplicaSet) デプロイメントと連動して、ポッドのレプリカ数を管理します。デプロイメントを通して利用することが推奨されています。
ステートフルセット(StatefulSet) 永続データの処理に適したコントローラです。ポッドをシリアル番号で管理し、永続ボリュームとポッドの対応関係を維持します。
ジョブ(Job) バッチ処理のコンテナが正常終了するまで、再試行を繰り返すコントローラです。ポッドの実行回数、同時実行数、試行回数の上限を設定して実行することが可能です。また、削除されるまでログを保持する特徴もあります。
クーロンジョブ(CronJob) 時刻指定で定期的にジョブを生成します。UNIX の cron と同じ形式で、ジョブの生成時刻を設定できます。
デーモンセット(DaemonSet) K8s クラスタの全ノードで同じポッドを実行するためのコントローラです。ノードがクラスターに追加されるときは、ポッドがノード上に追加されます。ノードがクラスターから削除されたときは、それらのポッドはガーベージコレクターにより除去されます。システム運用の自動化に適しています。
  • ワークロードの種類とそれに適したコントローラの目安の対応関係は以下のとおりです:
ワークロードの種類 コントローラの種類
フロントエンド処理 デプロイメント
バックエンド処理 ステートフルセット
バッチ処理 ジョブ、クーロンジョブ
システム運用 デーモンセット

Amazon EKS を使う

f:id:linkode-okazaki:20200424095005p:plain

  • 公式サイト
  • K8s を動かす環境として[Docker for Desktop]や[Minikube]のように手元で動かす環境を選択することも可能ですが、ここでは、Amazon Elastic Container Service for Kubernetes (EKS) を利用します。
  • 利用する際にかかる費用は、EKS の利用料金の他に、EC2 の費用は別途必要です。

準備するもの

  • 以下のものが準備されていることを確認します。
    • Docker for Desktop
    • kubectl
    • AWS CLI(IAMユーザー作ってprofile設定済み)
    • aws-iam-authenticator
    • eksctl

クラスタの作成

  • 以下のコマンドでクラスタを作成します。
    • 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
  • 上記コマンドが完了した後の構成は以下の図のようなイメージです:

f:id:linkode-okazaki:20200424095320p:plain

外部に公開されたサービスをデプロイする

f:id:linkode-okazaki:20200424094847p:plain

デプロイメントの作成

  • 以下の内容の 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

f:id:linkode-okazaki:20200424095047p:plain

  • 以下のコマンドで、クラスタにデプロイメントを作成します。
$ 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>

  • ここまでで出来たものは先ほどの図の以下の箇所です。

f:id:linkode-okazaki:20200424094925p:plain

サービスの作成

  • ここまででアプリケーションを実行する環境は整いましたが、外部からのアクセスを受け付けられる状態にはなっていません。
  • 外部からのアクセスを受け付けるために、LoadBalancerのサービスを追加します。

f:id:linkode-okazaki:20200424095038p:plain

  • デプロイメントのときと同様に、マニフェストファイル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

f:id:linkode-okazaki:20200424095353p:plain

  • 以下のコマンドで、クラスタにサービスを作成します。
$ 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 にアクセスすると、ポッドのアプリケーションにアクセスできるようになります。

f:id:linkode-okazaki:20200424095358p:plain

  • 実際に、ブラウザにEXTERNAL-IPのアドレスにアクセスしてみると、Nginx の画面が表示されます。

f:id:linkode-okazaki:20200424095041p:plain

サービス稼働中にマニフェストを書き換えてみる

ポッドを増やしてみる

  • deployment.ymlreplicas を 1 から 3 に変更して適用してみます。

f:id:linkode-okazaki:20200424095407p:plain

// 適用前の確認
$ 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 つのポッドが新たに作成され、以下の図のようになったことがわかります。

f:id:linkode-okazaki:20200424094930p:plain

サービスを受け付けるポートを変更してみる

  • service.yml を変更し、サービスへのアクセスを受け付けるポートを 80 から 8080 に変更します。変更を保存したら適用します。

service.ymlの変更内容の説明 f:id:linkode-okazaki:20200424095034p:plain

$ 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 に変更してアクセスすると、問題なくアクセスできることがわかります。

f:id:linkode-okazaki:20200424095017p:plain

利用するコンテナイメージを変更し、ポッドの数も減らしてみる

  • 再び、deployment.yml の内容を以下のように変更します。変更点は以下の2箇所です。
    • replicas の数を 3 から 1 に変更する
    • template.spec.containersimage を nginx から httpd に変更する

deployment.ymlの変更内容 f:id:linkode-okazaki:20200424095422p:plain

  • 変更を保存したら適用します。
// 適用前の確認
$ 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
  • 上の変遷で何が起こっているかを順に追っていくと、こういうことになります。
    • ポッドを減らすのと新たなイメージを展開するのを同時進行で行っている
    • 新たなイメージのポッドが稼働状態になるまで、変更前のポッドが稼働し続け、サービスが停止しないようになっている
    • 新しいイメージのポッドが稼働し始めると、古いポッドは消去される

f:id:linkode-okazaki:20200424094907p:plain f:id:linkode-okazaki:20200424094918p:plain f:id:linkode-okazaki:20200424095028p:plain

  • 稼働中のポッドの詳細を確認すると、イメージが変更されていることがわかります。
$ 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 の初期画面に変更されていることがわかります。

f:id:linkode-okazaki:20200424095445p:plain

お片付け

  • EKS の料金は、クラスターを置いてある時間単位で課金されるため、使わずにそのまま放置してしまうと、無駄に費用がかかってしまいます。
  • また、EKS と一緒に作成した EC2 インスタンスも時間単位で課金されます。
  • そのため、使い終わったらインスタンスも一緒にクラスターを削除しておけば、無駄な費用がかかりません。
  • まず、以下のコマンドで、外部に公開されたサービスがないかを確認します。
    • クラスターを削除する前にこれらのサービスが残っていると、ネットワークの設定等が残っているため、クラスターがうまく削除できません。
$ kubectl get svc --all-namespaces
  • 上記コマンドで出た一覧のうち、EXTERNAL-IP が付与されているものは下記コマンドで削除します。
$ kubectl delete svc [サービス名]
  • 最後に、下記のコマンドでクラスターごと削除して完了です。
$ eksctl delete cluster --name [クラスター名]

まとめ

  • Kubernetes の基礎を確認し、実際に EKS 上で動かし、その基本的な動きを確認しました。
  • コンテナの更新や削除を行ってもサービスが停止しないよう、K8s でよしなに動作してくれることがわかりました。
  • K8s の各オブジェクトの動作や EKS の機能の詳細については、より深く確認していく必要があります。

参考資料