My Note

自己理解のためのブログ

AWSのCode3兄弟とAnsible+PackerでAMI作成までを自動化する

AWSのCI/CDサービス(CodePipeline/Deploy/Build)と構成管理ツールのAnsible + Packerを利用してEC2インスタンスのイメージを自動構築する。 コードはGitで管理するが今回はバージョニングを有効にしたS3に保存し、そのイベントをフックにインスタンスのイメージを自動作成する。

環境

  • AWS
    • CodePipeline
    • CodeDeploy
    • CodeBuild
    • SSM
    • S3
    • EC2 ( Ubuntu )
    • NAT-GW
    • VPC
  • 構成管理ツール
    • Ansible
    • Packer (CodeBuildのコンテナイメージ )

今回やること ( 全体のフロー )

  • DeveloperがインフラコードをS3にputして、EC2インスタンスのAMIが作成されるまでの流れです。

f:id:yhidetoshi:20190617115109p:plain
flow

CodePipelineの設定

CodePipelineのソースには、GitHubやCodeCommitやS3など指定可能です。 今回はS3をソースに指定した。CodePipelineはS3から指定したソースからコードを取得する。 パイプラインの設定値は以下の通り。

項目 設定値
アクションカテゴリー ソース
ソースプロバイダ Amazon S3
Amazon S3 の場所(.zipまで指定) s3://name-of-bucket/free-name.zip
出力アーティファクト ( 任意の値 ) SourceArtifact
  • 今回作成したパイプラインの全体

f:id:yhidetoshi:20190617115139p:plain

  • パイプラインの編集画面 f:id:yhidetoshi:20190617115202p:plain

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に定義した内容が実行される。

デプロイの処理フローは以下の通り

f:id:yhidetoshi:20190617115358p:plain

[参照] https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html

フックの環境変数の可用性

環境変数 説明
APPLICATION_NAME 現在のデプロイの一部である AWS CodeDeploy のアプリケーションの名前
DEPLOYMENT_ID ID AWS CodeDeploy が、現在のデプロイに割り当てられています
DEPLOYMENT_GROUP_NAME 現在のデプロイの一部である AWS CodeDeploy のデプロイグループの名前
DEPLOYMENT_GROUP_ID 現在のデプロイの一部である AWS CodeDeploy のデプロイグループの ID
LIFECYCLE_EVENT 現在のデプロイライフサイクルイベントの名前

[参照] https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html

├── 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

のように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の実行のために

→ 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.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兄弟を使って、コンテナイメージを作成するのもやっていきたいと思います。