ようへいの日々精進XP

よかろうもん

CircleCI Workflows のジョブ制御を完全に理解した (わけではない)

tl;dr

CircleCI の Workflows で, 世界中で多分自分だけがハマった (他の人はきっとハマることは無いはず) のでメモっておきます.

そして, この記事は...

YAMAP エンジニア Advent Calendar 2019 の 7 日目の記事になる予定です.

qiita.com

そして, CircleCI Advent Calendar 2019 の 7 日目の記事になる予定です.

qiita.com

とある .circleci/config.yml

むかーし, むかーし

とある .circleci/config.yml (以後, config.yml) がありましたとさ.

version: 2.1

executors:
  default:
    docker:
      - image: docker:19.03.5-git

jobs:
  test:
    executor:
      name: default
    steps:
      - run:
          name: Test
          command: |
            echo 'Test'
  build:
    executor:
      name: default
    steps:
      - run:
          name: Build
          command: |
            echo 'Build'
  deploy:
    executor:
      name: default
    steps:
      - run:
          name: Deploy
          command: |
            echo 'Deploy'

workflows:
  version: 2
  test-only:
    jobs:
      - test:
          filters:
            branches:
              ignore: 
                - master
                - development
  build-after-deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: 
                - master
                - development

この config.yml の Workflows で実現したかったのは,

  • master と development ブランチの場合には, build ジョブと deploy ジョブを実行したい
  • また, build ジョブの後に deploy ジョブを実行する必要がある
  • master と development ブランチ以外は test ジョブのみ実行したい

だったそうな.

ところが

master と development 以外のブランチでも build と deploy のジョブが動いちゃうことを確認しました.

f:id:inokara:20191206001231p:plain

あれ, deployment/demo02 ブランチなのに, test-onlybuild-after-deploy が動いちゃってる. なんでやねん.

しかも, build しか動いていないという...

f:id:inokara:20191206001553p:plain

修正

冒頭の config.yml を以下のように修正しました.

... (略) ...
workflows:
  version: 2
  test-only:
    jobs:
      - test:
          filters:
            branches:
              ignore: 
                - master
                - development
  build-after-deploy:
    jobs:
      - build:
          filters:
            branches:
              only: 
                - master
                - development
      - deploy:
          requires:
            - build

これで改めてジョブを実行してみる.

以下は deployment/demo02 ブランチ.

f:id:inokara:20191206002135p:plain

以下は master ブランチにマージした場合の挙動.

f:id:inokara:20191206002422p:plain

意図した通りにジョブが動いていることを確認しました.

参考

circleci.com

EC2 イメージ (AMI) を操作するツール (amiCtrl) を作って公開していましたが, 最近, 大幅にバージョンアップしたので自慢させてください

tl;dr

