Kubernetes は、コンテナ化されたアプリケーションのデプロイと管理を自動化するコンテナオーケストレーションシステムです。Helm は、Kubernetes アプリケーションの定義、インストール、アップグレードに役立つ Kubernetes 用パッケージマネージャーです。Helm を使うと、Kubernetes コンポーネント (デプロイ、サービス、HPA、サービスアカウントなど) について、繰り返し使えるテンプレートを定義し、公開し、複数のアプリケーションで共有できます。

このチュートリアルでは、アプリケーション用の Helm チャートを作成し、AWS EKS クラスタにインストールする方法について説明します。具体的には、Python Flask アプリを作成し、Docker 化して、このアプリ用の Helm チャートを作成します。最後に、CircleCI を使用して、既存の AWS EKS クラスタへのデプロイを自動化します。

前提条件

このチュートリアルでは、Flask アプリを作成するために、Python をローカルシステムにセットアップする必要があります。また、アプリケーションをデプロイするための AWS アカウントと、デプロイを自動化するための CircleCI アカウントを作成する必要もあります。

以下のリストを参照して、このチュートリアルに必要な準備を整えてください。

開発を進めながらローカルでテストしたい方は、以下の準備も必要です。

*CircleCI アカウントをお持ちでない場合は、こちらから無料アカウントを作成してください。

Flask アプリを用意する

このセクションでは、Python 3 でサンプル Flask アプリを作成し、それを Docker でコンテナ化します。Flask は、Web アプリケーション開発用のオープンソース Python フレームワークであり、軽量アプリケーションの API サービスレイヤーの開発によく使われています。

アプリを作成する

まず、Flask プロジェクト用の新しいディレクトリを作成し、そのディレクトリに移動します。以下を実行します。

mkdir flask-helm-charts-circleci
cd flask-helm-charts-circleci

次は、アプリのライブラリ依存関係を定義するための requirements.txt ファイルを、flask-helm-charts-circleci ディレクトリ内に作成します。今回の Python アプリでは flask パッケージを使用するので、この依存関係を requirements.txt ファイルに追加します。

Flask==2.2.2

app.py ファイルを flask-helm-charts-circleci ディレクトリ内に作成し、以下のコードを入力します。

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0')

上記のコードスニペットでは、静的応答を返す GET ルートを定義しています。ユースケースに応じて、hello_world メソッドに独自のロジックを追加してもよいでしょう。また、以下のようなルーティングを追加することもできます。

@app.route('/health')
def health_check():
    return 'Health OK'

このアプリは、次のコマンドでローカル実行できます。このコマンドを実行すると、Flask アプリが localhost のポート 5000 で実行されます。

python app.py

http://127.0.0.1:5000 にアクセスして、応答を確認します。

アプリを Docker 化する

Flask アプリを作成できたので、次は Dockerfile を追加してこのアプリをコンテナ化します。今回は、Docker Hub にある Python 3.9.1 ベースイメージを使用します。Dockerfileflask-helm-charts-circleci ディレクトリ内に作成し、以下のコードスニペットを入力します。

FROM python:3.9.1

ADD . /app
WORKDIR /app
RUN pip install -r requirements.txt

EXPOSE 5000

CMD ["python", "app.py"]

このコードスニペットで行っている処理は次のとおりです。

  • Python 3.9.1 ベースイメージを指定する
  • ADD . ステップで、プロジェクトのルートディレクトリにあるファイルすべてをコンテナの /app ディレクトリにコピーする
  • WORKDIR で、コンテナのカレントディレクトリを /app に設定する。以降のコマンドはすべて、このディレクトリ内で実行されます。
  • RUN ステップで、requirements.txt ファイルで定義した Python 依存関係をインストールする
  • EXPOSE ステップで、Docker コンテナのポート 5000 を公開する
  • 最後に CMD で、コンテナのスタートアップコマンドを定義する。これで、コンテナの実行時に Flask アプリが起動されます。

Docker コンテナをビルドして実行する

