AWSのCode3兄弟とAnsible+PackerでAMI作成までを自動化する
AWSのCI/CDサービス(CodePipeline/Deploy/Build)と構成管理ツールのAnsible + Packerを利用してEC2インスタンスのイメージを自動構築する。 コードはGitで管理するが今回はバージョニングを有効にしたS3に保存し、そのイベントをフックにインスタンスのイメージを自動作成する。
- 環境
- 今回やること ( 全体のフロー )
- CodePipelineの設定
- CodeDeployの設定
- CodeBuildの設定
- S3の設定
- appsepc.ymlの準備 ( CodeDeploy )
- buildspec.yml ( CodeBuild )
- Packerの実行のために
- まとめ
環境
- AWS
- 構成管理ツール
- Ansible
- Packer (CodeBuildのコンテナイメージ )
今回やること ( 全体のフロー )
- DeveloperがインフラコードをS3にputして、EC2インスタンスのAMIが作成されるまでの流れです。
CodePipelineの設定
CodePipelineのソースには、GitHubやCodeCommitやS3など指定可能です。 今回はS3をソースに指定した。CodePipelineはS3から指定したソースからコードを取得する。 パイプラインの設定値は以下の通り。
項目 | 設定値 |
---|---|
アクションカテゴリー | ソース |
ソースプロバイダ | Amazon S3 |
Amazon S3 の場所(.zipまで指定) | s3://name-of-bucket/free-name.zip |
出力アーティファクト | ( 任意の値 ) SourceArtifact |
- 今回作成したパイプラインの全体
- パイプラインの編集画面
CodeDeployの設定
インフラCDとしてCodeDeployを利用する。今回は、S3に保存したコード(AnsibleやPackerやジョブ定義したymlファイルなど)を指定したEC2インスタンスへデリバリーする。 そして、デリバリーしたコードを appspec.yml
に定義した内容をcodedeploy-agentが実行する。今回はappsepc.ymlにAnsibleを実行するように定義した。appspec.ymlの内容は下記に記載している。
デリバリー先のEC2インスタンスにcodedeploy-agentをインストールし、起動させておく必要がある。
項目 | 設定値 |
---|---|
アクションカテゴリ | デプロイ |
デプロイプロバイダ | AWS CodeDeply |
アプリケーション名 | ( 任意の値 ) deploy-dev |
デプロイグループ | bastion |
出力アーティファクト | (CodePipelineと合わせる) SourceArtifact |
アプリケーション(デプロイグループ)作成について
項目 | 設定値 |
---|---|
アプリケーション名 | 任意の値 |
デプロイグループ名 | bastion |
デプロイタイプ | インプレースデプロイ |
環境設定 | Amazon EC2インスタンスを選択 |
タググループ | キー: Name, 値: デプロイするインスタンス名 |
デプロイ設定 | CodeDeployDefault.AllAtOnce |
サービスロール | CodeDeployで必要な権限ロールを付与 |
CodeBuildの設定
インフラCIとしてCodeBuildを利用する。CodeBuildは任意のコンテナイメージを指定し、そのコンテナ環境で処理を実行できる。
今回は、EC2インスタンスのイメージを作成するために、HashiCorpのPackerのイメージを指定した。buildspec.yml
の定義は以下に記載している。
項目 | 設定値 |
---|---|
プロジェクト名 | 任意の値 |
ソースビルドの対象 | 今回はS3が指定されている |
サービスロール | Buildに必要な権限ロールを作成&選択する |
VPC | Packerでsshするため、VPCとサブネット、SecurityGroupを選択する |
セキュリティグループ等を間違えるとPacker作成インスタンスにsshできなくて失敗する。 最期にVPCの検証を実行して、インターネットに疎通があるかことを確認する。
環境ビルド方法
- 環境イメージ: Dockerイメージの指定
- 環境タイプ: Linux
- カスタムイメージタイプ: その他
- カスタムイメージID:
hashicorp/packer:1.3.2
暗号化キー
- ssmパラメータで暗号化している場合に必要
- KMSを発行する
S3の設定
- S3のバージョニングを有効にする
appsepc.ymlの準備 ( CodeDeploy )
- CodeDeployでは、appsepc.ymlに定義した内容が実行される。
デプロイの処理フローは以下の通り
フックの環境変数の可用性
環境変数名 | 説明 |
---|---|
APPLICATION_NAME | 現在のデプロイの一部である AWS CodeDeploy のアプリケーションの名前 |
DEPLOYMENT_ID | ID AWS CodeDeploy が、現在のデプロイに割り当てられています |
DEPLOYMENT_GROUP_NAME | 現在のデプロイの一部である AWS CodeDeploy のデプロイグループの名前 |
DEPLOYMENT_GROUP_ID | 現在のデプロイの一部である AWS CodeDeploy のデプロイグループの ID |
LIFECYCLE_EVENT | 現在のデプロイライフサイクルイベントの名前 |
- 今回実施のディレクトリ構造
├── codedeploy-script │ ├── afterinstall.sh │ └── beforeinstall.sh └── packer └── web.json
BeforeInstall
のフェーズでbeforeinstall.shを実行する- timeoutとrunasで実行ユーザを指定する
AfterInstall
のフェーズでafterinstall.shを実行する- timeoutとrunasで実行ユーザを指定する
$ cat appspec.yml
version: 0.0 os: linux files: - source: / destination: /opt/src/yhidetoshi-infra/ pattern: "**" owner: ubuntu group: ubuntu hooks: BeforeInstall: - location: codedeploy-script/beforeinstall.sh timeout: 60 runas: root AfterInstall: - location: codedeploy-script/afterinstall.sh timeout: 900 runas: ubuntu
$ cat beforeinstall.sh
#!/bin/bash find /opt/src/yhidetoshi-infra/ -type f ! -name ".vault" | xargs rm -f
$ cat afterinstall.sh
#! /bin/bash if [[ "${DEPLOYMENT_GROUP_NAME}" == "base-dev" ]]; then ENV="dev" fi cd /opt/src/yhidetoshi-infra/ansible ansible-playbook -i inventory/${ENV} bastion.yml -D -C ansible-playbook -i inventory/${ENV} bastion.yml -D
$ cat roles/ssm/tasks/main.yml
のようにssmパラメータに必要なパラメータを登録する
buildspec.yml ( CodeBuild )
$ cat buildspec.yml
version: 0.2 env: parameter-store: VAULT_PASSWORD: "ansible_vault_password" INSTANCE_TYPE: "web_instance_type" IAM_ROLE: "web_role_profile_arn" WEB_SGID: "web_sgid" PACKER_SUBNETID: "web_subnet_id" SOURCE_AMI: "web_base_ami" AWS_ENV: "env" phases: install: commands: - apk add --no-cache python3 && python3 -m ensurepip - pip3 install boto3 pre_build: commands: - echo "Do Nothing" build: commands: - echo "DO BUILD" - echo ${VAULT_PASSWORD} > ./ansible/.vault - cd ./packer && packer build -machine-readable web.json - echo "DONE BUILD" post_build: commands: - echo "Do Nothing"
Packerの実行のために
- buildspec.ymlの以下のコードでpackerを実行し、Ansibleでプロビジョニングし、AMIを作成する。
Packerのドキュメントを参考にPackerを定義する
Packerの動作
EC2インスタンスを指定したSubnet/SecurityGroupで起動する
- EC2へSSH接続する
- Packerで定義した処理を実行する
- 起動したEC2インスタンスを停止する
- 停止したEC2インスタンスのAMIを作成する
- 停止したEC2インスタンスを削除する
→ CodeBuildの属するNW設定とPackerで指定したNW設定がずれているとSSH接続できずに失敗する
commands: - cd ./packer && packer build -machine-readable web.json
- 以下のように
| tee output.log
を出力させて、ログ内容に応じて、追加で処理をさせることもできる。- AMIのイメージIDやAMI焼き完了のタイミングなど
commands: - cd ./packer && packer build -machine-readable web.json | tee output.log
上記コマンドでpackerを実行したあと、下記のコードでPacker内でAnsibleをまわしその結果をAMI焼きする
- このコードは以下から抜粋した部分
{ "type": "ansible-local", "playbook_file": "../ansible/web.yml", "playbook_dir": "../ansible", "inventory_groups": [ "env_{{user `aws_env`}}" ], "group_vars": "../ansible/group_vars", "command": "PYTHONUNBUFFERED=1 ansible-playbook -D" }
"playbook_file"
の指定で$ cat ansible/web.yml
を実行する。
- hosts: role_web user: ubuntu become_method: sudo gather_facts: yes roles: - nginx
- Ansibleのディレクトリ構成
. ├── ansible │ ├── ansible.cfg │ ├── bastion.yml │ ├── group_vars │ │ ├── all │ │ │ └── main.yml │ │ ├── env_dev │ │ │ ├── main.yml │ │ │ └── vault.yml │ │ ├── env_stg │ │ │ └── main.yml │ │ ├── role_bastion │ │ └── role_web │ ├── inventory │ │ ├── dev │ │ └── stg │ ├── roles │ │ ├── bastion │ │ │ └── tasks │ │ │ └── main.yml │ │ ├── codedeploy │ │ │ ├── handlers │ │ │ │ └── main.yml │ │ │ └── tasks │ │ │ └── main.yml │ │ ├── nginx │ │ │ ├── handlers │ │ │ │ └── main.yml │ │ │ └── tasks │ │ │ └── main.yml │ │ ├── ruby │ │ │ └── tasks │ │ │ └── main.yml │ │ ├── ssm │ │ │ └── tasks │ │ │ └── main.yml │ │ └── verifi │ │ └── tasks │ │ └── main.yml │ └── web.yml ├── appspec.yml ├── buildspec.yml ├── codedeploy-script │ ├── afterinstall.sh │ └── beforeinstall.sh └── packer └── web.json
web.json
{ "variables": { "instance_type": "{{env `INSTANCE_TYPE`}}", "packer_subnetid": "{{env `PACKER_SUBNETID`}}", "web_sgid": "{{env `WEB_SGID`}}", "source_ami": "{{env `SOURCE_AMI`}}", "aws_env": "{{env `AWS_ENV`}}", "pass": "{{env `VAULT_PASSWORD`}}" }, "builders": [{ "type": "amazon-ebs", "region": "ap-northeast-1", "source_ami": "{{user `source_ami`}}", "instance_type": "{{user `instance_type`}}", "enable_t2_unlimited": false, "ssh_username": "ubuntu", "ssh_timeout": "5m", "subnet_id": "{{user `packer_subnetid`}}", "security_group_ids": [ "{{user `web_sgid`}}" ], "iam_instance_profile": "web", "ssh_interface": "private_ip", "ami_name": "web_{{isotime | clean_ami_name}}", "launch_block_device_mappings": [{ "device_name": "/dev/sda1", "volume_size": 10, "volume_type": "gp2", "delete_on_termination": true }] }], "provisioners": [{ "type": "shell", "execute_command": "echo '{{user `pass`}}' | sudo -S sh -c '{{ .Vars }} {{ .Path }}'", "inline": [ "sudo apt-add-repository ppa:ansible/ansible", "sudo apt-get update", "sudo apt-get -y install ansible" ] },{ "type": "ansible-local", "playbook_file": "../ansible/web.yml", "playbook_dir": "../ansible", "inventory_groups": [ "env_{{user `aws_env`}}" ], "group_vars": "../ansible/group_vars", "command": "PYTHONUNBUFFERED=1 ansible-playbook -D" }] }
まとめ
インフラCI/CDでソースコードからEC2インスタンスのイメージを作成するまでの処理について記述しました。 利用したものは、CodePipeline/CodeDeploy/CodeBuild/Ansible/Packerです。 AWSCode3兄弟を使って、コンテナイメージを作成するのもやっていきたいと思います。