LoginSignup
18
10

More than 3 years have passed since last update.

CodeBuildとAmplify CLIでCI/CD

Last updated at Posted at 2019-12-14

はじめに

この記事はAWS Amplify Advent Calendar 2019の15日目です。

AWS Amplify、便利ですよね。特にAmplify Consoleのデプロイ機能は超便利ですよね!!
ですが様々な事情によりAmplify Consoleが使えないこともあります。例えばソース管理にGithub Enterpriseを使っている場合とか、Basic認証じゃ事足らなくてWAFがほしい場合とか。

でもAmplifyのCLIでCI/CDするのって結構しんどいんですよ。とはいえいまさら手動デプロイもイヤですよね。
CI/CDができないからってAmplify自体を諦めてしまうのはもっともったいないです。

というわけでこの記事では、ReactとAmplify CLIで構築したWebをCodeBuild(とその他諸々)を使ってCI/CDを構築する方法を紹介します。

CloudFormationでCode3兄弟を作る

とりあえずCD(Continuous Delivery)から始めていきます。
CodeCommit, CodeBuild, CodePipelineをCloudFormationで構築します。
CodeBuildServiceRoleCodePipelineServiceRoleという名のIAMロールを、以下を参考にして作成しておきます。
(すでにお使いのロールがあれば不要です)

IAMロールができたらCloudFormationのテンプレートを作っていきましょう。

CodeBuildCodePipeline.yml
Parameters:
  ProjectName:
    Type: String
  AccountID:
    Type: String
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  S3Artifact:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: !Sub ${ProductName}-artifact
  CodeCommit:
    Type: "AWS::CodeCommit::Repository"
    Properties:
      RepositoryName: !Sub ${ProductName}
  CodeBuild:
    Type: "AWS::CodeBuild::Project"
    Properties:
      Name: !Sub ${ProductName}
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        ComputeType: "BUILD_GENERAL1_SMALL"
        Image: "aws/codebuild/standard:2.0"
        Type: "LINUX_CONTAINER"
        EnvironmentVariables:
          - Name: ENV
            Type: PLAINTEXT
            Value: dev
      Source:
        Type: "CODEPIPELINE"
      ServiceRole: !Sub arn:aws:iam::${AccountID}:role/CodeBuildServiceRole
    DependsOn:
      - CodeCommit
  CodePipeLine:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: !Sub ${ProductName}
      RoleArn: !Sub arn:aws:iam::${AccountID}:role/CodePipelineServiceRole
      ArtifactStore:
        Location: !Sub ${ProductName}-artifact
        Type: S3
      Stages:
        - Name: Source
          Actions:
            - Name: "CodeCommit"
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: 1
              OutputArtifacts:
                - Name: SourceOutput
              Configuration:
                RepositoryName: !Sub ${ProductName}
                BranchName: master
              RunOrder: 1
        - Name: Build
          Actions:
            - Name: "CodeBuild"
              InputArtifacts:
                - Name: SourceOutput
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: !Sub ${ProductName}
              OutputArtifacts:
                - Name: CodeBuildOutput
              RunOrder: 1
    DependsOn:
      - S3Artifact
      - CodeBuild

CloudFormationのファイルができたらAWS CLIで流します。

$ aws cloudformation deploy \
--template-file CodeBuildCodePipeline.yml \
--stack-name {YourStackName} \
--parameter-overrides ProjectName={your-project-name} AccountID={YourAWSAccountID}

{}は適宜置き換えてください。{your-project-name}はS3のURLに使われるので、大文字は使えません。
しばらくしてCodeCommit, CodeBuild, CodePipelineの3点セット(とArtifact用のS3)が作成されれば、
CodeBuildまわりの準備はOKです。masterにpushするとCodeBuildが走るようになります。

React Webアプリの作成

CodeCommitからcloneしてきた場所でcreate-react-appします。私はTypeScript派です。

$ git clone ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/{YourProjectName}
$ create-react-app sample_app --typescript
$ cd sample_app
$ npm start