ローカルで Docker コンテナをビルドして実行し、コンテナが期待どおり動作することを確認します。セットアップに CircleCI を使用する予定の場合、この手順を省略してかまいません。

Docker イメージをビルドするには、flask-helm-charts-circleci ディレクトリで以下のコマンドを実行します。

docker build -t flask-hello-world .

注: 上記のコマンドを実行する前に、ローカルシステムに Docker をインストールしておく必要があります。インストール手順は、「前提条件」セクションのリンクを参照してください。

最後に、以下のコマンドを実行します。

docker run -it --rm -p 5001:5000 flask-hello-world:latest

このコマンドは、Docker コンテナのポート 5000 をローカルのポート 5001 に公開します。http://127.0.0.1:5001 にアクセスして、アプリが期待どおりに動作していることを確認してください。

AWS リソースを作成する

テストとデプロイを行うために、必要な AWS リソースをアカウントにプロビジョニングしましょう。アカウントにある既存のリソースを使用するか、新しいリソースをプロビジョニングしてください (リソースをまだ作成していない場合)。

クラスタの作成時に使用した IAM ユーザーは、CircleCI で認証情報を設定する際にも使用します。

Helm Chart (チャート) を作成する

Chart (チャート) とは、Helm のパッケージ形式です。チャートには、関連する Kubernetes リソース一式を記述するファイルをまとめます。このセクションでは、先ほど作成したアプリ用の Helm チャートを定義します。

アプリのチャートを作成するには、flask-helm-charts-circleci ディレクトリで以下のコマンドを実行します。

helm create charts

注: 上記のコマンドを実行する前に、Helm CLI をインストールしてください。インストール手順は、「前提条件」セクションのリンクを参照してください。

このコマンドを実行すると、次の構造の charts ディレクトリが作成されます。

charts/
  Chart.yaml
  values.yaml
  charts/
  templates/

charts/templates/ ディレクトリには複数のファイルが含まれていますが、必要になるのは _helpers.tpldeployment.yamlservice.yaml だけです。それ以外のファイルは、templates ディレクトリから削除してください。

