AWS CLIで Web サイトを構築、管理、運用する(22日目)

22 日目！

昨日の続編！
リポジトリ管理を始めたコンテンツをデプロイできるように設定をいれていきます。

# 22日目の要約
コンテンツをデプロイできるようにするよ！

# AWS CLI の準備
このあたりをみて、好きなバージョンとお使いのOSにあった環境設定をしてくださいね。
なんなら、 AWS CloudShell で実行するのも楽でよいと思います。
この記事シリーズは、AWS CloudShell で実行し、実行例を載せています。

バージョン１
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv1.html

バージョン２
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2.html

# 概要
CodeCommit を使ってコンテンツを管理するよ！

# さあ、やってみよう！
## CodePipeline サービスロールを作成する
Code Pipelineがパイプラインを動かすにあたって使用するサービスロールをまずは作成します。

```iam create-role``` コマンドを実行します

```sh
aws iam create-role --path /service-role/ --role-name  AWSCodePipelineServiceRole-ap-northeast-1-AdventCalendar2021 --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Service": "codepipeline.amazonaws.com"},"Action": "sts:AssumeRole"}]}'
```

ロール作成が完了すると、以下のような json が返ります。

```json
{
    "Role": {
        "Path": "/service-role/",
        "RoleName": "AWSCodePipelineServiceRole-ap-northeast-1-AdventCalendar2021",
        "RoleId": "AROA*****************",
        "Arn": "arn:aws:iam::************:role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-AdventCalendar2021",
        "CreateDate": "2021-12-21T15:47:04+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "codepipeline.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}
```

次にポリシーを作成します。
まずは、jsonファイルをこしらえます。

```json:policy-pipeline.json
{
    "Statement": [
        {
            "Action": [
                "iam:PassRole"
            ],
            "Resource": "*",
            "Effect": "Allow",
            "Condition": {
                "StringEqualsIfExists": {
                    "iam:PassedToService": [
                        "cloudformation.amazonaws.com",
                        "elasticbeanstalk.amazonaws.com",
                        "ec2.amazonaws.com",
                        "ecs-tasks.amazonaws.com"
                    ]
                }
            }
        },
        {
            "Action": [
                "codecommit:CancelUploadArchive",
                "codecommit:GetBranch",
                "codecommit:GetCommit",
                "codecommit:GetRepository",
                "codecommit:GetUploadArchiveStatus",
                "codecommit:UploadArchive"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "codedeploy:CreateDeployment",
                "codedeploy:GetApplication",
                "codedeploy:GetApplicationRevision",
                "codedeploy:GetDeployment",
                "codedeploy:GetDeploymentConfig",
                "codedeploy:RegisterApplicationRevision"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "codestar-connections:UseConnection"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "elasticbeanstalk:*",
                "ec2:*",
                "elasticloadbalancing:*",
                "autoscaling:*",
                "cloudwatch:*",
                "s3:*",
                "sns:*",
                "cloudformation:*",
                "rds:*",
                "sqs:*",
                "ecs:*"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "lambda:InvokeFunction",
                "lambda:ListFunctions"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "opsworks:CreateDeployment",
                "opsworks:DescribeApps",
                "opsworks:DescribeCommands",
                "opsworks:DescribeDeployments",
                "opsworks:DescribeInstances",
                "opsworks:DescribeStacks",
                "opsworks:UpdateApp",
                "opsworks:UpdateStack"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "cloudformation:CreateStack",
                "cloudformation:DeleteStack",
                "cloudformation:DescribeStacks",
                "cloudformation:UpdateStack",
                "cloudformation:CreateChangeSet",
                "cloudformation:DeleteChangeSet",
                "cloudformation:DescribeChangeSet",
                "cloudformation:ExecuteChangeSet",
                "cloudformation:SetStackPolicy",
                "cloudformation:ValidateTemplate"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "codebuild:BatchGetBuilds",
                "codebuild:StartBuild",
                "codebuild:BatchGetBuildBatches",
                "codebuild:StartBuildBatch"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Effect": "Allow",
            "Action": [
                "devicefarm:ListProjects",
                "devicefarm:ListDevicePools",
                "devicefarm:GetRun",
                "devicefarm:GetUpload",
                "devicefarm:CreateUpload",
                "devicefarm:ScheduleRun"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "servicecatalog:ListProvisioningArtifacts",
                "servicecatalog:CreateProvisioningArtifact",
                "servicecatalog:DescribeProvisioningArtifact",
                "servicecatalog:DeleteProvisioningArtifact",
                "servicecatalog:UpdateProduct"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "cloudformation:ValidateTemplate"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ecr:DescribeImages"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "states:DescribeExecution",
                "states:DescribeStateMachine",
                "states:StartExecution"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "appconfig:StartDeployment",
                "appconfig:StopDeployment",
                "appconfig:GetDeployment"
            ],
            "Resource": "*"
        }
    ],
    "Version": "2012-10-17"
}
```

