前回でイメージ作成のためのコードができましたが、ビルドとかアップロードを手作業でやる時代ではないので、自動化してしまいます。
自動処理を実行している間は、変に別の仕事しようとしないで、インターネットを徘徊したり、オヤツ食べたりトイレに行けばいいんです。安定なシステムは、平穏な心が生み出すのです。そういうものです。
目次
(0) はじめに(1) Docker
(2) イメージ自動生成 ← イマココ!
(3) サービス起動
(4) デプロイ
(5) Auto Scaling
(6) 費用と性能
CodeCommitでコード管理
組織ごとにコード管理を何でやっているかは様々でしょうが、AWSでのイメージ管理程度ならば Code なんちゃらを使ってしまうのがベターです。まずはイメージ作成用コードを CodeCommit で管理する場を整えます。進行図
まだまだ序盤なり。Terraform で CodeCommit 作成
ここから Terraform でのリソース作成が出現しますが、コードは私の手元のそのままコピペするので、local変数やWorkspace多用のため、そのままは動きません。参考程度に各自改変してください。まずは1つめのイメージ base から。
1 2 3 4 5 6 |
resource "aws_codecommit_repository" "base" { count = "${local.on_system ? 1 : 0}" repository_name = "${local.service_name}-image-base" description = "creating base image for ${local.service_name}" } |
IAM UserによるSSH接続設定
CodeCommitレポジトリにアクセスするためには、いくつかの方法が下記に示されているのですが、ここではLinuxからSSH接続する方法で準備します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# 開発者が利用する IAM User を1つ用意します # AWSCodeCommitFullAccess を含む様々なポリシーを持つでしょうから # Terraform で提示したりはしません # SSH公開鍵を用意します # 普段使っているものを使いたくないとか、形式が古い場合は生成します # 公開鍵は .pub の1行の方です。秘密鍵は複数行のやつでバラまいたら死です ssh-keygen # IAM User の管理画面を開き、利用するユーザーにて # 「AWS CodeCommit の SSH キー」から公開鍵を登録します # SSH Key ID (アクセスキーID) をコピーしておきます # 開発機でSSH設定ファイルを編集します # User に SSH Key ID を、 # IdentityFile に秘密鍵のファイルパスを指定します vim ~/.ssh/config #-----ここから----- Host git-codecommit.*.amazonaws.com User APKAEIBAERJR2EXAMPLE IdentityFile ~/.ssh/id_rsa Port 22 #-----ここまで----- # SSHクライアントのお約束でアクセス権限を整え chmod 600 ~/.ssh/config # CodeCommit 管理画面 -> リポジトリ からSSHのURLをコピーしてきて # clone できれば大勝利です mkdir -p ~/codecommit cd ~/codecommit git clone ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/test-image-base |
コードをアップロード
作業スペースができたので、早速Dockerコードをアップロードします。前回取得済みのベース用コードをガバっともってきて、デフォルトブランチとして置いておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 一通り cp します。.git ディレクトリはもってきません cd ~/codecommit/test-image-base cp -r ~/docker/docker-ecr-base/* ./ # 最初は何もブランチがないので master としてデフォルトブランチとします git checkout -b master git add . git commit git push origin master # 管理画面でデフォルトブランチとファイル群でも確認しておきます # 以後の更新では、ブランチきって、プルリク、マージ という流れで自動処理を発動します |
ECRリポジトリ作成
前回はCLIでECRリポジトリを作成して遊びましたが、terraform で作成します。
1 2 3 4 5 |
resource "aws_ecr_repository" "base" { count = "${local.on_system ? 1 : 0}" name = "${local.service_name}-base" } |
進行図
インターネッツがもったいないので省略。CodeBuild でイメージ作成とアップロード
ここでようやく、コードに入っていた buildspec.yml が活躍します。CodeBuildは、指定したソースコードに入っている buildspec.yml の内容を実行するサービスです。今回は CodeCommit ですが、GitHub なども利用可能です。CodeCommitのDocker用コードを取得し、buildspec.yml に従ってイメージをビルドし、ECRにアップロードする、までを CodeBuild に任せます。進行図
ようやくシステマチックになってきました。Role作成
まずは、CodeBuild がいろいろ作業するために必要な Role を作成しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
resource "aws_iam_role" "codebuild" { count = "${local.on_common ? 1 : 0}" name = "CodeBuildServiceRole" assume_role_policy = <<eof { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "codebuild.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } eof } resource "aws_iam_role_policy_attachment" "codebuild" { count = "${local.on_common ? 1 : 0}" role = "${aws_iam_role.codebuild.name}" policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser" } resource "aws_iam_role_policy" "codebuild" { count = "${local.on_common ? 1 : 0}" name = "CodeBuildServiceRolePolicy" role = "${aws_iam_role.codebuild.id}" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "codecommit:GitPull", "ec2:Describe*", "ec2:CreateNetworkInterface", "ec2:CreateNetworkInterfacePermission", "ec2:DeleteNetworkInterface", "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": [ "*" ] }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:GetObjectVersion", "s3:PutObject" ], "Resource": [ "*" ] } ] } EOF } |
長いですが、たいした内容ではないです。ECR周りはポリシーがあるのでアタッチしていますが、それ以外はちょうどよいのがないのでバラで指定しています。なんとなく、そのへんを使うんだーって眺めてもらえればよいです。
CloudWatch Logs
CodeBuild が実行した際の出力内容は Logs に吐き出されます。明示的に作らなければ勝手にロググループを作ってくれますが、それだと保持期限が無期限になってしまうので、自分で作成します。
1 2 3 4 5 6 |
resource "aws_cloudwatch_log_group" "codebuild-base" { count = "${local.on_system ? 1 : 0}" name = "/aws/codebuild/${aws_codecommit_repository.base.repository_name}" retention_in_days = 7 } |
CodeBuild 作成
複雑なサービスではどれもそうなんですが、管理画面でポチポチして作成すると、具体的にどんなリソースが勝手に作成されて、どんなリソースが関わってて、どんなオプションがあるのかが非常にわかりづらいと感じます。それでとりあえず動くのを楽しむだけならいいのですが、安心安全に運用するとなれば話は別です。Terraform で作成すると、関わるリソースの把握がしやすく、Terraformのドキュメントを見ると設定項目がほぼ全て一覧で見れるので、下手にAWSドキュメントを文章で読むよりも理解が早かったりします。とくに、この Code なんちゃら系はお互いが絡むので Terraform での理解がおすすめです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
resource "aws_codebuild_project" "base" { count = "${local.on_system ? 1 : 0}" name = "${aws_codecommit_repository.base.repository_name}" description = "Building image for base" service_role = "${data.terraform_remote_state.common.aws_iam_role_codebuild_arn}" build_timeout = "10" artifacts { type = "NO_ARTIFACTS" } # If terraform can use LOCAL, change it. cache { type = "NO_CACHE" } environment { compute_type = "BUILD_GENERAL1_SMALL" image = "aws/codebuild/docker:18.09.0" type = "LINUX_CONTAINER" privileged_mode = true environment_variable { "name" = "AWS_DEFAULT_REGION" "value" = "${local.region}" } environment_variable { "name" = "AWS_ACCOUNT_ID" "value" = "${local.service_account_id}" } environment_variable { "name" = "IMAGE_REPO_NAME" "value" = "${aws_ecr_repository.base.name}" } environment_variable { "name" = "IMAGE_TAG" "value" = "latest" } } source { type = "CODECOMMIT" location = "https://git-codecommit.${local.region}.amazonaws.com/v1/repos/${aws_codecommit_repository.base.repository_name}" git_clone_depth = 1 } tags = { service = "${local.service_name}" } } |
source で CodeCommit を指定し、
compute_type と image で処理をさせるイメージと性能を指定し、
environment_variable で buildspec.yml に渡す環境変数を列挙しています。
vpc_config でVPC内で処理できたりもしますが、その場合は Lambda と同じで NAT G/W が必要になったりするので、不要なら書かないでよいです。buildspec.yml の位置を変えたりもできますが、まぁ不要でしょう。
buildspec.yml を見てみる
難しいことは特になく、ビルドのフェーズごとにスクリプトを記述するだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
version: 0.2 phases: install: commands: - echo `date` Starting install phase... - echo `date` Finished install phase... pre_build: commands: - echo `date` Starting pre_build phase... - echo Logging in to Amazon ECR... - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION) - echo `date` Finished pre_build phase... build: commands: - echo `date` Starting build phase... - echo Building the Docker image... - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG - echo `date` Finished build phase... post_build: commands: - echo `date` Starting post_build phase... - echo Pushing the Docker image... - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG - echo `date` Finished post_build phase... artifacts: files: artifacts.json |
前回 Docker で遊んだ最後の内容をやってもらっているだけです。
Docker login し、
current にある Dockerfile で build して tag 付け、
ECR に push して終了です。
↑ではStartとFinishで日付を出力していますが、CloudWatch Logs でタイムスタンプがでるので実は不要だったりします。気分です気分。
CodeBuildを実行してみる
CodeBuild 管理画面で、「Start build」をポチって動かしてみます。一発で成功するほど甘いものではない気がしますが、Build history を見て失敗したら処理内容を確認し、原因を解決していきます。成功したら、ECR のリポジトリを覗いてみましょう。latest の更新時間が最新となっているはずです。
CodePipeline で完全自動化
CodeBuild で手動でポチッとするのも面倒くさい時代です。CodeCommit にて master にマージされた時点で、勝手に CodeBuild が動けば、なお幸せになれるはずです。CodeCommit をトリガーとして、CodeBuild を発火する CodePipeline を作成します。進行図
自動化は心が洗われるのです・・・S3 を作成
今回はそんなに気にする必要はないのですが、仕組み的に入出力アーティファクトのためにS3が必要になるのでバケットを作ります。気になる人はCodePipeline について軽く見ておくとよいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
resource "aws_s3_bucket" "code-artifact" { count = "${local.on_system ? 1 : 0}" bucket = "${local.s3_bucket_code_artifact}" lifecycle_rule { enabled = true id = "${aws_codecommit_repository.base.repository_name}" prefix = "${aws_codecommit_repository.base.repository_name}/" transition { days = 30 storage_class = "STANDARD_IA" } transition { days = 60 storage_class = "GLACIER" } expiration { days = 90 } } tags { service = "${local.service_name}" } } |
IAM Role を作成
Pipelineさんが作業するために必要な権限を用意します。これといったポリシーが用意されていなく、ドキュメントから引っ張ってきた内容と、自前で追加したもので管理しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
resource "aws_iam_role" "codepipeline" { count = "${local.on_common ? 1 : 0}" name = "CodePipelineServiceRole" assume_role_policy = <<eof { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "codepipeline.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } eof } resource "aws_iam_role_policy" "codepipeline-default" { count = "${local.on_common ? 1 : 0}" name = "CodePipelineServiceRoleDefaultPolicy" role = "${aws_iam_role.codepipeline.id}" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject", "s3:GetObjectVersion", "s3:GetBucketVersioning" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "s3:PutObject" ], "Resource": [ "arn:aws:s3:::codepipeline*", "arn:aws:s3:::elasticbeanstalk*" ], "Effect": "Allow" }, { "Action": [ "codedeploy:CreateDeployment", "codedeploy:GetApplicationRevision", "codedeploy:GetDeployment", "codedeploy:GetDeploymentConfig", "codedeploy:RegisterApplicationRevision" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "elasticbeanstalk:CreateApplicationVersion", "elasticbeanstalk:DescribeApplicationVersions", "elasticbeanstalk:DescribeEnvironments", "elasticbeanstalk:DescribeEvents", "elasticbeanstalk:UpdateEnvironment", "autoscaling:DescribeAutoScalingGroups", "autoscaling:DescribeLaunchConfigurations", "autoscaling:DescribeScalingActivities", "autoscaling:ResumeProcesses", "autoscaling:SuspendProcesses", "cloudformation:GetTemplate", "cloudformation:DescribeStackResource", "cloudformation:DescribeStackResources", "cloudformation:DescribeStackEvents", "cloudformation:DescribeStacks", "cloudformation:UpdateStack", "ec2:DescribeInstances", "ec2:DescribeImages", "ec2:DescribeAddresses", "ec2:DescribeSubnets", "ec2:DescribeVpcs", "ec2:DescribeSecurityGroups", "ec2:DescribeKeyPairs", "elasticloadbalancing:DescribeLoadBalancers", "rds:DescribeDBInstances", "rds:DescribeOrderableDBInstanceOptions", "sns:ListSubscriptionsByTopic" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "lambda:invokefunction", "lambda:listfunctions" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "s3:ListBucket", "s3:GetBucketPolicy", "s3:GetObjectAcl", "s3:PutObjectAcl", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::elasticbeanstalk*", "Effect": "Allow" } ] } EOF } resource "aws_iam_role_policy" "codepipeline-custom" { count = "${local.on_common ? 1 : 0}" name = "CodePipelineServiceRoleCustomPolicy" role = "${aws_iam_role.codepipeline.id}" policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:*" ], "Resource": [ "arn:aws:s3:::${local.s3_bucket_code_artifact}/", "arn:aws:s3:::${local.s3_bucket_code_artifact}/*" ], "Effect": "Allow" }, { "Action": [ "codecommit:GetBranch", "codecommit:GetCommit", "codecommit:UploadArchive", "codecommit:GetUploadArchiveStatus", "codecommit:CancelUploadArchive" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "codebuild:BatchGetBuilds", "codebuild:StartBuild" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "ecr:DescribeImages" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "ecs:DescribeServices", "ecs:DescribeTaskDefinition", "ecs:DescribeTasks", "ecs:ListTasks", "ecs:RegisterTaskDefinition", "ecs:UpdateService" ], "Resource": "*", "Effect": "Allow" } ] } EOF } |
CodePipeline を作成
S3とRoleを使って、CodeCommit の masterブランチを発火元として、CodeBuild をぶっ放す Pipeline を作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
resource "aws_codepipeline" "base" { count = "${local.on_system ? 1 : 0}" name = "${aws_codecommit_repository.base.repository_name}" role_arn = "${data.terraform_remote_state.common.aws_iam_role_codepipeline_arn}" artifact_store { type = "S3" location = "${aws_s3_bucket.code-artifact.bucket}" } stage { name = "Source" action { name = "Source" category = "Source" owner = "AWS" provider = "CodeCommit" version = "1" output_artifacts = ["source"] configuration = { RepositoryName = "${aws_codecommit_repository.base.repository_name}" BranchName = "master" } } } stage { name = "Build" action { name = "Build" category = "Build" owner = "AWS" provider = "CodeBuild" version = "1" input_artifacts = ["source"] configuration = { ProjectName = "${aws_codebuild_project.base.name}" } } } } |
動作確認
手元のコードを編集し、CodeCommit に送り、master にプルリクしてマージ、まで行うと十数秒くらいで自動的にPipelineが動き始めるので、最後まで成功することを確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# コード置き場へ cd ~/codecommit/test-image-base # 適当にブランチきって、適当な編集して、push まで git checkout -b test vim nginx/index.html git add . git commit git push origin test # CodeCommit管理画面でプルリクエストを作成します # master < test へ # master へマージまで完了させます # CodePipeline管理画面を開きます # 該当のパイプラインの中をみていると、Source が動き出します # 成功したら Build も動き出します # Build が完了したら、ECR のイメージが更新されています # 手元のお掃除でもしておきます git checkout master git branch -D test git pull |
アプリケーション用のイメージも自動化する
ここまでで1つ目のベースとなるイメージ作成の自動化ができました。次は、アプリケーション用のイメージ作成も自動化しておきます。が、内容はほぼ同じなので、省略して要点だけ記述しておきます。まず作成するリソースは、IAM や S3 など共通するものを除いた、base と書かれたTerraformコードをコピペして application 用を作成します。git grep base でだいたい対象がわかります。
あとは、Dockerコードの FROM は、ECR用に変更する必要があります。
1 2 3 4 5 |
# 一行目のこれを FROM alpine-base:latest # ECRのそれに変更します FROM 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-base:latest |
あとは master へマージまで実行して動作確認するのは同じです。
進行図
イメージのコード化、イメージ生成の自動化、でそれっぽい風味がしてきましたが、まだ先は長い……
次へまいります:サービス起動