EC2 インスタンスのマシンイメージ (以後, AMI) を操作するツール (https://github.com/oreno-tools/amiCtrl, 以後 amiCtrl) を前職で作って利用していたのですが, 色々と AMI を弄ることが多くなったので amiCtrl を大幅にバージョンアップして利用していますので紹介させて頂きます.

github.com

そして, この記事は YAMAP エンジニア Advent Calendar 2019 の六日目の記事になる予定です.

qiita.com

amiCtrl に出来ること

amiCtrl は以下のような AMI に纏わる作業を 1 コマンドで実行出来るようになっています.

  • 指定した EC2 インスタンスの AMI を作成
  • 指定した AMI の情報を取得
  • 指定した AMI の削除
  • 指定したリージョンの AMI 一覧を取得

実際に動かしてみると, 以下のような感じになります.

# AMI 作成
$ ./amiCtrl -instance=i-18173987 -name=suzuki-ami-desu -create
+-----------------+--------------+-----------+-------------+
|    AMI NAME     |    AMI ID    |   STATE   | SNAPSHOT ID |
+-----------------+--------------+-----------+-------------+
| suzuki-ami-desu | ami-00385acd | available | snap-0c1cf6 |
+-----------------+--------------+-----------+-------------+

# AMI 情報取得
$ ./amiCtrl -ami=ami-00385acd # -ami オプションを指定しない場合, 所有している AMI の一覧が表示されます
+-----------------+--------------+-----------+-------------+
|    AMI NAME     |    AMI ID    |   STATE   | SNAPSHOT ID |
+-----------------+--------------+-----------+-------------+
| suzuki-ami-desu | ami-00385acd | available | snap-0c1cf6 |
+-----------------+--------------+-----------+-------------+

# AMI 削除
$ ./amiCtrl -ami=ami-00385acd -delete
+-----------------+--------------+-----------+-------------+
|    AMI NAME     |    AMI ID    |   STATE   | SNAPSHOT ID |
+-----------------+--------------+-----------+-------------+
| suzuki-ami-desu | ami-00385acd | available | snap-0c1cf6 |
+-----------------+--------------+-----------+-------------+
上記の AMI を削除しますか?(y/n): y
AMI を削除します...
AMI を削除しました.

シンプルにこれだけのことしか出来ないコマンドラインツールです.

今回, amiCtrl に追加した機能

今回 AMI の作成を自動化するにあたり, 自動的に作成した AMI のから古い AMI を自動的に削除する必要がありましたので, 以下のような機能追加 (オプション追加) 及び機能改善 (速度改善) を行いました.

  1. AMI 名の一部を指定して AMI 情報を取得出来る --prefix オプションを追加
  2. 作成日 (CreationDate) をチェックして古い AMI を検出する --days オプションを追加 (--prefix オプションと併用)
  3. 作成日 (CreationDate) をチェックして最新の AMI を検出する --latest オプションを追加 (--prefix オプションと併用)
  4. AMI に紐づく Snapshot ID 取得の高速化
  5. AMI 一覧の取得において, CreationDate が古い順に一覧を取得する --sort-by-creation オプションを追加

今回, 特に自慢したいのは, AMI に紐付いている Snapshot ID 取得の高速化です. 過去のバージョンでは AMI ID をキーにして describe-snapshots を実行して Snapshot ID を取得していましたが, 今回のバージョンからは describe-images 内に含まれている Snapshot ID を取得する実装に変更しています. これにより, AWSAPI を叩く回数を大幅に減らすことで Snapshot ID 取得が高速化され, 全体的な処理速度向上につながっています.

amiCtrl の様子

以下は amiCtrl を動かしている様子です.

f:id:inokara:20191205231918g:plain

AMI の一覧を取得, AMI の一覧を AMI が作成された順番で出力, AMI を AMI ID を指定して削除しようとしている様子です.

落ち穂拾い

Go で作ったコマンドラインツールのテスト

amiCtrl は一応 Golang で実装されています. また, 自分の Golang 力が無いのでユニットテストはありませんが, 一応, インテグレーションテスト的なものは用意しています.

インテグレーションテストは moto_server という AWS のモックサーバーを起動してモックサーバーに対して amiCtrl を実行して結果をパースするテストを行っています.

テストの実行自体は Docker コンテナをガチャガチャして docker-compose を介して, 以下のようにテストを実行します.

$ docker-compose exec -T amictrl_local make test
=== RUN   TestVersionFlag
--- PASS: TestVersionFlag (2.38s)
=== RUN   TestStdoutList
--- PASS: TestStdoutList (3.65s)
=== RUN   TestStdoutCreate
--- PASS: TestStdoutCreate (2.96s)
=== RUN   TestStdoutCreateError
--- PASS: TestStdoutCreateError (2.79s)
=== RUN   TestStdoutDelete
--- PASS: TestStdoutDelete (3.34s)
=== RUN   TestStdoutDeleteError
--- PASS: TestStdoutDeleteError (3.33s)
=== RUN   TestStdoutDeleteNo
--- PASS: TestStdoutDeleteNo (3.27s)
=== RUN   TestStdoutState
--- PASS: TestStdoutState (5.34s)
=== RUN   TestStdoutJson
--- PASS: TestStdoutJson (5.01s)
PASS
ok      amiCtrl 32.076s

docker-compose.yml は以下のような感じです.

version: '3'
services:
  moto_server:
    build:
      context: ./tests
      dockerfile: Dockerfile.moto_server
    container_name: moto-server
    command: ["moto_server", "ec2", "-H", "0.0.0.0", "-p", "5000"]
    ports:
      - "5000:5000"
    networks:
      amictrl_test_net:
        ipv4_address: 192.168.0.100
  amictrl_local:
    build:
      context: ./
      dockerfile: ./tests/Dockerfile.golang
    container_name: amictrl-local
    volumes:
      - .:/go/src/amiCtrl
    working_dir: /go/src/amiCtrl
    command: tail -f /dev/null
    networks:
      amictrl_test_net:
        ipv4_address: 192.168.0.2
  amictrl_circleci:
    build:
      context: ./
      dockerfile: ./tests/Dockerfile.golang
    container_name: amictrl-circleci
    working_dir: /go/src/amiCtrl
    command: tail -f /dev/null
    networks:
      amictrl_test_net:
        ipv4_address: 192.168.0.3

networks:
  amictrl_test_net:
    driver: bridge
    ipam:
     driver: default
     config:
       - subnet: 192.168.0.0/24

amictrl_localamictrl_circleci の 2 のサービスが用意されている理由は後述いたします.

CircleCI でテスト

CircleCI でテストを実行する場合にはひと手間必要でした. .circleci/config.yml 内で setup_remote_docker を利用した場合, Docker の --volume オプションが利用出来ないという制約があります.

docker-compose.yml だと volumes キーでローカルのパスを Docker コンテナにマウントする以下の部分になります.

    volumes:
      - .:/go/src/amiCtrl

マウント出来ないのであれば, 無視してくれれば良いのですが, CircleCI では空のディレクトリがマウントされた状態となる為, ローカル端末でテストを実行する場合と CircleCI でテストを実行する場合とで docker-compose の Service を分けている為, 先述の通り, docker-compose.yml 内に 2 つのサービスを用意しています.

ローカルの端末上でテストを実行する場合, 以下のように実行します.

$ docker-compose exec amictrl_local make test

CircleCI 上でテストを実行する場合, 以下のように実行します.

$ docker-compose exec amictrl_local make test

ということで

AMI を操作する際には是非, amiCtrl をご用命くださいませ.

(完)

2019 年 12 月 05 日 (木)

ジョギング

  • 博多天神 45 分くらい
  • 懸垂 x 8 回
  • RINCON で走ったが, 左足小指の付け根は軽く痛い, 右の踵にやっぱ違和感

ランチ

  • 会社のチームランチでハンバーグ, 美味しかった!

ギョーム

  • ミーティングやら
  • WordPress やら
  • docker コマンドを叩ける Docker コンテナから docker ps 叩いたり, 運用管理ツール実装にあたっての調査したり
  • とあるシステムのデプロイの為の .circleci/config.yml を頑張って書いた

夕飯

  • 定番の豚コマの炒めがいつも美味しい

俺の AWS CDK コードを恥ずかしげもなく晒す (2) 〜 よくありそうな S3 + CloudFront + Route53 構成 (2) 〜

tl;dr

俺の AWS CDK コードを恥ずかしげもなく晒すシリーズ第一弾の続き. そして, この記事は YAMAP エンジニア Advent Calendar 2019 の五日目の記事になる予定です.

qiita.com

実現したいこと

昨日, 恥ずかしげもなく公開したコードを少し進化させてみました. 進化の内容は以下の通り.

  • 出来るだけ, 肝となるコード内にドメイン名とか ID とか汎用性の低い情報を書かないようにする
  • WAF の WebACL を紐付ける

俺の AWS CDK

リポジトリ

引き続き, 以下にアップしています.

github.com

肝となる lib/tutorial01-stack.ts は以下の通り.

import cdk = require('@aws-cdk/core');
import s3 = require('@aws-cdk/aws-s3');
import cf = require('@aws-cdk/aws-cloudfront');
import iam = require('@aws-cdk/aws-iam');
import route53 = require('@aws-cdk/aws-route53');
import route53_targets = require('@aws-cdk/aws-route53-targets/lib');

interface Tutorial01InfraStackProps extends cdk.StackProps {
  domain: string;
  project: string;
  issue: string;
  owner: string;
  certificate_arn: {[key: string]: string};
  logbucket_name: string;
  webacl_id: string;
}

export class Tutorial01InfraStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props: Tutorial01InfraStackProps) {
    super(scope, id, props);