json を使って IAM ポリシーを作成します。

```sh
aws iam create-policy --policy-name AWSCodePipelineServiceRole-ap-northeast-1-AdventCalendar2021 --path /service-role/ --policy-document file://policy-pipeline.json
```

ポリシー作成が成功すると、以下の json が返りますので、 Arn の値を確認します。

```json
{
    "Policy": {
        "PolicyName": "AWSCodePipelineServiceRole-ap-northeast-1-AdventCalendar2021",
        "PolicyId": "ANPA*****************",
        "Arn": "arn:aws:iam::************:policy/service-role/AWSCodePipelineServiceRole-ap-northeast-1-AdventCalendar2021",
        "Path": "/service-role/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2021-12-21T16:27:44+00:00",
        "UpdateDate": "2021-12-21T16:27:44+00:00"
    }
}
```

作成したロールとポリシーを関連づけてしまいます。

```sh
aws iam attach-role-policy --role-name AWSCodePipelineServiceRole-ap-northeast-1-AdventCalendar2021 --policy-arn arn:aws:iam::<AWS アカウントID>:policy/service-role/AWSCodePipelineServiceRole-ap-northeast-1-AdventCalendar2021
```

割り当てに成功しても特に応答はありません。

## パイプラインの元になる json を作成する
CodePipeline で作成するパイプラインの定義となる json を作成します。

```json:pipeline.json
{
    "pipeline": {
        "name": "AdventCalendar2021",
        "roleArn": "arn:aws:iam::<AWSアカウントID>:role/service-role/AWSCodePipelineServiceRole-ap-northeast-1-AdventCalendar2021",
        "artifactStore": {
            "type": "S3",
            "location": "codepipeline-ap-northeast-1-<12桁くらいの文字列>"
        },
        "stages": [
            {
                "name": "Source",
                "actions": [
                    {
                        "name": "Source",
                        "actionTypeId": {
                            "category": "Source",
                            "owner": "AWS",
                            "provider": "CodeCommit",
                            "version": "1"
                        },
                        "runOrder": 1,
                        "configuration": {
                            "BranchName": "master",
                            "OutputArtifactFormat": "CODE_ZIP",
                            "PollForSourceChanges": "false",
                            "RepositoryName": "AdventCalendar2021"
                        },
                        "outputArtifacts": [
                            {
                                "name": "SourceArtifact"
                            }
                        ],
                        "inputArtifacts": [],
                        "region": "ap-northeast-1",
                        "namespace": "SourceVariables"
                    }
                ]
            },
            {
                "name": "Deploy",
                "actions": [
                    {
                        "name": "Deploy",
                        "actionTypeId": {
                            "category": "Deploy",
                            "owner": "AWS",
                            "provider": "S3",
                            "version": "1"
                        },
                        "runOrder": 1,
                        "configuration": {
                            "BucketName": "CloudFrontのオリジンになっているS3バケット名",
                            "Extract": "true"
                        },
                        "outputArtifacts": [],
                        "inputArtifacts": [
                            {
                                "name": "SourceArtifact"
                            }
                        ],
                        "region": "ap-northeast-1",
                        "namespace": "DeployVariables"
                    }
                ]
            }
        ],
        "version": 1
    }
}
```

## パイプラインを作成する
```codepipeline create-pipeline``` コマンドでパイプラインを作成します。

```sh
aws codepipeline create-pipeline --cli-input-json file://pipeline.json
```

パイプラインの作成に成功すると json が返りますが長いので割愛します。

## 初回動作の状況を確認する
パイプラインが作成されると、CodeCommit からソースの取得を行い、S3 バケットへデプロイを行います。
この状況を確認します。

```sh
aws codepipeline get-pipeline-state --name AdventCalendar2021
```

取得に成功すると json が出力されます。
この json のうち、2つある、```latestExecution``` の中にある、```status``` の値が、両方とも **Succeeded** になっていればOKです。

