このディレクトリ構成で、AWSへのデプロイフローを作る。
Vue.jsのWebサイトを 本番 (prod) / ステージング (stg)環境に分けて管理する。ステージング環境は検証用として使える。
v2okimochi-dev/
├ .gitub/
│ └ workflows/
│ └ cdk.yml (GitHub Actionsワークフローを定義するymlファイルを置く)
│
├ frontend/
│ ├ staging_assets/
│ │ └ robots.txt
│ └ ... (ここにVueのアレコレを置く)
│
├ batch/
│ └ signed_url/
│ └ ... (ここに証明書付きURL作成用のPythonバッチアレコレを置く)
│
└ infra/
└ aws-cdk/
└ ... (ここにAWS CDKのアレコレを置く)
ほとんどのAWSサービスは AWS CDK (docsはコレ)でデプロイする。CDKの性質上、CloudFormationを軸に管理されるみたい。
CDKによるデプロイ部分は、 (local上でも良いが、) GitHub Actionsに定義して developブランチにpush (merge)されたらステージング環境へデプロイし、masterブランチにpush (merge)されたら本番環境へデプロイするような自動デプロイフローを作る。
全体としては、こうなる
- S3
-
frontend/dist/を置く場所 - CloudFront以外からアクセスさせる気はないのでOrigin Access Identityだけ許す
- ステージング環境だけ
robots.txtも置く- 念のため検索ロボットによる捕捉も防ぐ
-
- CloudFront
- S3内のファイルは全てCloudFrontを通してアクセスさせる
- HTTPS通信だけ許す
- 本番環境だけ、予め取得しておいたAWSのパブリック
SSL/TLS証明書&独自のドメイン名を設定する- 詳細はそれぞれRoute53, Certificate Managerの項目に書いた
- デフォルトではAWSによってCloudFront用のドメイン名と証明書が付与される
- ステージング環境だけ、
index.htmlへのアクセスは署名付きURL認証を差し込む (公開されても嬉しくないので)- 全ファイルに差し込みたかったが
index.html以外が403を返してしまい、原因もわからず泣く泣く妥協 -
index.html以外のファイルはURLがバレれば未認証でもアクセスできてしまうのがだいぶアレ
- 全ファイルに差し込みたかったが
- Route53
- 本番環境だけ、独自ドメインを含むドメイン名 (たとえば
www.mydomain.com)でCloudFrontへ繋ぐ - 今回は
お名前.com(外部)で登録したドメイン名なので、お名前.com側に 予めRoute53で作ったホストゾーン内のネームサーバーを登録しておく - CDKではAレコード・AAAAレコードの作成をやる (FQDNとCloudFrontドメイン名の紐付け)
- 本番環境だけ、独自ドメインを含むドメイン名 (たとえば
- Certificate Manager
- AWSのパブリック
SSL/TLS証明書を取得する - リージョンは
バージニア北部 (us-east-1)で作成する (CloudFrontとの結びつけがここしか対応してなさそう) - 予めRoute53に
CNAMEレコードとして登録しておく
- AWSのパブリック
AWS側で予めやっておく手動作業
全部cdkでやりたかったけど。。。
![]()
AWS外で登録した独自ドメインをAWS Route53で管理
たとえば お名前.comで独自ドメイン mydomain.comを登録した場合。
- Route53でパブリックホストゾーン
mydomain.comを作成
このホストゾーンIDは後で使うのでメモしておく - お名前.comで独自ドメイン
mydomain.comのネームサーバーを、 "1.によりホストゾーンに自動生成された NSレコードmydomain.comのネームサーバー"に変更
AWSでSSL/TLS証明書を取得する
- Certificate Managerでパブリック証明書
*.mydomain.comをリクエストする
リクエストが作られ、ステータスが検証保留中となるはず - 証明書のCNAMEレコードをRoute53のホストゾーン
mydomain.comに追加する
(Route53に追加みたいなボタンを押せば自動で追加してくれる) - 証明書のARNをメモしておく (CloudFrontとの紐付けをcdkでやるため)
local上での作業
shellで手作業
CDKの導入は Getting started with the AWS CDKに従った。
あとCDKは全部TypeScriptで書いた。
npm install @aws-cdk/aws-s3 @aws-cdk/aws-s3-deployment @aws-cdk/aws-cloudfront @aws-cdk/aws-certificatemanager @aws-cdk/aws-route53 @aws-cdk/aws-route53-targets
AWSアカウントIDとリージョンを指定して CDKTookKitというCloudFormation スタックを作成
cdk bootstrap XXXXXXXXXXXX/ap-northeast-1
AWS CDKで利用するみたい。作成してないと、こういうエラーを吐かれる。。。 ![]()
❌ v2okimochi-dev failed: Error: This stack uses assets, so the toolkit stack must be deployed to the environment (Run "cdk bootstrap aws://unknown-account/unknown-region")
cf. AWS CDKの'aws-s3-deployment'を使ってクライアントサイドも一緒にデプロイする
AWS CDKのstackを記述
各ソースコード (いっぱいあるので折りたたんだ)
# !/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { AwsCdkStack } from '../lib/stacks/v2okimochi-dev';
import * as util from "../lib/util";
const validateEnvironment = (target: string) => {
/**
* 意図した環境が `--context`オプションで指定されていない場合、例外終了させる
*/
if (!target) throw new Error("環境が指定されていません");
const validTarget = util.findEnvironment(target);
if (!validTarget) throw new Error(`環境名が正しくありません: ${target}`);
return validTarget;
};
const productName = "v2okimochi-dev";
const app = new cdk.App();
const target = validateEnvironment(app.node.tryGetContext("target"))
new AwsCdkStack(app, `${productName}-${target}`, target);
/**
* プロダクト環境名の定義
*/
export enum Environments {
PROD = "prod",
STG = "stg",
}
export function findEnvironment(env: string): string | undefined {
if (env == Environments.PROD) return Environments.PROD;
else if (env == Environments.STG) return Environments.STG;
else return undefined;
}
/**
* 環境変数の取り出し
*/
export function validateEnvironmentVariable(key: string): string {
const value = process.env[key];
if (!value) throw new Error(`環境変数の値が見つかりません: ${key}`);
return value;
}
import * as cdk from "@aws-cdk/core";
import * as certificatemanager from "@aws-cdk/aws-certificatemanager";
import * as s3 from "@aws-cdk/aws-s3";
import * as s3deploy from "@aws-cdk/aws-s3-deployment";
import { Environments, validateEnvironmentVariable } from "../util";
import { setupCloudFront } from "../services/cloudfront/setupCloudFront";
import { setupRoute53 } from "../services/route53/setupRoute53";
export class AwsCdkStack extends cdk.Stack {
constructor(
scope: cdk.Construct,
id: string,
target: string,
props?: cdk.StackProps
) {
super(scope, id, props);
/** ##############################
* S3 Bucket
*/
const s3WebName = `${id}-web`;
const s3Web = new s3.Bucket(this, id, {
bucketName: s3WebName,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
// Prod環境以外では、Webクローラに登録させないためのrobots.txtもデプロイする
const s3WebAssets: s3deploy.ISource[] =
target === Environments.PROD
? [s3deploy.Source.asset("../../frontend/dist")]
: [
s3deploy.Source.asset("../../frontend/dist"),
s3deploy.Source.asset("../../frontend/staging_assets"),
];
new s3deploy.BucketDeployment(this, `${s3WebName}-deployment`, {
sources: s3WebAssets,
destinationBucket: s3Web,
});
/** ##############################
* CloudFront, Certificate Manager
*/
const validDomain = validateEnvironmentVariable(
"V2OKIMOCHI_DEV_PROD_DOMAIN"
);
const validSubDomain = validateEnvironmentVariable(
"V2OKIMOCHI_DEV_PROD_SUBDOMAIN"
);
const fqdn = `${validSubDomain}.${validDomain}`;
const arn = validateEnvironmentVariable(
"V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN"
);
const validCertificate = certificatemanager.Certificate.fromCertificateArn(
this,
`${id}-acm-certificate`,
arn
);
const cloudFront = new setupCloudFront(this, {
id: `${id}-cloudfront`,
target: target,
s3WebBucket: s3Web,
certificate: validCertificate,
fqdns: [fqdn],
});
/** ##############################
* Route53
*/
const route53 =
target === Environments.PROD
? new setupRoute53(this, {
id: `${id}-route53`,
target: target,
domain: validDomain,
subDomain: validSubDomain,
cloudFront: cloudFront,
})
: undefined;
/** ##############################
* Tag
*/
cdk.Tag.add(this, "Product", id);
}
}
import * as cdk from "@aws-cdk/core";
import * as certificatemanager from "@aws-cdk/aws-certificatemanager";
import * as cloudfront from "@aws-cdk/aws-cloudfront";
import * as s3 from "@aws-cdk/aws-s3";
import { Environments, validateEnvironmentVariable } from "../../util";
export interface setupCloudFrontProps {
/**
* リソースの共通名 (たとえばプロダクト名)
*/
readonly id: string;
/**
* 環境 (Prodなど)
*/
readonly target: string;
/**
* オリジンとして指定するS3 Bucket
*/
readonly s3WebBucket: s3.Bucket;
/**
* SSL/TLS証明書を取得したAWS Certificate Manager
*/
readonly certificate: certificatemanager.ICertificate;
/**
* CloudFrontのカスタムFQDNリスト
*/
readonly fqdns: string[];
}
export class setupCloudFront {
private cloudFrontWebDistribution: cloudfront.CloudFrontWebDistribution;
public distribution(): cloudfront.CloudFrontWebDistribution {
return this.cloudFrontWebDistribution;
}
constructor(context: cdk.Stack, props: setupCloudFrontProps) {
/** ##############################
* 本番 (Prod)以外の環境では、公開しないようにデプロイする
*/
const signedAccount = validateEnvironmentVariable(
"V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID"
);
// Prod環境以外では署名付きURLを使用する
const behaviorsWithEnv: cloudfront.Behavior[] =
props.target === Environments.PROD
? [
{
isDefaultBehavior: true,
compress: true,
minTtl: cdk.Duration.seconds(0),
maxTtl: cdk.Duration.days(0),
defaultTtl: cdk.Duration.days(0),
},
]
: [
{
isDefaultBehavior: false,
pathPattern: "/index.html",
trustedSigners: [signedAccount],
compress: true,
minTtl: cdk.Duration.seconds(0),
maxTtl: cdk.Duration.days(0),
defaultTtl: cdk.Duration.days(0),
},
{
isDefaultBehavior: true,
compress: true,
minTtl: cdk.Duration.seconds(0),
maxTtl: cdk.Duration.days(0),
defaultTtl: cdk.Duration.days(0),
},
];
// Prod環境以外では403を200にリダイレクトさせる
const errorConfigurationsWithEnv: cloudfront.CfnDistribution.CustomErrorResponseProperty[] =
props.target === Environments.PROD
? [
{
errorCode: 403,
errorCachingMinTtl: 0,
responseCode: 200,
responsePagePath: "/index.html",
},
]
: [];
// Prod環境にだけ独自ドメイン名を割り当てる
const viewerCertificateWithEnv: cloudfront.ViewerCertificate | undefined =
props.target === Environments.PROD
? {
aliases: props.fqdns,
props: {
acmCertificateArn: props.certificate.certificateArn,
sslSupportMethod: "sni-only",
},
}
: undefined;
// CloudFrontだけがS3にアクセスできるようにする (ユーザはS3に直接アクセスできない)
const oai = new cloudfront.OriginAccessIdentity(
context,
`${props.id}-oai`,
{
comment: "s3 access.",
}
);
this.cloudFrontWebDistribution = new cloudfront.CloudFrontWebDistribution(
context,
`${props.id}-cloudfront`,
{
defaultRootObject: "index.html",
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.HTTPS_ONLY,
viewerCertificate: viewerCertificateWithEnv,
httpVersion: cloudfront.HttpVersion.HTTP2,
priceClass: cloudfront.PriceClass.PRICE_CLASS_200,
originConfigs: [
{
s3OriginSource: {
s3BucketSource: props.s3WebBucket,
originAccessIdentity: oai,
},
behaviors: behaviorsWithEnv,
},
],
geoRestriction: {
restrictionType: "whitelist",
locations: ["JP"],
},
errorConfigurations: errorConfigurationsWithEnv,
}
);
}
}
import * as cdk from "@aws-cdk/core";
import * as route53 from "@aws-cdk/aws-route53";
import * as route53targets from "@aws-cdk/aws-route53-targets";
import { setupCloudFront } from "../../services/cloudfront/setupCloudFront";
import { validateEnvironmentVariable } from "../../util";
export interface setupRoute53Props {
/**
* リソースの共通名 (たとえばプロダクト名)
*/
readonly id: string;
/**
* 環境 (Prodなど)
*/
readonly target: string;
/**
* ドメイン名
*/
readonly domain: string;
/**
* サブドメイン名
*/
readonly subDomain: string;
/**
* CloudFront distribution
*/
readonly cloudFront: setupCloudFront;
}
export class setupRoute53 {
constructor(context: cdk.Stack, props: setupRoute53Props) {
const fqdn = `${props.subDomain}.${props.domain}`;
const hostedZoneId = validateEnvironmentVariable(
"V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID"
);
const hostedZone = route53.HostedZone.fromHostedZoneAttributes(
context,
`${props.id}-hosted-zone`,
{
zoneName: props.domain,
hostedZoneId: hostedZoneId,
}
);
const aRecord = new route53.ARecord(context, `${props.id}-a-record`, {
zone: hostedZone,
recordName: fqdn,
target: route53.RecordTarget.fromAlias(
new route53targets.CloudFrontTarget(props.cloudFront.distribution())
),
});
const aaaaRecord = new route53.AaaaRecord(
context,
`${props.id}-aaaa-record`,
{
zone: hostedZone,
recordName: fqdn,
target: route53.RecordTarget.fromAlias(
new route53targets.CloudFrontTarget(props.cloudFront.distribution())
),
}
);
}
}
アカウントIDとか、ハードコーディングするとアレなやつは環境変数として埋め込むようにした。
- V2OKIMOCHI_DEV_PROD_DOMAIN
- 本番環境で使う独自ドメイン
- 今回は
mydomain.comみたいなやつ
- V2OKIMOCHI_DEV_PROD_SUBDOMAIN
- 本番環境で使うサブドメイン
- たとえば
www(FQDNにすればwww.mydomain.comになる)
- V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID
- 予め作っておいたRoute53ホストゾーンのID
- V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN
- 予め作っておいたCertificate Manager SSL/TLS証明書のARN
- V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID
- 署名済みユーザとして登録するアカウントID
- 今回は自分のアカウントIDだけ
たとえばこうやってCDKコマンドを使う。
diffで差分と文法エラーを確認、deployで実際にデプロイ、destroyで該当スタックを削除する。
# 本番
$ cdk diff --context target=prod
# ステージング
$ cdk diff --context target=stg
実行時の引数でスタックIDごと変えることによって、本番環境とステージング環境を分けるようにした。
S3の設定
aws-cdk/lib/stacks/v2okimochi-dev.tsに書いた。
-
removalPolicyをDESTROYにする- CloudFormationスタックが削除される時S3バケットも削除するため
cf. [[AWS CDK(Cloud Development Kit)] スタック削除時にS3バケットも削除されるように設定する] (https://dev.classmethod.jp/articles/aws-cdk-s3-delete-policy/) - ただしS3バケットが空でないと削除できずにエラー失敗するので、事前にバケットを空にしておく必要がある
cf. Automatically delete an S3 bucket with the AWS CDK stack
- CloudFormationスタックが削除される時S3バケットも削除するため
CloudFrontの設定
aws-cdk/lib/services/cloudfront/setupCloudFront.tsに書いた。
本番/ステージングの環境ごとに設定を用意したりして長くなったのでファイルを分けている。
- SSL/TLS証明書を予め手作業で (Certificate Managerに)作っておき、そのARNからCDKで参照する
-
trustedSignersにアカウントIDを指定することで、署名付きURLでのアクセス時にそのアカウントIDで持っているCloudFrontキーペアで照合されるっぽい- 自分のアカウントIDだけ指定した場合、CloudFront管理画面上では
selfと表示される
- 自分のアカウントIDだけ指定した場合、CloudFront管理画面上では
- Route53のレコードでも紐付けるが、CloudFront側にもaliasとして独自ドメイン名を付ける (
CNAMEs列に表示される) - なんとなく日本だけからアクセスできるようにした (geoRestriction)
- 今回は海外からアクセスされても嬉しくないので、リスクを軽減した
- CDKでは
文字列のリスト型なのでJPを見出すのに苦労した - ちなみに国コードは ISO 3166-1 alpha-2に従っているらしい
cf. AWS - ディストリビューションを作成または更新する場合に指定する値 - 制限 - Countries (国)
Route53の設定
aws-cdk/lib/services/route53/setupRoute53.tsに書いた。
本番環境でだけ作成する (条件分岐は aws-cdk/lib/stacks/v2okimochi-dev.tsのほう)
- ホストゾーンは予め手作業で作っておき、そのID (
HostedZoneId)からCDKで参照する - CDKで作るのはAレコードとAAAAレコード
- どちらもCloudFrontディストリビューションと独自ドメイン名を紐付ける
- ディストリビューションはCDK内で作成したインスタンスから拾う
- もしCDKでやらずにWebから操作するなら、紐付け対象を
CloudFront配信、リージョンをバージニア北部にしてCloudFrontドメイン名のほうを貼り付ければ良い (選択候補には出ないので手動で貼り付ける)
Tagの設定
aws-cdk/lib/stacks/v2okimochi-dev.tsの最後にしれっと書いている。
どうせCloudFormationによって管理されてるけど。。。
署名付きURLの作成
(手動だけど。。。)
/batch/signed_url/に、 ステージング環境のWebページにアクセスするための署名付きURLを作成するPythonスクリプトを書いた。
そんな頻繁に定期実行するわけじゃないからバッチと呼ぶことには諸説ありそうだが。。。
AWS側での事前準備
署名済みユーザ (今回は自分のアカウントID)でCloudFrontキーペアを作り、プライベートキー ( .pem)をダウンロードする。
rootユーザとしてログインしないと作れないみたい。
cf. 署名付き URL と署名付き Cookie (信頼された署名者) の作成が可能な AWS アカウントの指定
スクリプト作成と実行
pipenvで適当にpython3.8の仮想環境を整えて、 boto3をimportして使う。
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[packages]
boto3 = "==1.4.4"
[dev-packages]
flake8 = "==3.8.3"
[requires]
python_version = "3.8.3"
[scripts]
flake8 = "flake8 --ignore E501 ."
([dev-packages]と[scripts]に関する記述はAWSとは無関係なので無くても良い)
公式docsに従い、署名付きURL生成のコードを書く。
import datetime
import os
from botocore.signers import CloudFrontSigner
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
def rsa_signer(message):
private_pem_path = os.getenv(
'V2OKIMOCHI_DEV_STG_CLOUDFRONT_PRIVATE_PEM_PATH')
with open(private_pem_path, 'rb') as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(), password=None, backend=default_backend())
return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())
def handler():
key_id = os.getenv('V2OKIMOCHI_DEV_STG_CLOUDFRONT_KEY_ID')
url = os.getenv('V2OKIMOCHI_DEV_STG_CLOUDFRONT_URL')
expire_date = datetime.datetime(2020, 7, 28)
cloudfront_signer = CloudFrontSigner(key_id, rsa_signer)
# Create a signed url that will be valid until the specfic expiry date
# provided using a canned policy.
signed_url = cloudfront_signer.generate_presigned_url(
url, date_less_than=expire_date)
print(signed_url)
if __name__ == "__main__":
handler()
cf. Boto3 Docs - Generate a signed URL for Amazon CloudFront
ハードコーディングがアレな値は環境変数にした。
- V2OKIMOCHI_DEV_STG_CLOUDFRONT_PRIVATE_PEM_PATH
- ダウンロードしたCloudFrontプライベートキー (
.pem)へのパス - たとえば
/batch/signed_url/に置いたなら値は./xxxxx.pemみたいになる
- ダウンロードしたCloudFrontプライベートキー (
- V2OKIMOCHI_DEV_STG_CLOUDFRONT_KEY_ID
- pemファイル名に含まれているkey ID (大文字で20字くらいあるアレ)
- V2OKIMOCHI_DEV_STG_CLOUDFRONT_URL
-
https://xxxxxxxxxxx.cloudfront.net/index.htmlみたいになる
-
スクリプトで署名付きURLを発行
この main.pyを実行して、署名付きURLを発行する。
今回はpipenvでPython仮想環境を作ったので pipenv runでやる
$ pipenv run python src/main.py
- 上記コードでは
2020/7/28まで有効なURLを発行する - print文で標準出力されたURLを (改行は取り除いて)使う
- このへんもガチで自動化しようとするなら、コードをLambda化してCognitoやAPI Gatewayと連携させてログインページ作るくらいのことが必要そう
- Cookie付与とかもしんどかったので今回はクエリパラメータだけで済む署名付きURLにした
GitHub Actionsの記述
公式docsに従い、GitHubリポジトリに /.github/workflows/ディレクトリを用意してyamlで定義する。
cf. https://docs.github.com/ja/actions/configuring-and-managing-workflows/configuring-a-workflow
動きとしてはこんな感じ
-
test_and_build_frontend-
/frontend/でlintして問題なければbuild - (テストがあればtestもやるべきだけど今回はテスト書いてない
)
-
-
infra- 前のjobが成功してから動くように
needsで依存関係を作った -
/infra/aws-cdk/でdiffして問題なければdeploy - masterブランチ以外へのpushなら、ステージング環境へのdiffまでやる
- developブランチへのpushなら、ステージング環境へのdiffとdeployまでやる
- masterブランチへのpushなら、本番環境へのdiffとdeployまでやる
- 前のjobが成功してから動くように
ソースコード (長いので折りたたんだ)
name: cdk
on: push
env:
production-branch: 'refs/heads/master'
staging-branch: 'refs/heads/develop'
frontend-working-directory: frontend
infra-working-directory: infra/aws-cdk
aws-default-region: 'ap-northeast-1'
jobs:
test_and_build_frontend:
runs-on: ubuntu-18.04
defaults:
run:
shell: bash
working-directory: frontend
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Cache frontend Node modules
uses: actions/cache@v2
env:
cache-name: frontend-cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: '12.18'
- name: Setup dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Build
run: npm run build
- name: Upload frontend built path
uses: actions/upload-artifact@v2
with:
name: frontend-build-path
path: frontend/dist
infra:
needs: [test_and_build_frontend]
runs-on: ubuntu-18.04
defaults:
run:
shell: bash
working-directory: infra/aws-cdk
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Cache infra Node modules
uses: actions/cache@v2
env:
cache-name: infra-cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: '12.18'
- name: Setup dependencies
run: npm ci
- name: Download frontend built path
uses: actions/download-artifact@v2
with:
name: frontend-build-path
path: frontend/dist
- name: CDK Diff (Validate) with Stg
if: ${{github.ref != env.production-branch }}
run: npm run cdk diff -- --context target=stg
env:
AWS_DEFAULT_REGION: ${{ env.aws-default-region }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
V2OKIMOCHI_DEV_PROD_DOMAIN: ${{ secrets.V2OKIMOCHI_DEV_PROD_DOMAIN }}
V2OKIMOCHI_DEV_PROD_SUBDOMAIN: ${{ secrets.V2OKIMOCHI_DEV_PROD_SUBDOMAIN }}
V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID: ${{ secrets.V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID }}
V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN: ${{ secrets.V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN }}
V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID: ${{ secrets.V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID }}
- name: CDK Diff (Validate) with Prod
if: ${{github.ref == env.production-branch }}
run: npm run cdk diff -- --context target=prod
env:
AWS_DEFAULT_REGION: ${{ env.aws-default-region }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
V2OKIMOCHI_DEV_PROD_DOMAIN: ${{ secrets.V2OKIMOCHI_DEV_PROD_DOMAIN }}
V2OKIMOCHI_DEV_PROD_SUBDOMAIN: ${{ secrets.V2OKIMOCHI_DEV_PROD_SUBDOMAIN }}
V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID: ${{ secrets.V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID }}
V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN: ${{ secrets.V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN }}
V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID: ${{ secrets.V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID }}
- name: CDK Deploy to Stg
if: ${{github.ref == env.staging-branch }}
run: npm run cdk deploy -- --context target=stg --require-approval never
env:
AWS_DEFAULT_REGION: ${{ env.aws-default-region }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
V2OKIMOCHI_DEV_PROD_DOMAIN: ${{ secrets.V2OKIMOCHI_DEV_PROD_DOMAIN }}
V2OKIMOCHI_DEV_PROD_SUBDOMAIN: ${{ secrets.V2OKIMOCHI_DEV_PROD_SUBDOMAIN }}
V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID: ${{ secrets.V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID }}
V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN: ${{ secrets.V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN }}
V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID: ${{ secrets.V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID }}
- name: CDK Deploy to Prod
if: ${{github.ref == env.production-branch }}
run: npm run cdk deploy -- --context target=prod --require-approval never
env:
AWS_DEFAULT_REGION: ${{ env.aws-default-region }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
V2OKIMOCHI_DEV_PROD_DOMAIN: ${{ secrets.V2OKIMOCHI_DEV_PROD_DOMAIN }}
V2OKIMOCHI_DEV_PROD_SUBDOMAIN: ${{ secrets.V2OKIMOCHI_DEV_PROD_SUBDOMAIN }}
V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID: ${{ secrets.V2OKIMOCHI_DEV_PROD_HOSTED_ZONE_ID }}
V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN: ${{ secrets.V2OKIMOCHI_DEV_PROD_CERTIFICATE_ARN }}
V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID: ${{ secrets.V2OKIMOCHI_DEV_SIGNED_ACCOUNT_ID }}
-
npm run buildした成果物をupload保存しておき、後のjobでdownloadしてデプロイする - ブランチ名は
github.refで取得できるみたい -
npm runのコマンドに引数を渡す際は--を間に差し込む
cf. https://docs.npmjs.com/cli/run-script - 今回cdkコマンドには引数として
targetを渡す (環境を判別する)- 独自に引数を渡すには
--context
- 独自に引数を渡すには
-
cdk deployには--require-approval neverも加える- IAM roleなどセキュリティ関係の変更もスッと通す (デフォルトだと確認が差し込まれてしまい
yでEnterせねばならない)
- IAM roleなどセキュリティ関係の変更もスッと通す (デフォルトだと確認が差し込まれてしまい
- cdkのコードで利用する環境変数は、GitHub側のsecretsに定義した上でworkflow上の
envに紐付けておく (今回はstepごとに定めた) -
timeout-minutesを仕込んだ (cdkでたまに無限デプロイを始めてしまうので)
おわり
pushすればこんな感じで動く ( Actionsタブから確認)
- 上の画像ではdevelopブランチがpushされたので、ステージング環境のdiffとdeployが行われている
- 対象外のstepはスキップされるらしい
- 途中で失敗すると、後のstepはスキップされる
- 画像右上の
Artifactsはフロー内でアップロードしたfrontendのビルド成果物 - 画像右上の
Re-run all jobsでActionsを回し直せる- 偶然失敗したケースならpushし直さなくて済む
- CircleCIと比べると、rerun対象のjobを選べない (全jobやり直してしまう)
かなり多くのサイトを参考にしたけど、あまりにもそれぞれのサイトから断片的な情報を組み合わせたので記憶の彼方に。。。 ![]()