    // const envName = this.node.tryGetContext('env');
    const envName = process.env.DEPLOY_ENV ? process.env.DEPLOY_ENV : 'dev';
    const domainName = this.node.tryGetContext('fqdn');

    // Create Bucket for contents
    const websiteBucket = new s3.Bucket(this, `Tutorial01Infra-s3bucket-${this.stackName}`, {
      bucketName: domainName,
    });

    // Define Bucket for logging
    const loggingBucket = s3.Bucket.fromBucketName(
      this,
      `Tutorial01Infra-loggingbucket-${this.stackName}`,
      props.logbucket_name
    );

    // Create CloudFront Origin Access Identity
    const OAI = new cf.CfnCloudFrontOriginAccessIdentity(this, `Tutorial01Infra-identity-${this.stackName}`,{
      cloudFrontOriginAccessIdentityConfig:{
        comment: `Tutorial01Infra-identity-${this.stackName}`
      }
    });

    // Create Access Policy for S3 Bucket
    const webSiteBucketPolicyStatement = new iam.PolicyStatement({effect: iam.Effect.ALLOW});
    webSiteBucketPolicyStatement.addCanonicalUserPrincipal(OAI.attrS3CanonicalUserId);
    webSiteBucketPolicyStatement.addActions("s3:GetObject");
    webSiteBucketPolicyStatement.addResources(`${websiteBucket.bucketArn}/*`);
    websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);

    // Create CloudFront Distribution
    const distribution = new cf.CloudFrontWebDistribution(this, `Tutorial01Infra-cloudfront-${this.stackName}`, {
      originConfigs:[
        {
          s3OriginSource: {
            s3BucketSource: websiteBucket,
            originAccessIdentityId: OAI.ref
          },
          behaviors: [{ isDefaultBehavior: true}]
        }
      ],
      aliasConfiguration: {
        acmCertRef: props.certificate_arn[envName],
        names: [domainName],
        sslMethod: cf.SSLMethod.SNI,
        securityPolicy: cf.SecurityPolicyProtocol.TLS_V1_1_2016,
      },
      viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
      priceClass: cf.PriceClass.PRICE_CLASS_ALL,
      loggingConfig: {
        bucket: loggingBucket,
        prefix: domainName + '/' 
      },
      webACLId: props.webacl_id
    });

    const zone = route53.HostedZone.fromLookup(this, 'Zone', { domainName: props.domain });
    new route53.ARecord(this, `Distribution-route53-record-${this.stackName}`, {
      recordName: domainName,
      target: route53.AddressRecordTarget.fromAlias(new route53_targets.CloudFrontTarget(distribution)),
      zone
    });

    for (let cons of [websiteBucket, distribution]) {
      cdk.Tag.add(cons, 'Project', props.project);
      cdk.Tag.add(cons, 'Environment', envName);
      cdk.Tag.add(cons, 'Owner', props.owner);
      cdk.Tag.add(cons, 'Issue', props.issue);
      cdk.Tag.add(cons, 'Name', domainName);
    }

    // Output CloudFront URL
    new cdk.CfnOutput(this, 'CloudFrontURL', {value: `https://${distribution.domainName}/`})
    // Output Distribution ID
    new cdk.CfnOutput(this, 'DistributionId', { value: distribution.distributionId });
  }
}