ローカルでReactの初期画面が表示されればOKです。

Amplifyの設定

作成したReact WebアプリにAmplifyの設定をします。詳細な設定は本題ではないので省きます。

$ amplify init
(中略)
$ amplify add hosting
(中略)
$ amplify publish

env名はここでは仮にdevとします。
Amplify Consoleを使う場合とは違い、S3とCloudFrontをCLIから立てるadd hostingが必要になります。
設定はPROD (S3 with CloudFront using HTTPS)がおすすめです。
CloudFrontがセットになることで、HTTPSはもちろん、IP制限などの目的でWAFを使ったりすることもできるようになります。

amplify publishが終了して最後に表示されたCloudFrontのURLにReactのページが表示されればOKです。
S3のURLに転送される場合は、S3のStatic website hostingを無効にしてしばらく(20分ぐらい?)経つと正常にアクセスできるようになります。
CloudFront関連は反映に時間がかかるのが難点ですね…

buildspec.ymlの編集

いよいよ本題です。CodeBuildで使う設定ファイル、buildspec.ymlを作成していきます。
buildspec.ymlはリポジトリのルートディレクトリにおいておきます。
Codebuildの標準ビルドイメージにはAmplify CLIは入っていないのでインストールします。ローカルの環境と揃えるために、バージョン指定しておいたほうがいいと思います。
amplify_init.shはCodeBuild上に開発環境と同様のAmplify CLI環境を作成するためのスクリプトです。このあと作成します。

buildspec.yml
version: 0.2
phases:
  install:
    runtime-versions:
      nodejs: 10
    commands:
      - npm install -g @aws-amplify/cli@4.5.0
      - cd sample_app
      - ./amplify_init.sh $ENV
      - npm install
  build:
    commands:
      - amplify publish -c --yes

Amplifyのenv設定スクリプトの作成

Amplify CLIのamplify initは、パラメータを渡す事で自動化でき、既存のenvをそのままインポートすることができます。なのでシェルスクリプトを作成します。

amplify_init.sh
#!/bin/bash
set -e
IFS='|'
# Systems Managerのパラメータストアから、Amplify CLIで使用しているaws_access_key_idとaws_secret_access_keyを取得
access_key_id=$(aws ssm get-parameters --names '/AmplifyCICD/AccessKeyID' --query Parameters[].Value --output text)
secret_access_key=$(aws ssm get-parameters --names '/AmplifyCICD/SecretAccessKey' --query Parameters[].Value --output text)

# env名をCodeBuildの環境変数から取得
env=$1

# AWS ProfileをCodeBuild上で設定
aws configure set aws_access_key_id $access_key_id
aws configure set aws_secret_access_key $secret_access_key
aws configure set default.region ap-northeast-1

# Amplifyの設定
AWSCLOUDFORMATIONCONFIG="{\
\"configLevel\":\"project\",\
\"useProfile\":true,\
\"profileName\":\"default\"\
}"
AMPLIFY="{\
\"projectName\":\"sample_app\",\
\"envName\":\"$env\",\
\"defaultEditor\":\"code\"\
}"
PROVIDERS="{\
\"awscloudformation\":$AWSCLOUDFORMATIONCONFIG\
}"

# Amplify initの実行
amplify init \
--amplify $AMPLIFY \
--providers $PROVIDERS \
--yes

ポイントは、 

  • Amplify CLIはprofileで動くのでAWS Profileを用意する
  • AWS Profileのためのaccess key idとsecret accesskeyの取り扱いが悩ましいが、Systems Manergerパラメータストアでとりあえず納得する

というあたりです。もっといい方法があったら教えて下さい。

Systems Managerパラメータストアの設定

Systems ManagerパラメータストアにAmplify CLIで使用しているAccess Key IDとSecret Access Keyを保存しておきます。

名前
/AmplifyCICD/AccessKeyID aws_access_key_idの値
/AmplifyCICD/SecretAccessKey aws_secret_access_keyの値

これで準備が整いました。

動作確認

いざcommit & push。CodeBuildを開いて祈ります。