## Amazon EventBridge の設定を行い、プッシュから自動でデプロイできるようにする
### EventBridge用の IAM ロールを作成する

リポジトリへのプッシュを契機に、EventBridge が CodePipeline を動作できるようにロールとポリシーを作成していきます。

```sh
aws iam create-role --role-name role-event-adcale --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Service": "events.amazonaws.com"},"Action": "sts:AssumeRole"}]}'
```

ロールが作成できると json が返ってきます。

次に、ポリシーを作成します。
まずは json ファイルを作成します。

```json:policy-event.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "codepipeline:StartPipelineExecution"
            ],
            "Resource": [
                "arn:aws:codepipeline:ap-northeast-1:<AWSアカウントID>:AdventCalendar2021"
            ]
        }
    ]
}
```

ポリシーをロールに割り当てます。

```sh
aws iam put-role-policy --role-name role-event-adcale --policy-name pol-policyevent --policy-document file://policy-event.json
```

特に応答はありません。

### EventBridge のルールを作成する
```events put-rule``` コマンドでルールを作成します。

```sh
aws events put-rule --name "MyCodeCommitRepoRule" --event-pattern "{\"source\":[\"aws.codecommit\"],\"detail-type\":[\"CodeCommit Repository State Change\"],\"resources\":[\"arn:aws:codecommit:ap-northeast-1:<AWS アカウントID>:AdventCalendar2021\"],\"detail\":{\"referenceType\":[\"branch\"],\"referenceName\":[\"master\"]}}" --role-arn "arn:aws:iam::<AWS アカウントID>:role/role-event-adcale"
```

### CorePipeline をイベントのターゲットに指定する
最後に、イベントのターゲットとして指定します。

```sh
aws events put-targets --rule MyCodeCommitRepoRule --targets Id=1,Arn=arn:aws:codepipeline:ap-northeast-1:<AWS アカウントID>:AdventCalendar2021,RoleArn="arn:aws:iam::<AWS アカウントID>:role/role-event-adcale"
```

## 動作確認
まずは、現状の コンテンツを確認します。

```sh
curl https://<ドメイン名>
```

```HTML
<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Advent calendar 2021</title>
</head>
<body>
  <h1>Hello world!!</h1>
  <h2>Advent calendar 2021 DAY 21</h2>
</body>
</html>
```

確認ができたら、ローカルリポジトリのディレクトリに移動します。

```sh
cd <ローカルリポジトリのディレクトリ>
```

リモートリポジトリと同期させます。

```sh
git pull origin master
```

```index.html``` ファイルを編集します。

```HTML:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Advent calendar 2021</title>
</head>
<body>
  <h1>Hello world!!</h1>
  <h2>Advent calendar 2021 DAY 22</h2>
  <h2>Content is managed by CodeCommit!!</h2>
  <h2>Deployed by CodePipeline!!</h2>
</body>
</html>
```



編集が完了したら、コミットし、リモートリポジトリへプッシュします。

```sh
git add index.html
git commit -m "Day 22"
[master 5******] Day 22
 1 file changed, 2 insertions(+), 1 deletion(-)

git push origin master

Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 2 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 327 bytes | 327.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/AdventCalendar2021
   a******..5******  master -> master
```

プッシュした後、```codepipeline get-pipeline-state``` コマンドで状態を確認します。

２つある ```actionStates``` 内の ```latestExecution``` 以下の情報を確認します。

* status: Suceeded になっているか
* summary: コミット時に設定したメッセージが割り当てられているか
* lastStatusChange: プッシュした時刻(UTC表記)に近い値か

問題がなさそうであれば、改めて、 curl コマンドでデプロイしたコンテンツが表示されるか確認します。

```sh
curl https://<ドメイン名>/
```

```HTML
<!DOCTYPE html>
<html lang="ja">
<head>
  <title>Advent calendar 2021</title>
</head>
<body>
  <h1>Hello world!!</h1>
  <h2>Advent calendar 2021 DAY 22</h2>
  <h2>Content is managed by CodeCommit!!</h2>
  <h2>Deployed by CodePipeline!!</h2>
</body>
</html>
```

問題なさそうですね。よかった。

# まとめ
AWS CLI だけで CI/CD の設定までできてしまいました。

残り3日！
明日、明後日は API Gateway + Lambda を AWS CLI だけで設定、構築していく予定です。