次に、deployment テンプレートを更新して、このチュートリアルに不要ないくつかの変数を削除します。また、Flask アプリケーションを実行するポートは 5000 なので、containerPort をこの値に変更します。charts/templates/deployment.yaml の内容を、以下のコードスニペットで置き換えます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "charts.fullname" . }}
  labels:
    {{- include "charts.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "charts.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "charts.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 5000
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http

次に、values.yaml ファイル内の値を、アプリケーションにあわせて更新します。charts/values.yaml の内容を、以下のコードスニペットで置き換えます。

replicaCount: 1

image:
  repository: <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/<AWS_ECR_REPO_NAME>
  pullPolicy: IfNotPresent
  tag: "latest"

labels:
  env: "staging"

service:
  port: 5000
  targetPort: 5000
  type: LoadBalancer

replicaCount: 1

autoscaling:
  enabled: false

ingress:
  enabled: false

imagePullSecrets:
  - name: regcred

<AWS_ACCOUNT_ID> は使用する AWS アカウント ID に、<AWS_REGION> は AWS のデプロイリージョンに、<AWS_ECR_REPO_NAME> は Flask Docker イメージの AWS ECR リポジトリに置き換えてください。

最後に、charts/Chart.yamlname を更新します。

name: flask-helm-chart

このチャート名は、AWS ECR Helm リポジトリの名前にする必要があります。

ローカルでテストを実行する

CircleCI でデプロイを自動化する前に、これまでに準備したものをローカルでテストすることをお勧めします。このセクションでは、手動でイメージと Helm チャートを AWS ECR にプッシュして、Helm チャートを AWS EKS クラスタにデプロイする方法を説明します。すぐに CircleCI でデプロイを自動化したい方は、次のセクションに進んでください。

Docker イメージを ECR にプッシュする

Docker イメージをプッシュするには、AWS ECR で Docker を認証する必要があります。以下のコマンドを実行して認証を行います。

aws ecr get-login-password --region <AWS_REGION> | docker login --username AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com

上述のコマンドを実行する前に、AWS CLI をインストールし、AWS 認証情報を設定しておく必要があります。詳細な手順は、「前提条件」セクションのリンクを参照してください。

次に、以下のコマンドを実行して、前のセクションでビルドした Docker イメージにタグを付けます。

docker tag flask-hello-world:latest <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/flask-hello-world

使用する AWS アカウントで、flask-hello-world という名前の ECR リポジトリを用意しておいてください。

以下のコマンドを実行して、Docker イメージを AWS ECR リポジトリにプッシュします。

docker push <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/flask-hello-world:latest

Helm チャートを ECR にプッシュする

Helm チャートをプッシュするには、AWS ECR で helm を認証する必要があります。以下のコマンドを実行して認証を行います。

aws ecr get-login-password --region us-west-2 | helm registry login --username AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com

次に、プロジェクトのルートディレクトリで以下のコマンドを実行して、Helm チャートをパッケージ化します。

helm package charts

このコマンドにより、圧縮バージョンの Helm チャートを含む flask-helm-chart-0.1.0.tgz ファイルが生成されます。

tgz ファイルの名前は、charts/Chart.yaml で定義したチャート名によって異なります。

最後に、以下のコマンドを実行して、Helm チャートを AWS ECR リポジトリにプッシュします。

helm push flask-helm-chart-0.1.0.tgz oci://<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/

Helm チャートを AWS EKS にデプロイする

Helm チャートを AWS ECR リポジトリにプッシュしたことで、チャートを AWS EKS クラスタにデプロイする準備が整いました。まず、eksctl を使用して、ローカルの kubeconfig を更新します。

eksctl utils write-kubeconfig --cluster=<AWS_EKS_CLUSTER_NAME> --region=<AWS_REGION>

このコマンドを実行する前に、eksctl をローカルシステムにインストールしておいてください。 <AWS_EKS_CLUSTER_NAME> は AWS EKS クラスタの名前に変更し、<AWS_REGION> は選択した AWS リージョン名に変更してください。

このコマンドを実行すると、kubeconfig が $HOME/.kube/config に書き込まれます。

最後に、helm install コマンドを実行して、チャートを EKS クラスタにインストールします。

helm install flask-helm oci://<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/flask-helm-chart --version 0.1.0

このコマンドにより、ワークロードが EKS クラスタにインストールされます。クラスタの pods を確認して、インストールが完了したか確かめてください。

kubectl get pods

CircleCI でデプロイを自動化する

ローカルでの Kubernetes クラスタのテストが完了したので、次はワークフローを自動化して、main ブランチにコードをプッシュするたびに変更後のコードがデプロイされるようにします。デプロイを自動化するには、以下の作業が必要です。

  • Helm デプロイスクリプトを追加する
  • コンフィグスクリプトを追加する
  • CircleCI でプロジェクトをセットアップする

Helm デプロイスクリプトを追加する

クラスタ上の既存の Helm チャートを更新する場合、helm install コマンドではなく helm upgrade コマンドを使用する必要があります。flask-helm という名前のチャートがクラスタ上にデプロイ済みであること、および適切なコマンドを使っていることを確認するシェルスクリプトを作成しましょう。scripts/deploy-flask.sh というファイルを作成して、以下のコードを入力します。

#!/bin/bash
TAG=$1
AWS_ECR_ACCOUNT_URL=$2
AWS_ECR_HELM_REPO_NAME=$3
echo "oci://$AWS_ECR_ACCOUNT_URL/$AWS_ECR_HELM_REPO_NAME"
export KUBECONFIG=$HOME/.kube/config
result=$(eval helm ls | grep flask-helm)
if [ $? -ne "0" ]; then
   helm install flask-helm "oci://$AWS_ECR_ACCOUNT_URL/$AWS_ECR_HELM_REPO_NAME" --version $TAG
else
   helm upgrade flask-helm "oci://$AWS_ECR_ACCOUNT_URL/$AWS_ECR_HELM_REPO_NAME" --version $TAG
fi

このシェルスクリプトでは、TAGAWS_ECR_ACCOUNT_URLAWS_ECR_HELM_REPO_NAMEをパラメーターとして受け取ります。

コンフィグスクリプトを追加する

まず、CI パイプライン用のコンフィグファイルが置かれているプロジェクトのルートに、.circleci/config.yaml スクリプトを追加します。このファイルに、以下のコードスニペットを追記します。

version: 2.1

orbs:
  aws-ecr: orbies/aws-ecr@1.2.1

jobs:
  install_helm_chart:
    docker:
      - image: cimg/python:3.10.7
    steps:
      - checkout
      - run:
          name: Install awscli
          command: curl --silent "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && unzip awscliv2.zip && sudo ./aws/install
      - run:
          name: Install eksctl
          command: curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp && sudo mv /tmp/eksctl /usr/local/bin
      - run:
          name: Install and configure kubectl
          command: curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl && kubectl version --client
      - run:
          name: Install and configure kubectl aws-iam-authenticator
          command: curl -Lo aws-iam-authenticator https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/v0.5.9/aws-iam-authenticator_0.5.9_linux_amd64 && chmod +x ./aws-iam-authenticator && mkdir -p $HOME/bin && cp ./aws-iam-authenticator $HOME/bin/aws-iam-authenticator && export PATH=$PATH:$HOME/bin
      - run:
          name: Install and configure helm
          command: sudo curl -L https://get.helm.sh/helm-v3.10.1-linux-amd64.tar.gz | tar xz && sudo mv linux-amd64/helm /bin/helm && sudo rm -rf linux-amd64
      - run:
          name: "docker login"
          command: |
            aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ECR_ACCOUNT_URL
      - run:
          name: "helm login"
          command: |
            aws ecr get-login-password --region $AWS_DEFAULT_REGION | helm registry login --username AWS --password-stdin $AWS_ECR_ACCOUNT_URL
      - run:
          name: "cluster configs"
          command: |
            eksctl utils write-kubeconfig --cluster=$AWS_EKS_CLUSTER_NAME --region=$AWS_CLUSTER_REGION
      - run:
          name: "helm install"
          command: bash ./scripts/deploy-flask.sh 1.0.0 $AWS_ECR_ACCOUNT_URL $AWS_ECR_HELM_REPO_NAME

workflows:
  build_and_push_image:
    jobs:
      - aws-ecr/build-and-push-image:
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          repo: "${AWS_ECR_REPO_NAME}"
          docker-login: false
          account-url: AWS_ECR_ACCOUNT_URL
          region: AWS_DEFAULT_REGION
          tag: "latest"
      - aws-ecr/push-helm-chart:
          account-url: AWS_ECR_ACCOUNT_URL
          aws-access-key-id: AWS_ACCESS_KEY_ID
          aws-secret-access-key: AWS_SECRET_ACCESS_KEY
          create-repo: false
          path: ./charts
          region: AWS_DEFAULT_REGION
          repo: "${AWS_ECR_HELM_REPO_NAME}"
          tag: 1.0.0
          requires:
            - aws-ecr/build-and-push-image
      - install_helm_chart:
          requires:
            - aws-ecr/push-helm-chart

少し時間をとって、上記の CircleCI コンフィグファイルを確認してみましょう。このコンフィグファイルでは、3 つのジョブを定義しています。

  • aws-ecr/build-and-push-image ジョブでは、aws-ecr orb を使用して Flask Docker イメージをビルドし、AWS ECR リポジトリにプッシュします。
  • aws-ecr/push-helm-chart ジョブも、aws-ecr orb を使用し、Helm チャートをパッケージ化して AWS ECR Helm リポジトリにプッシュします。
  • install_helm_chart ジョブでは、ECR リポジトリから Helm チャートをプルし、AWS EKS クラスタにインストールします。 このジョブのステップで行う処理は以下のとおりです。
    • awsclieksctlkubectlaws-iam-authenticatorhelm などの必要なツールをインストールする
    • AWS CLI を使用して Docker および Helm を AWS ECR で認証する
    • eksctl を使用して kubeconfig を $HOME/.kube/config に書き込む
    • scripts/deploy-flask.sh スクリプトを使用して Helm チャートを EKS クラスタにインストールする

コンフィグファイルを適切に用意できたので、GitHub にプロジェクトのリポジトリを作成して、すべてのコードをプッシュしましょう。手順については、GitHub にプロジェクトをプッシュする方法に関するブログ記事 (英語) を参照してください。

CircleCI でプロジェクトをセットアップする

次は、CircleCI アカウントにログインします。CircleCI ダッシュボードの [Projects (プロジェクト)] タブをクリックし、目的の GitHub リポジトリ名を検索して、該当するプロジェクトの [Set Up Project (プロジェクトのセットアップ)] をクリックします。

CircleCI プロジェクト設定サンプル

新しいコンフィグファイルを手動で追加するか、既存のコンフィグファイルを使用するかを確認するメッセージが表示されます。必要なコンフィグファイルはコードベースにプッシュ済みなので、[Fastest (最速)] オプションを選択し、コンフィグファイルが置かれているブランチの名前を入力します。[Set Up Project (プロジェクトのセットアップ)] をクリックして続行します。

CircleCI プロジェクトの設定

セットアップが完了すると、パイプラインがトリガーされます。まだ環境変数を定義していないので、最初の実行ではパイプラインが失敗します。

環境変数を設定する

プロジェクトダッシュボードの [Project settings (プロジェクト設定)] をクリックし、[Environment variables (環境変数)] タブに移動します。[Add environment variable (環境変数を追加)] ボタンをクリックし、新しいキー値を追加します。

以下の環境変数を追加してください。

  • AWS_ACCESS_KEY_ID: AWS 認証情報の作成中に取得されるアクセスキー
  • AWS_SECRET_ACCESS_KEY: AWS 認証情報の作成中に取得されるシークレット
  • AWS_DEFAULT_REGION: アプリケーションをデプロイするリージョン
  • AWS_ECR_ACCOUNT_URL: AWS アカウントの URL。
  • <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com の値を適切なアカウント ID とリージョンに置き換え、アカウント URL として設定します。
  • AWS_ECR_HELM_REPO_NAME: Helm チャートの AWS ECR リポジトリの名前
  • AWS_ECR_REPO_NAME: Flask アプリケーションの Docker イメージが置かれている AWS ECR リポジトリの名前
  • AWS_EKS_CLUSTER_NAME: AWS EKS クラスタの名前
  • AWS_CLUSTER_REGION: クラスタを作成したリージョン。このリージョンは、デフォルトと異なる場合があります。

CircleCI 環境変数の設定

環境変数を設定したら、パイプラインを再実行します。今回は、ビルドに成功するはずです。

CircleCIパイプラインの構築成功

まとめ

このチュートリアルでは、CircleCI を使用して、Helm チャートを自動的にビルドし AWS EKS クラスタにデプロイする方法について説明しました。Helm チャートは再利用可能なテンプレートであり、クラスタの複雑な面を簡単に処理し、クラスタの更新プロセスを簡素化できます。また、Helm チャートは、パブリックサーバーとプライベートサーバーの両方で簡単にバージョン管理、共有、ホストできます。CircleCI を使用すると、ビルドとデプロイのパイプラインを自動化して、継続的デリバリーを実現できます。パイプラインを使用して、Docker イメージと Helm チャートを AWS ECR リポジトリにプッシュし、ワークロードに更新があれば EKS クラスタも更新できます。

今回使用したソースコードは、こちらの GitHub リポジトリにあります。


Vivek Kumar Maskara 氏は、JP Morgan のソフトウェアエンジニアです。好きなことは、コードの記述、アプリの開発、Web サイトの作成、自身の経験をつづった技術ブログの執筆などです。プロフィールと連絡先情報については、maskaravivek.com をご覧ください。

さんの他の投稿を読む Vivek Maskara