コンテナ化と継続的デリバリーを用いたデプロイ
概要
本記事のゴールは、CI/CDパイプラインを作ることである。GitHubレポジトリと連携して、コードがpushされたら、EKSクラスタにデプロイしたアプリが更新される仕組みを開発する。全体図は、以下に記載する。
- GitHubでFlaskアプリが管理されている。複数のエンジニアによって、こちらで最新のコードがあげられる。
- GitHubレポジトリにコードがpushされると、AWS CodeBuildがトリガーされる。AWS CodeBuildとは、ソースコードをコンパイル、テスト実行、デプロイする継続的インテグレーションサービスである。CodeBuildはpushされたソースコードから、新しいアプリケーションを作り、それをコンテナレジストリにアップロードする。
- 次にCodePipelineは、作成されたアプリケーションイメージを自動的にkubernetesクラスタにデプロイする。
- 最後に、kubernetesクラスタは、アプリケーションのサービス提供を開始する。
プロジェクトのToDoリスト
以下に記載する。
- ローカル環境にて、アプリの動作確認を行う
- まずは、Flaskアプリをローカルで実行する。
- 次に、そのアプリをローカルでコンテナ化して、アプリケーション実行に必要な環境を理解する。
- AWSクラウドでアプリを実行する
- EKSクラスタとIAMロールを作成する。最初に、任意のリージョンにて、AWS CLIを用いてEKSクラスタを作成する。そして、Codebuildがk8s/EKSクラスタにアクセスできるようなIAMロールを作成する。
- GitHubのアクセストークンを作成する。GitHubアカウントでアクセストークンを作成する。こちらをAWS CodeBuildで使うことで、GitHubからCodeBuildに対して、コードのpushやテストができるようになる。
- CloudFormationを用いて、CodeBuild / CodePipelineを作成する。作成は、CloudFormationのテンプレを用いて行う。
- 最後に、手動でビルドを実行する。それにより、アプリケーションのデプロイが可能となる。
ローカル環境にて、アプリの動作確認を行う
事前準備
下記の用意をすること
- Docker Desktop / Git Code editor / Python 3.7-3.9のインストール
- AWSアカウントの登録
- 下記レポジトリのForkをすること
最後に、デフォルトリージョンをus-east-2に設定して、GitHubレポジトリのディレクトリへ移動する。
aws configure set region us-east-2
# Run this command to see if the AWSCLI is configured correctly. It should return the list of IAM users.
aws iam list-users
cd cd0157-Server-Deployment-and-Containerization/
Flaskアプリをローカルで実行
アプリケーションのコンテナ化をする前に、下記事項を実施し動作確認する。
- アプリに必要なパッケージをインストールする。
# Assuming you are in the cd0157-Server-Deployment-and-Containerization/ directory
pip install -r requirements.txt
- python main.pyを実行して、Flaskサーバを立ち上げる。立ち上げたら、http://127.0.0.1:8080/ にアクセスして、"Healthy" と表示されるか確認する。
- /authエンドポイントに対してログイン情報をPOSTして、結果として返却されたトークンをTOKENに保存する。これにより、正しくJWTトークンが取得できているか、確認する。
export TOKEN=`curl --data '{"email":"abc@xyz.com","password":"mypwd"}' --header "Content-Type: application/json" -X POST localhost:8080/auth | jq -r '.token'
echo $TOKEN
以上が、ローカル環境での動作確認となる。
アプリをローカルでコンテナ化し実行
このステップでは、アプリのDocker化を説明する。これにより、アプリをDockerのコンテナで動かすことができる。
- 環境変数の保存をする。コンテナはローカルホスト環境の変数読み込みができないので、.env_fileを作成する。こちらで、下記内容を保管する。こちらはあくまで、コンテナをローカル環境で動作確認するためである。なので、GitHubにpushする必要はなく、.gitignoreで上げないように注意する。
JWT_SECRET='myjwtsecret'
LOG_LEVEL=DEBUG
- イメージをビルドする。
docker build -t myimage .
- コンテナを構築し動かす。コンテナのポート8080とローカルPCの80を接続する。そして、エンドポイントのチェックをする。Healthyと表示されていればOK。
docker run --name myContainer --env-file=.env_file -p 80:8080 myimage
curl --request GET 'http://localhost:80/'
- 別の /authエンドポイントについては、下記コマンドで動作確認をする。
# Calls the endpoint 'localhost:80/auth' with the email/password as the message body.
# The return JWT token assigned to the environment variable 'TOKEN'
export TOKEN=`curl --data '{"email":"abc@xyz.com","password":"WindowsPwd"}' --header "Content-Type: application/json" -X POST localhost:80/auth | jq -r '.token'`
echo $TOKEN
# Decrypt the token and returns its content
curl --request GET 'http://localhost:80/contents' -H "Authorization: Bearer ${TOKEN}" | jq .
AWSクラウドでアプリを実行する
まず、EKSクラスタを構築する。EKSクラスタとは、kubernetesをクラウド上で利用できるマネージドサービスである。そして、CodeBuildがEKSクラスタにアプリケーションをデプロイするため、必要となるIAMロールを作成する。そして最後に、CodeBuildとCodePipelineを構築する。この時に、GitHubのアクセストークン、CloudFormationテンプレを使用したCodeBuildとCodePipelineの構築を行う。
EKSクラスタとIAMロール作成
まず最初に、EKSクラスタを構築する。構築されると、下記のように表示される。
eksctl create cluster --name simple-jwt-api --nodes=2 --version=1.22 --instance-types=t2.medium --region=us-east-2
次に、CodeBuild用のIAMロールを作成する。まずは、AWSアカウントのidを取得する。
aws sts get-caller-identity --query Account --output text
# Returns the AWS account id similar to
# 519002666132
下記の通り、trust.jsonファイルを更新する。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<ACCOUNT_ID>:root"
},
"Action": "sts:AssumeRole"
}
]
}
そして、下記の通り、IAMロールを作成する。
aws iam create-role --role-name UdacityFlaskDeployCBKubectlRole --assume-role-policy-document file://trust.json --output text --query 'Role.Arn'
最後に、作成したロールに対して、IAMポリシーをアタッチする。
aws iam put-role-policy --role-name UdacityFlaskDeployCBKubectlRole --policy-name eks-describe --policy-document file://iam-role-policy.json
アタッチするポリシーには、EKSとSSMに関連した操作権限が記述されている。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"eks:Describe*",
"ssm:GetParameters"
],
"Resource": "*"
}
]
EKS RBACを使用してCodeBuildを認証
わかりやすく説明すると、EKSとはおもちゃの箱のようなもの。ここには、いろいろな種類のおもちゃ(アプリケーション)が入っていて、Amazonがその箱を管理している。一方、CodeBuildとは、ロボット工場のようなもの。ここでは、パーツ(コード)が入ってきて、それをロボットが組み立てて新しいおもちゃ(アプリケーション)を作る。
この箱は鍵がかかっていて、誰でもおもちゃに触れるわけではない。鍵を持つ人だけが触れる。それがRBACで、役割に基づいて鍵を持つ人を決める。aws-auth ConfigMapとは、大きな鍵のリストである。ここに名前が書かれた人だけが鍵をもらって、箱の中のおもちゃに触れる。
だから、ロボット工場(CodeBuild)新しいおもちゃを箱に入れたり、既存のおもちゃを修理する時には箱の鍵が必要となる。そのため、鍵のリスト(aws-auth ConfigMap)を書き換える必要がある。そうすると、箱の鍵をもらって、おもちゃを自由に扱える。
この鍵は、新しく箱(EKSクラスタ)を作るたびに、リストを書き加える手続きが必要となる。
以下で、専門用語の説明をする。
- RBACとは、Role-Based Access Controlのことで、ロールに基づいてシステムへのアクセスを制御する方式。個々のユーザではなく、ユーザの役割に基づいて、アクセス権限を設定する。
- ConfigMapとは、EKSクラスタへのアクセスを制御するための重要なkubernetesリソースである。このConfigMapはEKSクラスタにIAMユーザやロールを関連づけて、これらに対して特定のkubernetesロールを付与するために使われる。
上記理由より、CodeBuildがEKSクラスタを管理するためには、CodeBuild用のIAMロールをConfigMapに追加する必要がある。これによって、CodeBuildはEKSクラスタ内で操作が可能となる。具体的な手順は以下の通り。
- CodeBuild用のIAMロールを作成する。こちらには、EKSクラスタで必要な操作を実行するためのアクセス権を付与する。こちらは、既に実施済み。
- 次に、kube-systemにあるaws-authというConfig Mapを取得して、その結果をYAML形式で表示して、/tmp/aws-auth-patch.ymlにリダイレクトする。つまり、EKSクラスタでConfigMapを取得する。
kubectl get -n kube-system configmap/aws-auth -o yaml > /tmp/aws-auth-patch.yml
- ConfigMapのYAMLを編集して、新たに作成したIAMロールを追加する。まずaws-auth-patch.ymlファイルを開く
code /System/Volumes/Data/private/tmp/aws-auth-patch.yml
- そして、下記内容を追加する。
- groups:
- system:masters
rolearn: arn:aws:iam::<ACCOUNT_ID>:role/UdacityFlaskDeployCBKubectlRole
username: build
- 最後に、ConfigMapを更新する。
kubectl patch configmap/aws-auth -n kube-system --patch "$(cat /tmp/aws-auth-patch.yml)"
CodePipelineとCodeBuildを使用したKubernetesへのデプロイ
では、パイプラインを使用して、GitHubのコードpushを監視して、アプリケーションの継続的CI/CDを行う。レポジトリに対してコードがpushされた際に、新しいイメージが作成されて、EKSにデプロイされる。
GitHubトークンの作成
まず最初に、下記リンクからGitHubトークンを作成する。これにより、プライベートリポジトリのアクセス権を付与できる。
CloudFormationによるCI/CDパイプライン作成
ForkしたGitHubのレポジトリで、ci-cd-codepipeline.cfn.ymlファイルがあるので、そちらでCloudFormationを実行する。実行手順は、下記を参考にすること。
そして、GitHubUserのDefaultがSudKulとなっているので、自身のGitHubユーザ名に書き換える。
パスワードをAWSパラメータストアに保存
EKSクラスタで、暗号情報をJWTとして保管する必要がある。前述の通り、/authのエンドポイントで、myjwtsecretというテキストを使用した。こちらをEKSでも使うため、以下のコードでAWSパラメータストアに保管する。
aws ssm put-parameter --name JWT_SECRET --overwrite --value "myjwtsecret" --type SecureString
## Verify
aws ssm get-parameter --name JWT_SECRET
buildspec.ymlの設定
こちらは、AWS CodeBuildとEKSクラスタを使うとき、それらがお互いうまく働くようにするための指示である。
- 最初に、どのバージョンの道具(kubectl)を使っているか、確認する。kubectl version --short --clientを実行することで、確認できる。
- 次に、AWSもしくはKubernetesドキュメントを参照して、Linuxマシン用の利用可能なバージョンを調べる。調べた内容に基づいて、buildspec.ymlのkubectlのバージョンを変更する。以下は一例である。
- KUBECTL_VERSION="v1.21.2" # Your chosen version
- 最後に、秘密(JWT_SECRET)を、CodeBuildが使えるようにする。これは、buildspec.ymlの一番最後に記載する必要がある。
env:
parameter-store:
JWT_SECRET: JWT_SECRET
CI/CDパイプラインの完成
以下の通り、パイプラインが作られる。
以下の手順でコードをコミットすると、パイプラインが実行される。
## Verify the remote destination.
## It should point to the repo in your account (not the repo in the Udacity account).
## Otherwise, FORK the Udacity repo, and then clone it locally
git remote -v
## Make the changes locally
git status
## Add the changed file to the Git staging area
git add <filename>
## Provide a meaningful commit description
git commit -m “my comment“
## Push to the local master branch to the remote master branch
git push
そうすると、buildが実行されたことを確認できるはずである。
APIエンドポイントの動作確認
最後に、外部IPアドレスを使って、APIエンドポイントの動作確認を行う。まずは、下記コマンドでサービスの外部IPアドレスを取得する。
kubectl get services simple-jwt-api -o wide
このコマンドは、simple-jwt-apiというサービス情報を取得し、external-ipを取得できる。
次に、その外部IPアドレスを使って、正しくAPIエンドポイントが動作しているかテストする。実行スクリプトは、以下の通りである。
export TOKEN=`curl -d '{"email":"<EMAIL>","password":"<PASSWORD>"}' -H "Content-Type: application/json" -X POST <EXTERNAL-IP URL>/auth | jq -r '.token'`
curl --request GET '<EXTERNAL-IP URL>/contents' -H "Authorization: Bearer ${TOKEN}" | jq
まずauthエンドポイントにPOSTリクエストを送って、トークンを取得する。そのトークンを使って、contentsエンドポイントにGETリクエストを送信して、${TOKEN}という環境変数に保存する。
ビルドプロセスにテストを追加
新しいコードをクラスタにデプロイする前に、ユニットテストが通過することを要求する。それで通過した場合のみ、クラスタに新しいコードをデプロイする。
- まずbuildspec.ymlファイルを開く。pre_buildセクションで、必要なソフトウェアをインストールする。
pre_build:
commands:
- pip3 install -r requirements.txt
- python3 -m pytest test_main.py
- 次に、test_main.pyファイルを開く。任意のテストで、assert Falseを追加することで、故意にテストを失敗させる。その結果、不適切なデプロイが防止されるか、下記のように確認できる。
まとめ
今回は、CI/CDパイプラインを使用した、アプリケーションのデプロイ方法を記載した。
- まずは、ローカルでアプリケーションの動作確認をした。各エンドポイントが、正しく実行されているか、確認した。
- 次に、アプリケーションのコンテナ化を行った。その上で、コンテナとローカルPCのポートを繋いで、動作確認をした。
- アプリケーションをデプロイするため、EKSクラスタ / CodeBuild向けのIAMロール / CodeBuildを作成した。CodeBuildが適切にEKSクラスタを使用できるように、EKS RBACの認証設定をした。
- 最後に、CodePipelineとCodeBuildへアプリケーションをデプロイした。これにより、GitHubでコード更新がされたら、適宜アプリケーションが更新される。
- そのために、まずGitHubアクセストークンを発行した。これにより、AWSサービスがGitHubにアクセスできる。
- 次に、CodeBuildとCodePipelineをCloudFormationを使って作成した。
上記内容で、コードをGitHubにpushすると、アプリケーションが更新されるのを確認できるはずである。
参照
Discussion