更に, bin/tutorial01-stack.ts は以下のように書きました. こちらに出来るだけユニークな情報 (汎用性の低い情報) を寄せるようにした感じです.

#!/usr/bin/env node
import 'source-map-support/register';
import cdk = require('@aws-cdk/core');
import { Tutorial01Stack } from '../lib/tutorial01-stack';

const deployEnv = process.env.DEPLOY_ENV ? process.env.DEPLOY_ENV : 'dev';

const app = new cdk.App();
new Tutorial01Stack(app, `Tutorial01Stack-${deployEnv}`, {
    env: {
        account: process.env.CDK_DEFAULT_ACCOUNT,
        region: process.env.CDK_DEFAULT_REGION
    },
    domain: 'example.com',
    project: 'my-project',
    issue: 'my-issue',
    owner: 'My Team',
    certificate_arn: { dev: 'arn:aws:acm:us-east-1:123456789012:certificate/xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx',
                       production: 'arn:aws:acm:us-east-1:123456789012:certificate/xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx' },
    logbucket_name: 'my-log-bucket-name',
    webacl_id: 'xxxxxxxxx-xxxx-xxxx-xxxxx-xxxxxxxx'
});

従来は --context オプションを使って渡していた dev とか production を表現する文字列については, 環境変数から渡すようにして, 環境毎に CloudFormation スタックを作るようにしました.

