私が関わっている案件ではK8s(っというかEKSやGKE)をよく使っています。K8sを使っていればArgoCDを使えるので、デプロイの仕組みをあまり意識する必要はありません。一方でAWS ECSで運用したい要望が出てくると自分でデプロイの仕組みを作る必要がでてくるので話は複雑になります。たまたまそんな機会があり、色々手法を検索しているとAWSのサンプルでECSをGitOpsで管理するサンプルがでてきました。今回はこれを動かしてみます。
gitops-amazon-ecs-sample
AWSのサンプルが色々置いてあるOrgで、その中の一つにGitOpsでECSを管理するサンプルが転がっていました。仕組みは以下の図です。
処理の流れを見ていくと
- CodeCommitにdeployment.yamlをPush
- CodePipelineが発火
- CodePipelineがCodeBuildをキック
- CodeBuildがdeployment.yamlをdeployment.jsonに変換
- CodePipelineがStep Functionsをキック、inputにdeployment.json
Step Functionsの中の処理は以下
なんやかんやした後、デプロイの成功失敗を通知しているようです。
必要なツール
以下のツールをインストールします。
- AWS SAM CLI
- Python 3.8
- git-remote-codecommit (Optional)
もしこれだけで動かない場合は以下のREADMEに必要なツールが記載されているので全部いれてみてください。
はじめ方
公式のサンプルはバグというか想定している処理が抜けているようなので、私のGitHubアカウントにフォークして一部修正しました。このリポジトリを使っている前提で話を進めます。
サンプルを適当なディレクトリにクローンします。
git clone https://github.com/vtj-ttanaka/gitops-amazon-ecs-sample.git cd gitops-amazon-ecs-sample
GitOps環境をデプロイします。
sam build sam deploy --guided --capabilities CAPABILITY_NAMED_IAM
sam deploy
をすると色々聞かれますが、全てデフォルトで問題ありません。もし、変更したい部分があれば変更してください。Deploy this changeset? [y/N]:
っと聞かれた時だけy
を入力してください。それでデプロイが開始されます。
デプロイが完了すると色々なリソースが作成されています。CloudFormationのスタックに「sam-app」というのができていると思うので確認してみてください。1.の手順で「Stack Name」を変更していたらここの値は変わっています。
CloudFormationのOutputにCodeCommitの情報が出力されています。
このリポジトリをCloneします。私はgit-remote-codecommitを使用しているので、こちらのツールで進めます。
git clone codecommit::ap-northeast-1://config-repo-sam-app
空のリポジトリがCloneされるので、gitops-amazon-ecs-sample/deployment.yamlをリポジトリにコピーします。
cp /path/to/gitops-amazon-ecs-sample/deployment.yaml /path/to/config-repo-sam-app
deployment.yamlの中身はコンテナをスケジュールで実行するtasksと、コンテナをサービスとして実行するservicesに分かれています。tasksはオプショナルなので、登録してもしなくてもOKです。
tasks: - service: コンテナ名 cwRuleName: CloudWatch Events Rule名で、定期実行をするためのもの image: デプロイするコンテナイメージURL services: - service: コンテナ名 clusterName: ECSクラスター名 serviceName: ECSにデプロイされているサービス名 image: デプロイするコンテナイメージURL
こんな感じでdeployment.yamlを修正します。今回は簡単に以下のようにしました。
release: 4.0 services: - service: echoserver clusterName: vtj-poc serviceName: vtj-poc image: "gcr.io/google_containers/echoserver:1.3"
いざデプロイヤー
git add deployment.yaml git commit -am 'add deployment.yaml' git push origin main
deployment.yamlをリポジトリにコミットするとCodePipelineが動き出します。
CodeBuildが完了してStep Functionsまでいけたら中身を見てみましょう。
今どんな処理が行われているかWeb UIから図としてみることができます。
ECSの画面も見てみてください。リビジョンが変わっていると思います。
リビジョンをクリックして中身をみるとどんなコンテナがデプロイされたのかを確認できます。
Gitを起点にデプロイすることができました。
Step Functionsで何をやっているのか
Step Functionsの中の動作が少し複雑なので説明します。
sam deploy
をすると4つのLambdaがデプロイされます。
- InitConfigFunction
- DeployTaskFunction
- DeployEcsFunction
- ValidateDeployFunction
1 InitConfigではInitConfigFunctionを実行します。InitConfigFunctionはsrc/init/lambda.pyがデプロイされています。このLambdaはdeployment.yamlに指定されたtasksとservicesに実行権限の情報を追加します。45行目と48行目が該当の処理です。
2 Step FunctionsのChooseTaskOrServiceが実行され、tasksにサービスが指定されていればDeployTasksに飛びます。tasksが定義されていなければDeployTasksはスキップされ、DeployEcsに行きます。
1. DeployTasksはDeployTaskFunctionを実行します。DeployTaskFunctionはsrc/task/lambda.pyがデプロイされています。このLambdaはデプロイされているコンテナイメージとdeployment.yamlで指定されているコンテナイメージを比較して、コンテナイメージに変更があれば新しいタスク定義を登録しCloudWatch Event Ruleを書き換えて、
次のスケジュールで実行されるサービスを更新します。デプロイを行ったサービスはdeployment_needed
フラグが立てられます。
3 DeployEcsはDeployEcsFunctionを実行します。DeployEcsFunctionはsrc/deploy/lambda.pyがデプロイされています。このLambdaはデプロイされているコンテナイメージとdeployment.yamlで指定されているコンテナイメージを比較して、コンテナイメージに変更があれば新しいタスク定義を登録し新しいリビジョンを登録します。デプロイを行ったサービスはdeployment_needed
フラグが立てられます。
4 Step FunctionsのWaitForDeploymentが実行され、デプロイの完了を待ちます。ここは決め打ちで300秒待機させられます。
5 ValidateDeployはValidateDeployFunctionを実行します。ValidateDeployFunctionはsrc/validate/lambda.pyがデプロイされています。deployment_needed
フラグの立てられたサービスでタスクをカウントし、0だったらfailed、1以上であればsuccessの処理に流れていきます。
片付け
CodePipelineで使うためのArtifactバケットがそのままだと削除できません。CloudFormationの画面からバケット名と特定しバケットをからにしてください。
バケットを空にできたらいよいよリソースの削除です。
sam delete
すべての質問にy
で答えて削除は完了です。
まとめ
gitops-amazon-ecs-sampleはECSなどを起動しない代わりに、既存のECSクラスターでGitOpsを始められるようになっていました。まだECSでGitOpsできていない環境では試してみる価値はあるかもしれません。少し気になったのはデプロイの方法です。Blue/Greenやローリングアップデートではなく、コンテナが単純に置き換えられていくので、タイミング次第ではサービス断が発生するかもしれません。Blue/Greenデプロイの仕組みをいれようと思うとLambdaで自前実装する必要があり、この辺の仕組みを考え実装できるだけのスキルが必要になりそうです。