CodeBuild
(略)
[Container] 2019/12/14 14:36:47 Running command ./amplify_init.sh $ENV 
Scanning for plugins... 
Plugin scan successful 
Note: It is recommended to run this command from the root of your app directory 

For more information on AWS Profiles, see: 
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html 

- Initializing your environment: dev 
✔ Initialized provider successfully. 
- Updating resources in the cloud. This may take a few minutes... 
✔ All resources are updated in the cloud 


Initialized your environment successfully. 

Your project has been successfully initialized and connected to the cloud! 

Some next steps: 
"amplify status" will show you what you've added already and if it's locally configured or deployed 
"amplify <category> add" will allow you to add features like user login or a backend API 
"amplify push" will build all your local backend resources and provision it in the cloud 
“amplify console” to open the Amplify Console and view your project status 
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud 

Pro tip: 
Try "amplify add api" to create a backend API and then "amplify publish" to deploy everything 

(略)

[Container] 2019/12/14 14:37:35 Entering phase BUILD 
[Container] 2019/12/14 14:37:35 Running command amplify publish -c --yes 
- Fetching updates to backend environment: dev from the cloud. 
✔ Successfully pulled backend environment dev from the cloud. 

Current Environment: dev 

| Category | Resource name   | Operation | Provider plugin   | 
| -------- | --------------- | --------- | ----------------- | 
| Hosting  | S3AndCloudFront | No Change | awscloudformation | 

No changes detected 

> sample_app@0.1.0 build /codebuild/output/src196595873/src/sample_app 
> react-scripts build 

Creating an optimized production build... 
Compiled successfully. 

File sizes after gzip: 

  39.93 KB  build/static/js/2.5867ff6c.chunk.js 
  772 B     build/static/js/runtime-main.6b5fbc27.js 
  611 B     build/static/js/main.af5f7261.chunk.js 
  547 B     build/static/css/main.d1b05096.chunk.css 

The project was built assuming it is hosted at the server root. 
You can control this with the homepage field in your package.json. 
For example, add this to build it for GitHub Pages: 

  "homepage" : "http://myname.github.io/myapp", 

The build folder is ready to be deployed. 
You may serve it with a static server: 

  yarn global add serve 
  serve -s build 

Find out more about deployment here: 

  bit.ly/CRA-deploy 

frontend build command exited with code 0 
- Uploading files... 
✔ Uploaded files successfully. 
CloudFront invalidation request sent successfuly. 
https://XXXXXXXXXXXXXX.cloudfront.net 
Your app is published successfully. 
https://XXXXXXXXXXXXXX.cloudfront.net 

[Container] 2019/12/14 14:37:55 Phase complete: BUILD State: SUCCEEDED 
[Container] 2019/12/14 14:37:55 Phase context status code:  Message:  
[Container] 2019/12/14 14:37:55 Entering phase POST_BUILD 
[Container] 2019/12/14 14:37:55 Phase complete: POST_BUILD State: SUCCEEDED 
[Container] 2019/12/14 14:37:55 Phase context status code:  Message:  

できたー!

動かないときはだいたいIAMのロールの問題ですので、CodeBuildServiceRoleCodePipelineServiceRoleに適切なロールを割り当ててあげてください。

Amplifyのリソース追加しても動くの?

もちろん動きます。試しにamplify add apiして、amplify pushせずにCommit & Pushしてみましょう。

$ amplify add api
(中略)
$ git commit -am 'amplify add api'
$ git push origin master
CodeBuild
[Container] 2019/12/14 15:44:43 Running command ./amplify_init.sh $ENV 
Scanning for plugins... 
Plugin scan successful 
Note: It is recommended to run this command from the root of your app directory 

For more information on AWS Profiles, see: 
https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html 

- Initializing your environment: dev 
✔ Initialized provider successfully. 

The following types do not have '@auth' enabled. Consider using @auth with @model 
     - Todo 
Learn more about @auth here: https://aws-amplify.github.io/docs/cli-toolchain/graphql#auth  