以上

引き続き, 頑張って AWS CDK を習得していきたいと思います.

2019 年 12 月 04 日 (水)

ジョギング

  • 山王公園を 40 分程度
  • 懸垂 x 8 回
  • RINCON で走ったが, 懸念していた左足小指の付け根が痛くなることがなかったので良かった, 良かった

ギョーム

  • AWS CDK 引き続き, 固有な情報はメインとなるコードには含めないような改修を行ったりした
  • 他, 色々と年末感がある
  • ちょっと疲れた

re:Invent

わーっと驚くほどの新サービスリリースラッシュ. クラスメソッドの人くらいしか追いつけていないんじゃないかなって思うくらい. そんな中で JAWS-UG 福岡青柳さんの EKS on Fargate の記事がとてもわかり易くて良かった.

dev.classmethod.jp

流石なだなって思った.

夕飯

俺の AWS CDK コードを恥ずかしげもなく晒す 〜 よくありそうな S3 + CloudFront + Route53 構成 〜

tl;dr

俺の AWS CDK コードを恥ずかしげもなく晒すシリーズ第一弾. もしかしたら, 第二弾は無いかもしれませんがご容赦ください. そして, この記事は YAMAP エンジニア Advent Calendar 2019 の四日目の記事になる予定です.

qiita.com

実現したいこと

  • S3 + CloudFront + Route53 という三種の神器に合わせて ACM は決め打ちで
  • AWS CDK は TypeScript を使ってみる

俺の AWS CDK

リポジトリ

以下にアップしました.

github.com

肝となる lib/tutorial01-stack.ts は以下の通り.

import cdk = require('@aws-cdk/core');
import s3 = require('@aws-cdk/aws-s3');
import cf = require('@aws-cdk/aws-cloudfront');
import iam = require('@aws-cdk/aws-iam');
import route53 = require('@aws-cdk/aws-route53');
import route53_targets = require('@aws-cdk/aws-route53-targets/lib');