GraphQL schema compiled successfully. 

Edit your schema at /codebuild/output/src519574347/src/sample_app/amplify/backend/api/sampleApp/schema.graphql or place .graphql files in a directory at /codebuild/output/src519574347/src/sample_app/amplify/backend/api/sampleApp/schema 
- Updating resources in the cloud. This may take a few minutes... 


UPDATE_IN_PROGRESS amplify-sampleapp-dev-230440 AWS::CloudFormation::Stack Sat Dec 14 2019 15:44:58 GMT+0000 (Coordinated Universal Time) User Initiated 


CREATE_IN_PROGRESS apisampleApp           AWS::CloudFormation::Stack Sat Dec 14 2019 15:45:03 GMT+0000 (Coordinated Universal Time)                             
CREATE_IN_PROGRESS apisampleApp           AWS::CloudFormation::Stack Sat Dec 14 2019 15:45:04 GMT+0000 (Coordinated Universal Time) Resource creation Initiated 
UPDATE_IN_PROGRESS hostingS3AndCloudFront AWS::CloudFormation::Stack Sat Dec 14 2019 15:45:04 GMT+0000 (Coordinated Universal Time)                             
UPDATE_COMPLETE    hostingS3AndCloudFront AWS::CloudFormation::Stack Sat Dec 14 2019 15:45:05 GMT+0000 (Coordinated Universal Time)                             
 (中略)
[Container] 2019/12/14 15:48:37 Entering phase BUILD 
[Container] 2019/12/14 15:48:37 Running command amplify publish -c --yes 
- Fetching updates to backend environment: dev from the cloud. 
✔ Successfully pulled backend environment dev from the cloud. 

Current Environment: dev 

| Category | Resource name   | Operation | Provider plugin   | 
| -------- | --------------- | --------- | ----------------- | 
| Hosting  | S3AndCloudFront | No Change | awscloudformation | 
| Api      | sampleApp       | No Change | awscloudformation | 

No changes detected 
(以下略)

ローカルでamplify pushしてないのに、APIできちゃいましたね…
最初はamplify publishのときにリソースが作られるのかなと思ったのですが、init時に変更を検知して作られるようです。
Amplifyのリソースだけでなく、CustomResources.jsonなどに追加した独自のAWSリソースもいけちゃいます。

ついでにCIしよう

ここまできたらテストも自動化したいですね。
ほぼcreate-react-appの話になってしまいますが、ついでに書いておきます。

create-react-appで作成されるpackage.jsonscriptsに、CIの定義を追加します。

package.json
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "test:CI": "CI=true react-scripts test", // 追加
    "eject": "react-scripts eject"
  }

さらに、buildspec.ymlにテストを実行する行を追加します。

buildspec.yml
version: 0.2
phases:
  install:
    runtime-versions:
      nodejs: 10
    commands:
      - npm install -g @aws-amplify/cli@4.5.0
      - cd sample_app
      - ./amplify_init.sh $ENV
      - npm install
  pre_build:
    commands:
      - npm run test:CI  # 追加
  build:
    commands:
      - amplify publish -c --yes

Commit & PushしてCodeBuildを確認しましょう。一度出来てしまえばあとは楽ですよね。

CodeBuild
[Container] 2019/12/14 15:29:27 Entering phase PRE_BUILD 
[Container] 2019/12/14 15:29:27 Running command npm run test:CI 

> sample_app@0.1.0 test:CI /codebuild/output/src453020370/src/sample_app 
> CI=true react-scripts test 

PASS src/App.test.tsx 
  ✓ renders learn react link (42ms) 

Test Suites: 1 passed, 1 total 
Tests:       1 passed, 1 total 
Snapshots:   0 total 
Time:        1.8s 
Ran all test suites. 

これでテストも毎回実行されるようになりました。

まとめ

ぶっちゃけAmplify Conosle使ったほうが100倍楽です!
でもAmplify使い始めた頃にはConsoleが東京リージョンになかったので仕方なかったんです!!!

お疲れさまでした!!!!

18
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
10