export class Tutorial01Stack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const envName = this.node.tryGetContext('env');
    const domainName = this.node.tryGetContext('domain');

    // Create Bucket
    const websiteBucket = new s3.Bucket(this, `Tutorial01Infra-s3bucket-${this.stackName}`, {
      bucketName: domainName,
    });

    // Create CloudFront Origin Access Identity
    const OAI = new cf.CfnCloudFrontOriginAccessIdentity(this, `identity-${this.stackName}`,{
      cloudFrontOriginAccessIdentityConfig:{
        comment: `Tutorial01Infra-${this.stackName}`
      }
    });

    // Create Access Policy for S3 Bucket
    const webSiteBucketPolicyStatement = new iam.PolicyStatement({effect: iam.Effect.ALLOW});
    webSiteBucketPolicyStatement.addCanonicalUserPrincipal(OAI.attrS3CanonicalUserId);
    webSiteBucketPolicyStatement.addActions("s3:GetObject");
    webSiteBucketPolicyStatement.addResources(`${websiteBucket.bucketArn}/*`);
    websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);

    // Create CloudFront Distribution
    const distribution = new cf.CloudFrontWebDistribution(this, `Tutorial01Infra-cloudfront-${this.stackName}`, {
      originConfigs:[
        {
          s3OriginSource: {
            s3BucketSource: websiteBucket,
            originAccessIdentityId: OAI.ref
          },
          behaviors: [{ isDefaultBehavior: true}]
        }
      ],
      aliasConfiguration: {
        // ここは決め打ちごめんなさい
        acmCertRef: 'arn:aws:acm:us-east-1:01234567891:certificate/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
        names: [domainName],
        sslMethod: cf.SSLMethod.SNI,
        securityPolicy: cf.SecurityPolicyProtocol.TLS_V1_1_2016,
      }
    });

    // mydomain.com を決め打ちでごめんなさい
    const zone = route53.HostedZone.fromLookup(this, 'Zone', { domainName: 'mydomain.com' });
    new route53.ARecord(this, `Tutorial01Infra-route53-record-${this.stackName}`, {
      recordName: domainName,
      target: route53.AddressRecordTarget.fromAlias(new route53_targets.CloudFrontTarget(distribution)),
      zone
    });

    for (let cons of [websiteBucket, distribution]) {
      cdk.Tag.add(cons, 'Environment', envName);
      cdk.Tag.add(cons, 'Name', domainName);
    }

    // Output CloudFront URL
    new cdk.CfnOutput(this, 'CloudFrontURL', {value: `https://${distribution.domainName}/`})
    // Output Distribution ID
    new cdk.CfnOutput(this, 'DistributionId', { value: distribution.distributionId });
  }
}

TypeScript が初めてだったので見様見真似, 主に以下のリポジトリのサンプルを参考に実装しました.

github.com

というか, ほとんどやりたいことがそのまま実装されていたので写経的な感じでした. for とかが何も考えずに使えるのってプラグラム言語だなーという気持ちになりました.

デプロイ

AWS CDK はすでに導入済みという前提で...

$ npm run build
$ cdk synth --context env=foo --context domain=foo.example.com
$ cdk deploy --context env=foo --context domain=foo.example.com

引数として, 値をコードに渡す場合には --context オプションを使います.

以上

これから頑張って AWS CDK を習得していきたいと思いますが, 取り急ぎ, チュートリアルでした.

2019 年 12 月 03 日 (火)

ジョギング

  • 久しぶりに山王公園を 40 分程度
  • 懸垂 x 8 回
  • ズームフライ 3 で走ったけど, 今度は右踵が痛くなる... こまったーーーー

ギョーム

  • Aurora と現在のシステムについて学ぶことが多い一日だった
  • 色々とやるべきことが見えてきた感じなのでもうひと山頑張ろうと思う
  • AWS CDK で初めてインフラを作ってみた

夕飯

  • 連日の鍋
  • 今日は仕事帰りに俺たちのサニーに立ち寄って鍋の具材を購入
  • 自宅に戻って鍋を実装, 奮発してタラとか鯛のすり身を入れたので明日からはお茶漬けの生活

2019 年 12 月 02 日 (月)

ジョギング

  • 完全に寝坊でお休み

ギョーム

  • 終日バタバタしていた感, 疲れた
  • 新プロジェクトのインフラ構築に AWS CDK を採用してみた, チュートリアル等を参考にしながら S3 + CloudFront 構成がシュッと書けた

ONE TEAM

今年の流行語大賞ONE TEAM になったらしいけど, この言葉を拡大解釈して, 誰も幸せにならないような状態にならないことを祈る. テレビの街頭インタビューで新橋の酔っぱらいが良いことを言っていたので引用.

ONE TEAM って, 実現が難しいからこそ良い感じに響くんじゃないんすかね

まさにそう. 酔っ払っている割には良いことを言うなって思った.

夕飯

  • やっぱり冬は鍋, 茅の舎の出汁で取ると最後まで美味しい鍋が出来る
  • 温まった

2019 年 12 月 01 日 (日)

ジョギング

  • おやすみ

mini ツーリング

九酔峡からの九重夢大吊橋, 長者原, 牧ノ戸峠等をグルーっとめぐる mini ツーリング. MINI 博多を介して出会ったメンバーは老若男女問わず一緒に過ごしていてとても楽しいメンバーで一日楽しむことが出来た.

俺の .circleci/config.yml を晒す 〜 S3 バケットに静的コンテンツをアップロードする 〜

tl;dr

俺の .circleci/config.yml を晒すシリーズ第一弾. もしかしたら, 第二弾は無いかもしれませんがご容赦ください. そして, この記事は YAMAP エンジニア Advent Calendar 2019 の三日目の記事になる予定です.

qiita.com

実現したいこと

  • ソースコードGithubリポジトリに存在している
  • 成果物は S3 バケットにアップロード(S3 バケットは CloudFront 経由でコンテンツを配信することを想定)
  • development ブランチに push した場合には, 開発環境の S3 バケット (your-development-bucket-name) にアップロードされるようにします
  • production ブランチに push した場合には, プロダクション環境の S3 バケット (your-production-bucket-name) にアップロードされるようにします
  • 成果物ディレクトリ以下の一部のディレクトリはアップロード不要なので除外されるようにします

俺の .circleci/config.yml

以下のように書きました. orbs を使って成果物をアップロードするプロセスはガチャガチャコードを書く必要がなくなっています. また, persist_to_workspace を使って build ジョブから deploy ジョブに成果物を共有しています.

version: 2.1

orbs:
  aws-s3: circleci/aws-s3@1.0.11

executors:
  default:
    docker:
      - image: circleci/node:12.12
      - image: circleci/python:2.7

jobs:
  build:
    executor: default
    working_directory: ~/repo
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "path/to/package.json" }}
            - v1-dependencies-
      - run:
          name: Install dependencies
          command: |
            # Install dependencies commands
            echo "export PATH=$PATH:$(pwd)/path/to/node_modules/.bin" >> $BASH_ENV
      - save_cache:
          paths:
            - build/node_modules
          key: v1-dependencies-{{ checksum "path/to/package.json" }}
      - run:
          name: Build 
          command: |
            # Build 
      - persist_to_workspace:
          root: /home/circleci/repo
          paths:
            - your/path/*
  deploy:
    executor: default
    steps:
      - attach_workspace:
          at: /home/circleci/repo
      - checkout
      - run:
          name: list files
          command: |
            ls -l /home/circleci/repo/
yo
      - run: 
          name: select bucket
          command: |
            if [ "${CIRCLE_BRANCH}" == "production" ]; then
                echo 'export S3_BUCKET=your-production-bucket-name' >> $BASH_ENV
            else
                echo 'export S3_BUCKET=your-development-bucket-name' >> $BASH_ENV
            fi
      - aws-s3/sync:
          from: /home/circleci/repo/your/path
          to: s3://${S3_BUCKET}
          overwrite: true
          arguments: >
            --delete
            --exclude /home/circleci/repo/your/path/ignore1
            --exclude /home/circleci/repo/your/path/ignore2

workflows:
  version: 2
  build-and-deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          filters:
            branches:
              only:
                - master
                - production
                - development

orbs を使うといちいち aws cli を駆使することなく YAML の設定だけで (アクセスキーとシークレットアクセスキーを環境変数に設定しておく必要がありますが) S3 バケットにファイルアップロードすることが可能となります. 出来るだけ orbs を使うようにしたいですよね.

こんな感じで

俺のしょぼい .circleci/config.yml を出来る範囲で晒していこうと思います.