アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

CDKv2 を触って既存のCDKv1プロジェクトをマイグレーションしてみた

morishitaです。

アクトインディのインフラの多くは AWS CDK を利用して構築しています。
それについていくつかのエントリで紹介してきました。

tech.actindi.net

そして 2021 年 12 月に初のメジャーアップデートとなる v2.0.0 がリリースされました🎉。

github.com

ちなみに 2022/02/04 現在の最新バージョンは v2.10.0 です。

今回は CDKv2 を触ってみて、既存プロジェクトをマイグレーションしてみたので紹介します。
なお、本エントリは Typescript 前提です。

まずは触ってみる

とりあえず、CDKv2 を触る最初の一歩として、CDK プロジェクトを初期化してみます。

❯ cd aws-cdk-init-sample
❯ npx cdk@2 init --language=typescript
npx: 213個のパッケージを7.157秒でインストールしました。
Applying project template app for typescript
# Welcome to your CDK TypeScript project!

This is a blank project for TypeScript development with CDK.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

 * `npm run build`   compile typescript to js
 * `npm run watch`   watch for changes and compile
 * `npm run test`    perform the jest unit tests
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template

Initializing a new git repository...
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint:   git config --global init.defaultBranch <name>
hint: 
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint: 
hint:   git branch -m <name>
Executing npm install...
npm WARN deprecated querystring@0.2.0: The querystring API is considered Legacy. new code should use the URLSearchParams API instead.
npm WARN deprecated uuid@3.3.2: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated sane@4.1.0: some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added
npm WARN deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated
npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm WARN deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated
npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN aws-cdk-init-sample@0.1.0 No repository field.
npm WARN aws-cdk-init-sample@0.1.0 No license field.

✅ All done!

CDKv1 のときと同様、実行したディレクトリに package.json とサンプルコードなどが作られたのち、npm install が実行されます。
すでに Git リポジトリも初期化された状態です。
デフォルトではメインブランチ名は master となっています。v2 になっても main にはなってないんですね。

出来上がるファイル群は次の通りです。これも CDKv1 と変わりません。

.
├── bin
│   └── aws-cdk-init-sample.ts
├── lib
│   └── aws-cdk-init-sample-stack.ts
├── node_modules
├── test
│   └── aws-cdk-init-sample.test.ts
├── README.md
├── cdk.json
├── jest.config.js
├── package-lock.json
├── package.json
└── tsconfig.json

ちなみに、上記の例ではわかりやすく npx cdk@2 init@2 でバージョンを指定していますが、バージョン指定なしでも構いません。
ちょっと前まではバージョン指定なしの npx cdk init では CDKv1 で初期化されたんですが、CDKv2 を使うようにアップデートされたようです。

cdk init を比べてみる

ざっと見た感じ、CDKv1 で init したものと大きくは変わらないようです。
もう少し詳しく見るために CDKv1 、CDKv2 それぞれで初期化した結果できたファイルと比較してみます。
わかりやすく比較するために npx cdk@1 init を使って作ったリポジトリに npx cdk@2 init で作ったファイルを上書きコピーしてプルリクエストを作りました。

github.com

違いがあるのは次のファイルでした。

  • package.json
  • package-lock.json
  • cdk.json
  • bin/aws-cdk-init-sample.ts
  • lib/aws-cdk-init-sample-stack.ts
  • test/aws-cdk-init-sample.test.ts

package.json

まずは package.json を見てみます。

f:id:HeRo:20220204184556p:plain
package.json

aws-cdk2.10.0 (2022/02/04 時点の最新バージョン) になっています。
そして @aws-cdk/** が廃止され aws-cdk-lib に集約されていますね。
さらに
constructs* が追加されています。
AWS Cloud Development Kit v2 開発者プレビューのお知らせ | Amazon Web Services ブログ に記述されているとおりです。

cdk.json

f:id:HeRo:20220204184953p:plain
cdk.json

cdk.json は CDK プロジェクトの動作設定をするファイルですが、あんまり自分で意識して編集しているファイルではないかもしれません。
context には各種機能の動作を変更する Feature Flag が設定されています。

CDKv2 では context 以下に追加はなく削除されているものがあります。 AWS Cloud Development Kit v2 開発者プレビューのお知らせ | Amazon Web Services ブログによると「v1 で作成された Feature flags はすべてデフォルトで有効になっているためです」とのことなので気にしなくても良さそうです。
CDKv2 に残っているものもデフォルトは有効だけど、無効にできるものが残されているようです。

Typescript ファイル

Typescript のコードは import 部分が変更されているだけです。
package.json で見たようにモジュール構成が整理されたためですね。

主な違いは次の通りです。

CDKv1 CDKv2
@aws-cdk/core aws-cdk-lib
@aws-cdk/XX aws-cdk-lib/XX

基本的には importfrom@aws-cdk が含まれているところを直していけば良さそうです。

CDKv1 から CDKv2 へのマイグレーション

さて、ざっと CDKv2 について確認したところで、既存の CDKv1 のプロジェクトを CDKv2 にマイグレーションしてみました。

マイグレーションの手順

マイグレーションは次の手順で行いました。

  1. CDK を v1.x.x の最新バージョンにアップデートする
  2. 非推奨なメソッド等を利用している部分を修正する
  3. cdk diff でデプロイ環境と差がないことを確認する
  4. cdk.jsoncontext をすべて削除する
  5. package.json を変更する。
  6. コードを修正
  7. cdk diff で差分の有無の確認

以下にやってみて感じたポイントを記します。

1. CDK を v1.x.x の最新バージョンにアップデートする

どうせ CDKv2 にアップするなら最新版にしたいところ。
古い CDKv1 から一気に CDKv2 の最新版にマイグレーションした場合、不具合が出ても CDKv2 に移行したためかどうか判別しづらくなります。
なので先に CDKv1 の最新にアップデートしておきます。

また、CDK のテストに利用される @aws-cdk/assert は deprecated になっており、@aws-cdk/assertionsへの移行が促されています1

例えば、CDK のアップデート時の変更を検出しやすいようにスナップショットテストを実装することがあるかと思います。 @aws-cdk/assertions に移行すると SynthUtils クラスが使えなくなるため、代わりに Template クラスを使って次の様に変更します。

- import { SynthUtils } from '@aws-cdk/assert';
+ import { Template } from '@aws-cdk/assertions';
import { App } from '@aws-cdk/core';
import { MyStack } from '../../lib/stacks/MyStack';

describe('MyStack', () => {
  test('Snapshot test', () => {
    const app = new App();
    const stack = new MyStack(app);
-   expect(SynthUtils.toCloudFormation(stack)).toMatchSnapshot();
+   expect(Template.fromStack(stack)).toMatchSnapshot();
  });
});

この変更前後で保存されているスナップショットファイルは変わりませんでした。
@aws-cdk/assert には CDKv2 に対応したバージョンも一応提供されていますが、これを機に @aws-cdk/assertions 移行しておくほうがいいと思います。

2. 非推奨なメソッド等を利用している部分を修正する

古いバージョンからアップデートすると利用していたクラスやメソッド、プロパティが非推奨になってしまっているかもしれません。
VSCode だと非推奨の利用箇所にワーニングが表示されるかと思います。
CDKv1 の API リファレンスに代わりに使うクラスやメソッドなどの記述があるかもしれません。それを参考に修正しておきましょう。
というのも、CDKv1 の非推奨は CDKv2 すべて削除されているとのことだからです。

3. cdk diff でデプロイ環境と差がないことを確認する

CDKv1 で最新にバージョンアップデートしたら、それをデプロイ環境にも反映しておきましょう。

デプロイしたら、念の為 cdk diff で差分が発生しないことを確認しておきます。
ここで差分をなくしておくと CDKv2 への移行後に差分が発生した場合に移行が原因だとわかります。
仮に差分の解消が難しいとしてももともとある差を知っておくと差分発生時の切り分けに役立つと思います。

ここまでが CDKv1 でやっておく作業です。

4. cdk.jsoncontext をすべて削除する

ここからが CDKv2 への移行のための変更です。

まずは cdk.jsoncontext をすべて削除します。
もし、今まで自分で設定していたものがあるなら、それらの値が CDKv2 で引き続き設定可能か?
あるいは別のフラグに変わっていないか?
あるとして意味が変わっていないかを確認して再設定してください。

5. package.json を変更する。

次のポイントを変更します。

  • *@aws-cdk/** モジュールをすべて削除する
  • devDependenciesaws-cdk のバージョンを "^2.10.0" にする
  • dependencies に次のモジュールを追加する
    • aws-cdk-lib ("^2.10.0")
    • constructs ("^10.0.0")

変更したら npm install or yarn install を実行してモジュールをインストールします。

7. コードを修正

コードの修正は主にモジュール構成の変更に伴う import の修正です。
基本は次の 2 つの置換です。

  • @aws-cdk/coreaws-cdk-lib に置換
  • @aws-cdk/aws-cdk-lib/ に置換

上記の置換でほとんど解決しますが、Construct クラスは aws-cdk-lib ではなく constructs モジュールからインポートすることに注意です。 例えば、CDKv1 で Construct, Stack をインポートしていた場合、CDKv2では次の様に 2 つの import に分かれます。

- import { Construct, Stack } from '@aws-cdk/core'; 
+ import { Construct } from 'constructs';
+ import { Stack } from 'aws-cdk-lib';

一通り修正したら、 cdk list やテストを実行して抜け漏れがないかを確認しましょう。
要修正箇所が残っていればエラーが発生すると思います。

import を修正してもエラーが発生する場合、次の原因が考えられます。

  • CDKv1 で非推奨なメソッド等を利用している
  • experimental なモジュール(@aws-cdk/aws-appsync など) を利用している

上記以外が原因で発生するエラーもあるかもしれません。CDKv2 の API リファレンスなどを参考に修正しましょう。

7. cdk diff で差分の有無の確認

ここまで修正してコンパイルエラーも出なくなったら、cdk diff でデプロイされているものとの差分を確認しましょう。

差がないのが理想ですが、どうしても発生する差分があるようです。 Migrating to AWS CDK v2 - AWS Cloud Development Kit (CDK) v2Troubleshooting には次の様に記載されています。

Expected changes include but are not limited to:

  • Changes to the CDKMetadata resource
  • Updated asset hashes
  • Changes related to the new-style stack synthesis, if your app used the legacy stack synthesizer in v1 (CDK v2 does not support the legacy stack synthesizer)
  • The addition of a CheckBootstrapVersion rule

「Changes to the CDKMetadata resource」というのは CDKv1 でもアップデートすると時々変わっていたやつだと思います。
「Updated asset hashes」は S3 に一旦置かれるもの(Lambda 関数のコード)などのハッシュ値が変わるということで、これまでの CDK のアップデートでも時々発生していたと思います。
「The addition of a CheckBootstrapVersion rule」は cdk diff の結果で次の様に出力される部分のことだと思います。

[+] Unknown Rules: {"CheckBootstrapVersion":{"Assertions":[{"Assert":{"Fn::Not":[{"Fn::Contains":[["1","2","3","4","5"],{"Ref":"BootstrapVersion"}]}]},"AssertDescription":"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."}]}}

この他に ParametersAssetParameters から始まるキーの定義とそれを参照している箇所がなくなっています。
おそらく Lambda 関数などをデプロイするときに一旦 S3 に置くのですが、そのファイルの情報を Parameters に定義して、それを参照する方法をやめた様です。

これらは仕方がないようなので、これら以外で差分が発生しているかを確認します。
ひょっとすると cdk.json でデフォルト有効となった次の Feature Flag に関係あるかもしれないので次の様に無効に設定すると解決するかもしれません。

{
  "context": {
    "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": false,
    "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": false,
    "@aws-cdk/aws-rds:lowercaseDbIdentifier": false,
    "@aws-cdk/core:stackRelativeExports": false
  }
}

理由不明の差分がなくなるまで調べますが、構築リソースの動作を変えるものでなければ無視してもいいかもしれません。

こうして原因不明の差分がなくなればマイグレーション完了です。cdk deploy でデプロイして動作確認しましょう。

まとめ

AWS CDK v2 がリリースされたので、CDKv1 からの変更点を確認しました。
そして既存の CDKv1 プロジェクトを CDKv2 にマイグレーションする手順とポイントを説明しました。

CDKv1 では利用する AWS のサービスに応じて @aws-cdk/** のモジュールを追加する必要がありました。
一方、CDKv2 では aws-cdk-libに集約されてモジュールの管理が格段に簡単になりました。
./node_modules/aws-cdk-lib/* 以下に利用してないサービスのモジュールもインストールされてしまうのが気になりますが、致し方なしか…。

さて、今は CDKv1 も更新が続いていますが、そのうち CDKv1 の更新は終了すると思われます。
マイグレーションによる差分発生も少ないうちに CDKv2 に移行しておくのがいいのかなと思います。 このエントリがその一助になれば幸いです。

参考

最後に

アクトインディではエンジニアを募集しています。

actindi.net

Elastic Beanstalk 応用編

morishitaです。

アクトインディではいこレポいこーよとりっぷなどいくつかのサービスで稼働環境として AWS Elastic Beanstalkを利用しています。

前回は Elastic Beanstalk で極々簡単な Web アプリをデプロイするして公開するまでを紹介しました。

tech.actindi.net

今回は少し発展的な使い方を紹介します。

もっといろいろ設定したい

前回はほとんど何も設定せず、デフォルトのまま Web アプリをデプロイしました。

Elastic Beanstalk には色々と設定できる項目があり、一旦、「環境」を作ると AWS の Web コンソールから設定できるようになります。
次の様な設定項目があります。

f:id:HeRo:20220125001832p:plain
「環境」の設定

お客様にサービスを提供するシステムを本番運用する場合、デフォルトのままではなく少なくとも次の項目は要件に応じて設定したいのではないでしょうか。

  • VPC
  • オートスケールやインスタンスのストレージの容量
  • ALB
  • デプロイポリシー
  • ログ出力
  • データベースとの接続

Web コンソールから設定しても構わないのですが、これらはすべて設定ファイルでも設定できます。
デプロイする Web アプリのディレクトリの直下に .ebextensions ディレクトリを作り、その中の拡張子 *.config のファイルが設定ファイルとして扱われます。
設定ファイルを利用するとソースコードの一部として変更管理できるのでオススメです。

sample-app/
├── .ebextensions/
│   ├── 01_vpc.config
│   ├── 02_asg.config
│   ├── 03_alb.config
│   └── 04_update.config
├── .elasticbeanstalk/
│   └── config.yml
├── .gitignore
└── docker-compose.yml

設定ファイルは1つにまとめる必要はなく、いくつに分けても構いません。

設定できる項目は「すべての環境に対する汎用オプション」に記されています。

具体的な設定例をいくつか見てみましょう。

デプロイするVPC、サブネットを指定する設定例

次の例はインスタンスを配置する VPC とその中のサブネットを指定する例です。
システムごとに専用の VPC を用意し、ネットワークを分離する場合には設定することになると思います。
設定は option_settings というキーの配下に定義していきます。

option_settings:
  aws:ec2:vpc:
    VPCId: 'vpc-XXXXXXXX'
    AssociatePublicIpAddress: 'false'
    Subnets: 'subnet-11111111, subnet-22222222'
    ELBSubnets: 'subnet-XXXXXXXX, subnet-ZZZZZZZZ'

AssociatePublicIpAddress: 'false' を設定し、Private サブネットにインスタンスを配置するように設定するとインターネットから直接接続できなくなり、よりセキュアにインスタンスを運用できると思います。

オートスケールやインスタンスのストレージの容量の設定例

次の設定はオートスケーリンググループと起動するインスタンスを設定する例です。
オートスケールする条件や起動するインスタンスのストレージなどを指定しています。

option_settings:
  aws:ec2:instances:
    EnableSpot: true # スポットインスタンスを利用するか否か
    InstanceTypes: 't3.small,t2.small'
    SecurityGroups: 'sg-00000000000000000'
    SpotFleetOnDemandBase: '2' # 必ずオンデマンドでプロビジョニングする台数
    SpotFleetOnDemandAboveBasePercentage: '33' # オンデマンドを利用する割合
  aws:autoscaling:asg:
    Cooldown: 300 # オートスケール処理の最低感覚
    MinSize: 2    # 最低サーバ台数
    MaxSize: 10   # 最大サーバ台数
  aws:autoscaling:launchconfiguration:
    RootVolumeType: 'gp3'   # ストレージボリュームの種類
    RootVolumeIOPS: '3000'  # ストレージボリュームの IOPS
    RootVolumeSize: '10'    # ストレージ容量(GB)
  aws:autoscaling:trigger: # オートスケールのトリガー条件
    BreachDuration: 5
    MeasureName: CPUUtilization
    Statistic: Average
    Unit: Percent
    UpperThreshold: 80
    UpperBreachScaleIncrement: 2
    LowerThreshold: 60
    LowerBreachScaleIncrement: -1

インスタンスに適用するセキュリティグループも上記の設定例では設定しています。
例えば RDS を利用する場合には通信を許可するように設定したセキュリティグループを適用する必要があります。
「環境」に紐づく RDS のデータベースも作成できますが、Web アプリを稼働する「環境」とそれが利用する DB はライフサイクルが同じでなかったりするので、別途作って接続するように設定したほうがいいかなと個人的に思います1

ALB の設定例

次の例は ALB のサーバ証明書やヘルスチェックについて設定しています。

option_settings:
  aws:elbv2:listener:443: # HTTPS を受けるリスナー設定
    ListenerEnabled: 'true'
    Protocol: HTTPS
    SSLCertificateArns: 'arn:aws:acm:ap-northeast-1:999999999999:certificate/00000000-aaaa-xxxx-zzzz-000000000000'
    SSLPolicy: 'ELBSecurityPolicy-2016-08'
  aws:elasticbeanstalk:environment:process:default: # ヘルスチェック設定
    DeregistrationDelay: '20'
    HealthCheckInterval: '30'     # ヘルスチェック間隔
    HealthCheckPath: /healthcheck # ヘルスチェックするパス
    HealthCheckTimeout: '25'
    HealthyThresholdCount: '3'
    UnhealthyThresholdCount: '5'
    Port: '80'
    Protocol: HTTP

デプロイポリシーの設定例

デプロイポリシーは「環境」を更新する場合にどの様にサーバに適用していくかです。
ローリングアップデートするのか? 既存サーバを更新するのか? 新しくインスタンスを起動して入れ替えるのか? などを指定します。

次の設定は全サーバ台数の 20% に当たる数のサーバを新たに起動し、新しいイメージや設定を適用し入れ替えながら更新する設定例です。例えば 10 台で運用している場合、2 台追加しては古いものを 2 台づつ減らしながら更新します。

option_settings:
  aws:elasticbeanstalk:command:
    BatchSize: 20
    BatchSizeType: 'Percentage'
    DeploymentPolicy: 'RollingWithAdditionalBatch'

Elastic Beanstalk のデプロイポリシーは「設定変更」に詳しく説明されているのでよく読んで運用に即した設定をすると良いと思います。

リソースを追加する

Elastic Beanstalk で構築されるリソースは EC インスタンスと ALB などですが、その他のリソースも同時に作りたい場合もあると思います。

Elastic Beanstalk は裏で CloudFormation スタックが動きます。
.ebextensionsconfig ファイルの中で CloudFormation テンプレートの断片を書けばそれに従って標準では作られないリソースを同時に作成可能です。

例えば、次の様な設定を追加すると SNS トピックを通知先とする CloudWatch アラームを追加します。

Parameters:
  AlermSNSTopic:
    Type: String
    Description: "AWS SNS Topic to Notify System Alert"
    Default: "arn:aws:sns:ap-northeast-1:999999999999:system-alert-notifier"

Resources:
  EBALBHealthyHostCountAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName:
        Fn::Join: [ "-", [Ref: AWSEBEnvironmentName, "EB", "ALB", "no", "healthy", "hosts"] ]
      AlarmDescription:
        Fn::Join: [ "", [Ref: AWSEBEnvironmentName, ": ALB no healthy backend hosts."] ]
      Namespace: AWS/ApplicationELB
      MetricName: HealthyHostCount
      Dimensions:
        - Name: LoadBalancer
          Value:
            Fn::GetAtt: [ "AWSEBV2LoadBalancer", "LoadBalancerFullName" ]
        - Name: TargetGroup
          Value:
            Fn::GetAtt: [ "AWSEBV2LoadBalancerTargetGroup", "TargetGroupFullName" ]
      Statistic: Maximum
      Period: 60
      EvaluationPeriods: 3
      Threshold: 1
      ComparisonOperator: LessThanThreshold
      AlarmActions:
        - Ref: AlermSNSTopic
      InsufficientDataActions:
        - Ref: AlermSNSTopic

Elastic Beanstalk が作成するリソースを参照する方法は Elastic Beanstalk が環境向けに作成するリソースを変更する に記載されています。詳しくはそちらを参照ください。

ログの設定

Elastic Beanstalk の設定項目にログがありますが、それらは ALB や Docker などから出力されるもので、Web アプリのアプリケーションログの出力に関するものではないです。

Web アプリのログ出力は docker-compose.yml で設定します。awslogs ドライバーを使って Cloudwatch logs 出力するのが最も簡単だと思います。

設定例を次に示します。

services:
  app:
    # 〜 中略 〜
    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: /aws/elasticbeanstalk/app
        awslogs-create-group: "true"
        tag: 'app-{{ with split .ImageName ":" }}{{join . "_"}}{{end}}-{{.ID}}'

この設定例は CloudWatch logs に /aws/elasticbeanstalk/app というロググループを作成しログを出力します。 tag の値がログストリームの名前になります。デプロイする Docker イメージ名を含んでいるのでデプロイするイメージ毎にストリームが切り替わる設定となります。

こんな感じで設定により結構柔軟に「環境」をカスタマイズできるのがわかるかと思います。

旧 Docker 環境では移行が促されている

以前より Elastic Beanstalk で Docker コンテナを運用している場合、AWS のコンソールで次の警告が表示されているかと思います。

f:id:HeRo:20220126180613p:plain
Amazon Linux 2 に移行を促す警告

以前はElastic Beanstalk で Docker コンテナを動かす環境は次の 2 種類でした。

  • Multi-container Docker running on 64bit Amazon Linux
  • Docker running on 64bit Amazon Linux

いずれも Deprecated となっており、これらで作られた環境では先の警告が出ています。
要は、Multi-container Docker running on 64bit Amazon Linux は非推奨になったので Docker running on 64bit Amazon Linux 2 に乗り換えてねってことです。
移行については次のページにまとまっています。

既存の「環境」の種類を変更できないので、新たな「環境」を Docker running on 64bit Amazon Linux 2 で作って入れ替えることになります。

移行の際には Dockerrun.aws.json という独自の JSON 形式で定義していたコンテナ構成を docker-compose.yml に変更する必要があります。 内容的には両フォーマットで大きな違いはないのでほとんどフォーマット変換だけだと思います。
一点、ログ設定だけはログストリームの指定方法が異なるので前述の設定例を参考にしてもらえればと思います。

docker-compose.yml に書き直したら次のコマンドで「環境」を作成します。

$ eb create <作成する環境名> -p docker

既存「アプリケーション」に「環境」を追加する場合、旧環境の種類がデフォルト設定されていると思うので -p docker オプションで指定します。
ひょっとすると ALB を使いたいのに CLB で「環境」が作られてしまうかもしれません。その場合は --elb-type application オプションも追加して指定すると良いです。

まだ、EOS の予定は示されてないですが、「非推奨ブランチには、予定されたリタイア日がある場合があります」とあるので放置しておくとそのうちいつまでに移行しなさいって事になると思います。早めに移行したほうが無難でしょう。

EB CLI の注意点

EB CLI を使っていて、いくつか気になる動きがあったので最後に記述します。
利用しているのは EB CLI 3.20.2 (Python 3.10.)です。

eb init は AWS_PROFILE が効かない

EB CLI は AWS CLI のクレデンシャルを利用します。
複数のプロファイルを使い分けていて、デフォルトプロファイル以外でコマンドを実行したい場合にはそのプロファイルを指定する必要があります。

コマンドを連続して実行する場合にはいちいち --profile オプションで指定するのは面倒なので AWS CLI では環境変数 AWS_PROFILE で設定しておくことができます。
EB CLI でも環境変数 AWS_PROFILE が基本的に使え、eb createeb deploy では AWS_PROFILE の設定が効きます。 しかし、eb init では効かないので --profile オプションで指定する必要があります。バグなのかなと思います。

eb コマンドはコミットしている状態をデプロイする

docker-compose.yml.ebextensions に置いた設定ファイルを Git で管理している場合、EB CLI はコミットしている状態をデプロイします。
最初、これに気づかずハマりました。多分、以前はそうじゃなかったと思うのですが…。

eb deploy コマンドには --staged オプションがあり、コミットしなくてもステージングしただけの変更をデプロイできるのですが、eb create コマンドにはこのオプションがないので実行前に予めコミットしておく必要があります。
これが割と不便なので、EB CLI に Git のコミット状態を無視して保存した状態の設定ファイルをそのままデプロイするオプションがあればいいのになと思いました。

ちなみに Git で管理されていない場合には保存されている設定ファイルがそのままデプロイされます。

まとめ

Elastic Beanstalk を .ebextensions の設定ファイルで設定する方法について紹介しました。 各種設定により要件に即した動作に調整できるだけでなく、リソースを追加も可能です。

Elastic Beanstalk の使い所ですが、結局クラスターを構成するすべてのインスタンスで同じコンテナ構成が起動します。 そのため様々な種類のコンテナを動かして、それぞれ稼働コンテナ数を調整したいような複雑なシステムの運用には向かないと思います。
しかし、モノリスな Web アプリを動かすようなシンプルなシステムを運用する環境としては構築が簡単で便利だと思います。

Docker の稼働環境としては今は過渡期で、Docker running on 64bit Amazon Linux 2 に移行が促されています。 それに伴いちょっと癖のある独自形式を学習する必要がなくなり docker-compose.yml になってより使いやすくなったと思います。

Elastic Beanstalk 自体に料金はかかりません。利用する EC2 インスタンスなどの AWS リソースに課金されるだけです。Heroku を検討するのだったら Elastic Beanstalk と比較してみてもいいかなと思います。

参考

最後に

アクトインディではエンジニアを募集しています。

actindi.net


  1. 「環境」に紐づけて DB を作成しても後で切り離すこともできるようです。ただ、Elastic Beanstalk のコンソールから RDS の DB を作成する画面では Aurora を選択できないようです。

Elastic Beanstalk 入門

morishitaです。

アクトインディではいこレポいこーよとりっぷなどいくつかのサービスで稼働環境として AWS Elastic Beanstalkを利用しています。

本エントリでは Elastic Beanstalk を使ってみる例として Docker コンテナで極々簡単な Web アプリを公開する流れを紹介しようと思います。

AWS Elastic Beanstalk とは

AWS Elastic Beanstalkは Web アプリケーションを稼働させるインフラをまるっと用意してくれるサービスです。

公式サイト「AWS Elastic Beanstalk(ウェブアプリの実行と管理)| AWS」では次の様に紹介されています。

AWS Elastic Beanstalk は、Java、.NET、PHP、Node.js、Python、Ruby、Go および Docker を使用して開発されたウェブアプリケーションやサービスを、Apache、Nginx、Passenger、IIS など使い慣れたサーバーでデプロイおよびスケーリングするための、使いやすいサービスです。

お客様はコードをアップロードするだけで、Elastic Beanstalk が、キャパシティのプロビジョニング、ロードバランシング、Auto Scaling からアプリケーションのヘルスモニタリングまで、デプロイを自動的に処理します。同時に、お客様のアプリケーションが稼動している AWS リソースの完全な制御を維持でき、いつでも基盤となるリソースにアクセスすることができます。

Elastic Beanstalk には追加料金はかかりません。アプリケーションを格納および実行するために必要な AWS のリソースに対してのみお支払いいただきます。

具体的にはアプリケーションの稼働環境としての EC2 インスタンス、ロードバランサーを用意し、オートスケールの設定もしてくれます。デプロイしたい Web アプリがあればすぐにデプロイして公開できる環境を一揃い用意してくれます。

各種プログラミング言語環境も便利ではあると思いますが、おすすめは Dcoker 環境です。これは Docker コンテナの上でアプリケーションを動作させる実行環境です。

Elastic Beanstalk でのアプリケーションの構造

Elastic Beanstalk では「アプリケーション」というリソースをまず作成します。
その「アプリケーション」の中に「環境」を作成して Web アプリケーションをデプロイします。
1 つの「アプリケーション」の中には複数の「環境」を作成できます。
例えば、本番環境の他にステージング環境などを作ることができます。

各「環境」はデプロイ繰り返してアプリの変更を反映していくことができます。そして、デプロイする毎に「アプリケーションバージョン」として記録され、どのバージョンがどの「環境」に現在デプロイされているのかが管理されます。もしデプロイしたバージョンに不具合があって戻したい場合、過去の「アプリケーションバージョン」を指定してサクッとデプロイし直したりできます。

この様なアプリケーションをいくつも作成できます。
図にするとこんな感じです。

f:id:HeRo:20220125001359p:plain
アプリケーションの構造

1つの「環境」を作って Web アプリを公開するまでの手順は大きく分けて次の3ステップです。

  1. 公開する Web アプリの Docker イメージと docker-compose.yml を用意する
  2. 「アプリケーション」を作成する
  3. 「環境」を作成する

では順に説明していきます。

EB CLI のインストール

上記のステップの説明に入る前に道具を用意します。
AWS の Web コンソールからも各種設定が可能ですが、 Elastic Beanstalk 専用の CLI が用意されているのでそれを使うのがおすすめです。
Web アプリをデプロイしていくのに先立ち EB CLI をインストールします。

macOS なら homebrew でサクッとインストールできます。

$ brew install awsebcli

なお EB CLI を実行する時の認証情報は AWS CLI のものを利用するのでそちらも設定しておきます1

公開する Web アプリの Docker イメージと docker-compose.yml を用意する

では、いよいよ Web アプリをデプロイしていきます。
そのためにはデプロイしたいWebアプリを含む Docker イメージを用意する必要があります。
イメージをビルドするところから説明すると少々長くなるので、今回そこは省略してAmazon ECR Public Galleryでイメージが公開されているパズルアプリ 2048 をデプロイしてみます。

実行するコンテナは docker-compose.yml で定義します。
アプリのルートとなるディレクトリを作って、その中に docker-compose.yml を作成します。
名前は何でもいいのですが、この例では sample-app という名前でディレクトリを作成してその中に作ります。

sample-app/
  └── docker-compose.yml

docker-compose.yml 中身はこんな感じです。

version: '2.4'
services:
  app:
    image: public.ecr.aws/l6m2t8p7/docker-2048:latest
    ports:
      - "80:80"
    mem_limit: 128m

Elastic Beanstalk にデプロイするための特殊な仕様追加などはなく、普通の docker-compose.yml です。 この例ではコンテナを1つだけ定義していますが、もちろん複数のコンテナを動かすことも可能です。

「アプリケーション」を作成する

docker-compose.yml を作成したら、sample-app ディレクトリでターミナルを開いて、eb init コマンドでアプリケーションを初期化(= 作成)します。

途中次を質問されます。この例ではそれぞれ次の様に選択します。

  • リージョン
    • => 適当に。例では東京リージョンを選択します。
  • アプリケーションの名前
    • => デフォルトではディレクトリ名になります。
  • プラットフォームの種類
    • => 3) Docker を選択
  • Docker プラットフォーム内の種類を選択
    • => Docker running on 64bit Amazon Linux 2 を選択。他の 2 つは Deprecated なので一択。
  • SSh での接続可否と keypair
    • => 必要に応じて設定する。この例では既存のキーを指定します。

実行例は次の通りです。

❯ eb init

Select a default region
1) us-east-1 : US East (N. Virginia)
2) us-west-1 : US West (N. California)
3) us-west-2 : US West (Oregon)
4) eu-west-1 : EU (Ireland)
5) eu-central-1 : EU (Frankfurt)
6) ap-south-1 : Asia Pacific (Mumbai)
7) ap-southeast-1 : Asia Pacific (Singapore)
8) ap-southeast-2 : Asia Pacific (Sydney)
9) ap-northeast-1 : Asia Pacific (Tokyo)
10) ap-northeast-2 : Asia Pacific (Seoul)
11) sa-east-1 : South America (Sao Paulo)
12) cn-north-1 : China (Beijing)
13) cn-northwest-1 : China (Ningxia)
14) us-east-2 : US East (Ohio)
15) ca-central-1 : Canada (Central)
16) eu-west-2 : EU (London)
17) eu-west-3 : EU (Paris)
18) eu-north-1 : EU (Stockholm)
19) eu-south-1 : EU (Milano)
20) ap-east-1 : Asia Pacific (Hong Kong)
21) me-south-1 : Middle East (Bahrain)
22) af-south-1 : Africa (Cape Town)
(default is 3): 9


Enter Application Name
(default is "sample-app"): 
Application sample-app has been created.
Select a platform.
1) .NET Core on Linux
2) .NET on Windows Server
3) Docker
4) GlassFish
5) Go
6) Java
7) Node.js
8) PHP
9) Packer
10) Python
11) Ruby
12) Tomcat
(make a selection): 3

Select a platform branch.
1) Docker running on 64bit Amazon Linux 2
2) Multi-container Docker running on 64bit Amazon Linux (Deprecated)
3) Docker running on 64bit Amazon Linux (Deprecated)
(default is 1): 1

Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization
Do you want to set up SSH for your instances?
(Y/n): 

Select a keypair.
1) my-aws-key
2) [ Create new KeyPair ]
(default is 1): 1

実行後、AWS コンソールを確認すると次の様にアプリケーションが作成されています。

f:id:HeRo:20220125001438p:plain
「アプリケーション」のリスト

また、sample-app ディレクトリには選択結果を保存した設定ファイルが作成されます。

sample-app/
├── .elasticbeanstalk/
│   └── config.yml
├── .gitignore
└── docker-compose.yml

config.yml の中身はこんな感じです。

branch-defaults:
  default:
    environment: sample-app-dev
    group_suffix: null
global:
  application_name: sample-app
  branch: null
  default_ec2_keyname: my-aws-key
  default_platform: Docker running on 64bit Amazon Linux 2
  default_region: ap-northeast-1
  include_git_submodules: true
  instance_profile: null
  platform_name: null
  platform_version: null
  profile: null
  repository: null
  sc: null
  workspace_type: Application

CLIによる環境作成

「アプリケーション」が作成できたので、いよいよ eb create コマンドで「環境」を作ってコンテナをデプロイします。

コマンド実行時に次の項目を尋ねられます。

  • 「環境」の名称
  • ロードバランサーの種類を聞かれます。
    • => ALB を利用したいので 2) application を選択
  • スポットインスタンスを利用するか
    • => 必要に応じて適当に。
  • インスタンスタイプ
    • => この例では無料枠に収まる t3.micro,t2.micro を指定

eb create コマンド実行例は次の通りです。

❯ eb create
Enter Environment Name
(default is sample-app-dev): 
Enter DNS CNAME prefix
(default is sample-app-dev): 

Select a load balancer type
1) classic
2) application
3) network
(default is 2): 2


Would you like to enable Spot Fleet requests for this environment? (y/N): y
Enter a list of one or more valid EC2 instance types separated by commas (at least two instance types are recommended).
(Defaults provided on Enter): t3.micro,t2.micro

Creating application version archive "app-220122_161704".
Uploading sample-app/app-220122_161704.zip to S3. This may take a while.
Upload Complete.
Environment details for: sample-app-dev
  Application name: sample-app
  Region: ap-northeast-1
  Deployed Version: app-220122_161704
  Environment ID: e-fnmkcyenrk
  Platform: arn:aws:elasticbeanstalk:ap-northeast-1::platform/Docker running on 64bit Amazon Linux 2/3.4.10
  Tier: WebServer-Standard-1.0
  CNAME: sample-app-dev.ap-northeast-1.elasticbeanstalk.com
  Updated: 2022-01-24 07:17:07.666000+00:00
Printing Status:
2022-01-24 07:17:06    INFO    createEnvironment is starting.
2022-01-24 07:17:07    INFO    Using elasticbeanstalk-ap-northeast-1-999999999999 as Amazon S3 storage bucket for environment data.
2022-01-24 07:17:29    INFO    Created target group named: arn:aws:elasticloadbalancing:ap-northeast-1:999999999999:targetgroup/awseb-AWSEB-DXXOTGRP890F/458e3c267aba54d9
2022-01-24 07:17:29    INFO    Created security group named: sg-0f2dd09c2badc9a0f
2022-01-24 07:17:45    INFO    Created security group named: awseb-e-fnmkcyenrk-stack-AWSEBSecurityGroup-197VD45J3TQRM
2022-01-24 07:18:31    INFO    Created Auto Scaling group named: awseb-e-fnmkcyenrk-stack-AWSEBAutoScalingGroup-R5X9LVVJYA2H
2022-01-24 07:18:31    INFO    Waiting for EC2 instances to launch. This may take a few minutes.
2022-01-24 07:18:31    INFO    Created Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:999999999999:scalingPolicy:220d602e-1461-4d09-9467-ba14e12ecbbc:autoScalingGroupName/awseb-e-fnmkcyenrk-stack-AWSEBAutoScalingGroup-R5X9LVVJYA2H:policyName/awseb-e-fnmkcyenrk-stack-AWSEBAutoScalingScaleDownPolicy-9H2VG3T76QRV
2022-01-24 07:18:31    INFO    Created Auto Scaling group policy named: arn:aws:autoscaling:ap-northeast-1:999999999999:scalingPolicy:fa2522b4-9460-4191-87c2-35df36d2bdff:autoScalingGroupName/awseb-e-fnmkcyenrk-stack-AWSEBAutoScalingGroup-R5X9LVVJYA2H:policyName/awseb-e-fnmkcyenrk-stack-AWSEBAutoScalingScaleUpPolicy-1BTC6KF1WN63W
2022-01-24 07:18:31    INFO    Created CloudWatch alarm named: awseb-e-fnmkcyenrk-stack-AWSEBCloudwatchAlarmHigh-1N9OM9QI0W0EF
2022-01-24 07:18:31    INFO    Created CloudWatch alarm named: awseb-e-fnmkcyenrk-stack-AWSEBCloudwatchAlarmLow-1I78W8RVKBR0U
2022-01-24 07:19:33    INFO    Created load balancer named: arn:aws:elasticloadbalancing:ap-northeast-1:999999999999:loadbalancer/app/awseb-AWSEB-C5P8NMUJTEM9/86fd65519f5f3b2b
2022-01-24 07:19:53    INFO    Created Load Balancer listener named: arn:aws:elasticloadbalancing:ap-northeast-1:999999999999:listener/app/awseb-AWSEB-C5P8NMUJTEM9/86fd65519f5f3b2b/ecb57231ccb8f7e8
2022-01-24 07:20:13    INFO    Instance deployment completed successfully.
2022-01-24 07:20:30    INFO    Application available at sample-app-dev.ap-northeast-1.elasticbeanstalk.com.
2022-01-24 07:20:31    INFO    Successfully launched environment: sample-app-dev

実行すると裏側で CloudFormation スタックが作成され次のリソース群を作成します。
VPC やサブネットはアカウントのデフォルトが利用されます。

  • オートスケーリンググループ
  • EC2 インスタンスとセキュリティグループ
  • ALB とターゲットグループとセキュリティグループ

作成中は AWS コンソールでも処理の様子を見ることができます。

f:id:HeRo:20220125001518p:plain
「環境」作成中

5分程度で処理は完了して「環境」が構築されます。

f:id:HeRo:20220125001554p:plain
作成された「環境」

デプロイされたアプリの URL が表示されているので、ブラウザからそれにアクセスすると 2048 が表示されます。

シンプルですが結構ハマるパズルアプリです2

f:id:HeRo:20220125001716p:plain
2048

なお、作成した「環境」は「アプリケーション」のリストにも表示されます。

f:id:HeRo:20220125001626p:plain
「環境」作成後の「アプリケーション」のリスト

どんなふうにデプロイされているのか?

SSH で構築された EC2 インスタンスに入ってどの様に動いているのか見てみます。

❯ ssh ec2-user@xx.xx.xx.xx -i ~/.ssh/my-aws-key.cer
The authenticity of host 'xx.xx.xx.xx (xx.xx.xx.xx)' can't be established.
ED25519 key fingerprint is SHA256:ht6XZNY0C0bbxLM1x/O14I+w9u6iET4VS0+NZ6Ft024.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'xx.xx.xx.xx' (ED25519) to the list of known hosts.
  _____ _           _   _      ____                       _        _ _
 | ____| | __   ___| |_(_) ___| __ )  ___  __ _ _ __  ___| |_ __ _| | | __
 |  _| | |/ _ \/ __| __| |/ __|  _ \ / _ \/ _\ | '_ \/ __| __/ _\ | | |/ /
 | |___| | (_| \__ \ |_| | (__| |_) |  __/ (_| | | | \__ \ || (_| | |   <
 |_____|_|\__,_|___/\__|_|\___|____/ \___|\__,_|_| |_|___/\__\__,_|_|_|\_\

 Amazon Linux 2 AMI

 This EC2 instance is managed by AWS Elastic Beanstalk. Changes made via SSH
 WILL BE LOST if the instance is replaced by auto-scaling. For more information
 on customizing your Elastic Beanstalk environment, see our documentation here:
 http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customize-containers-ec2.html

[ec2-user@ip-172-31-19-97 ~]$ sudo -i
[root@ip-172-31-19-97 ~]# docker ps
CONTAINER ID   IMAGE                                        COMMAND                  CREATED       STATUS       PORTS                               NAMES
3114193194cb   public.ecr.aws/l6m2t8p7/docker-2048:latest   "nginx -g 'daemon of…"   2 hours ago   Up 2 hours   0.0.0.0:80->80/tcp, :::80->80/tcp   current_app_1

ちゃんと指定したイメージでコンテナが稼働していますね。

また、docker-compose.yml/var/app/current/ ディレクトリに配置されています。

[root@ip-172-31-19-97 ~]# cat /var/app/current/docker-compose.yml
version: '2.4'
services:
  app:
    image: public.ecr.aws/l6m2t8p7/docker-2048:latest
    ports:
      - "80:80"
    mem_limit: 128m

まとめ

今回は 2048 のデプロイを通して、Elastic Beanstalk で Web アプリを公開するまでの流れを紹介しました。

結構、少ない手順でサーバを立てて、ブラウザでアクセスできるようにできるのがわかってもらえたかと思います。

しかし、商用のサービスシステムの稼働環境として利用する場合には次の様なことはどうするのか?と気になるかもしれません。

  • デフォルト VPC でなく、サービス専用の VPC にデプロイしたい
  • オートスケールの条件を設定したい
  • ログはどう出力するのか?
  • DB は?
  • etc.

次回、これらの設定について紹介したいと思います。

最後に

アクトインディではエンジニアを募集しています。

actindi.net

参考


  1. aws configure を使用したクイック設定

  2. とりあえず 2048 をやってみたいという方はこちらをどうぞ。2048

Amazon ECR Public Gallery から Docker Official Images を利用する

morishitaです。

サーバーサイドの開発では Docker コンテナを利用することが一般的になりました。
本番環境はもちろん、開発環境も Docker Compose などコンテナで構築することが多いのではないでしょうか。
その際、Ruby や Node.js といったプログラミング言語や MySQL や Redis などのミドルウェアは Dockerhub で提供されている Docker Official Imagesをそのまま利用したり、自前のイメージをビルドする際のベースに使うことが多いと思います。

一方、2020 年 11 月から Dockerhub からイメージを Pull する際に回数に制限がかかるようになりました。
ログインしない状態だと IP アドレスあたり 6 時間で 100 回までとなっています。
便利で利用頻度も高い Docker Official Images を Pull する場合にもその制限の範囲内でしか Pull できません。

個人だとそんな回数 Pull しないだろうし厳しい制限ではないかもしれないですが、AWS などのパブリッククラウドでは他のユーザと共有している IP アドレスからイメージを Pull する可能性があり、意外と制限に引っかかったりします。
例えば、AWS CodeBuild で Docker イメージを Pull するような処理をしていると、まれに制限に引っかかったります1

Amazon ECR Public Gallery から Docker Official Images が Pull 可能に!

2021 年 11 月の re:Invent で AWS と Docker 社からそれぞれ次の発表がありました。

aws.amazon.com

www.docker.com

どちらも Dockerhub 上で提供されてきた Docker Official Images が Amazon ECR Public からも Pull できますよという内容です。

AWS によると「Amazon ECR Public から任意の AWS リージョンにイメージをプルするお客様は、事実上無制限にダウンロードできます」2とのことです。
もともと Amazon ECR Public は「任意の AWS リージョンの AWS コンピューティングリソースにデータを転送する際には、コスト無しで無制限の帯域幅を得ることができます」3という無料枠がありました。この枠内で Docker Official Images を Pull できるということのようです。

ただし、AWS リージョン以外から AWS アカウントへのログインなしにイメージを Pull する際には 500GB/月、ログインしても 5TB/月に制限されますのでご注意を。

ECR Public での Docker Official Images のリストは次のページで確認できます。

gallery.ecr.aws

Ruby、PHP、Python、Node といったプログラミング言語、MySQLや PostgreSQL、Tomcat といったミドルウェアから Wordpress や Drupal といった CMSアプリケーションまで色々ありますね。
まあ、もともと Docker Official Images として存在していたものなのんですが。

Amazon ECR Public Gallery からイメージを Pull してみる

弊社開発でよく使う Ruby で試してみました。
Dockerhub、ECR Public それぞれの Web ページの URL は次の通りです。

まずは Dockerhub から Pull してみます。

❯ docker pull ruby:3.1.0
3.1.0: Pulling from library/ruby
94a23d3cb5be: Pull complete 
ac9d381bd1e9: Pull complete 
aa9c5b49b9db: Pull complete 
841dd868500b: Pull complete 
d4bb9078a4a2: Pull complete 
a0c2758411b7: Pull complete 
0e64e6848085: Pull complete 
1b8402df0f54: Pull complete 
Digest: sha256:e806e97b4a00c6b00f8baf36331f8798ab2d60b7d49f43a79577f7e7e3ab2623
Status: Downloaded newer image for ruby:3.1.0
docker.io/library/ruby:3.1.0

続いて ECR Public から Pull してみます。
Dockerhub でのイメージにの前に public.ecr.aws/docker/library/ をつければ ECR Public でのイメージ名になるようです。

❯ docker pull public.ecr.aws/docker/library/ruby:3.1.0
3.1.0: Pulling from docker/library/ruby
Digest: sha256:e806e97b4a00c6b00f8baf36331f8798ab2d60b7d49f43a79577f7e7e3ab2623
Status: Downloaded newer image for public.ecr.aws/docker/library/ruby:3.1.0
public.ecr.aws/docker/library/ruby:3.1.0

表示される Digest の値が同じなので同じイメージだとわかります。
そもそも改めてダウンロードもされてないですね。

もう少しわかりやすく確認してみましょう。

❯ docker images --digests |grep ruby
ruby                                                           3.1.0     sha256:e806e97b4a00c6b00f8baf36331f8798ab2d60b7d49f43a79577f7e7e3ab2623   802a366aecf3   11 days ago   839MB
public.ecr.aws/docker/library/ruby                             3.1.0     sha256:e806e97b4a00c6b00f8baf36331f8798ab2d60b7d49f43a79577f7e7e3ab2623   802a366aecf3   11 days ago   839MB

同じですね。全く同じイメージが取得できることが確認できました。

Node.js や MySQL でも試してみましたが、いずれも同じ Digest の同じイメージが Pull できました。
まあ、当たり前といえば当たり前なのですが。

気づいた点

2022 年 1 月時点でDockerhub の Docker Official Images を見ると 172 種類のイメージが公開されているようです。
しかし、ECR の Docker の Publisher ページ には 139 種類しかないようです。
今後、差がなくなっていくのかもしれないですが、現時点ではすべての Docker Official Images が ECR Public からも Pull できるというわけではないようです。

また、古いイメージも ECR Public からは Pull できないようです。
Ruby、Node.js、MySQL のそれぞれのレジストリから Pull できる最も古いバージョンは次の通りです。

プロダクト Dockerhub ECR Public
Ruby ruby:2.0.0-p647 public.ecr.aws/docker/library/ruby:2.5.9
Node.js node:4.2.1 public.ecr.aws/docker/library/node:10.23-slim
MySQL mysql:5.5.49 public.ecr.aws/docker/library/mysql:5.6.50

これらよりメジャーバージョン、マイナーバージョンが新しいものでも古いパッチバージョンが ECR Public にはなかったりします。

そのような古いバージョンはアップデートすべきとは思うのですが、やむを得ず使っているという場合もあるかと思います。
どうしても古いバージョンが必要な場合には ECR Public にないかもしれないということに注意が必要です。

まとめ

Docker Official Images を Amazon Elastic Container Registry Public から Pull できるようになりました。
ECR Public を利用することで AWS リージョンから Docker Official Images を Pull する場合には制限がなくなります。
AWS と Docker 社が公式に協力しているので安心して利用できそうです。

また、ECR Public の利用が広がれば Dockerhub からイメージを Pull する総回数も減るでしょう。
そうすれば Codebuild などで Docker Official Images 以外のイメージを Dockerhub から Pull する場合にも制限に引っかかりにくくなっていくかもしれません。

参考

最後に

アクトインディではエンジニアを募集しています。

actindi.net


  1. Docker イメージのビルドや Rspec の実行で多用しています。RSpec を実行する際には MySQL や Redis などのイメージを Pull して利用しています。

  2. https://aws.amazon.com/jp/blogs/news/docker-official-images-now-available-on-amazon-elastic-container-registry-public/

  3. https://aws.amazon.com/jp/ecr/pricing/

私のAWSを勉強する道

こんにちは、クアンです。

今年からRubyコードを書くだけではなくAWSのサービスをやりたいので、勉強をはじめました。

この投稿の内容は私の勉強した方法です。

初めに困ったこと

AWSは3つのパスがあります:アーキテクト、デベロッパーとシステムオーパーレーターのパスです。

  • ソリューションアーキテクト: AWS における分散システムの可用性、コスト効率、高耐障害性およびスケーラビリティの設計に関するスキルを認定する
  • デベロッパー: AWS ベースのアプリケーションの開発や保守に関するスキルを認定する
  • SysOps アドミニストレーター: AWS でのデプロイ、管理、運用に関するスキルを認定する

3つの初めのステップは「クラウドプラクティショナー」です。「クラウドプラクティショナー」とは入門・基礎レベルです。このレベルは、業務未経験者や非エンジニアにクラウドのクラウドの概念を教えます。

大学の先輩と友達のアドバイスは、ITベースがあったら、「クラウドプラクティショナー」をスキップしても大丈夫とのことでした。

ですから、すぐに「アソシエイト」レベルを入りました。でも、アーキテクト、デベロッパーとシステムオーパーレーター、どのパスを選ぶのか考えました。

3つのパスの内容、目的を見て、まずAWSシステムの一般的な知識を分かりたかったので、ソリューションアーキテクトのパスを決めました。本気の勉強は必ず目標を設定しなければならないから、「AWS 認定 ソリューションアーキテクト – アソシエイト」の試験を受けました。

勉強した方法

やってみた勉強方法

まず、AWSのホームページのドキュメントを読みました。結果はあまり分かりませんでした。

次の方法はYoutubeで「AWS Solution Associate Certificate」のビデオを見ました。多くの面白いものが紹介されましたが、知識のなかった私にはやはり馬の耳に念仏でした。

youtu.be

その次に試したのは、AWSのホームページのコースでした。このコースは全てビデオでしたから、Youtubeのコースと同様によくわかりませんでした。

三種類の方法の失敗から、有料のオンラインコースを受けると決めました!

Coursera で勉強したこと

AWSはまだ初心者だったので、次のコースを受けました。

www.coursera.org

このコースはとても分かりやすかったし、メモできるし、そして1週間無料でした。

でも、毎月¥4062ちょっと高かったです。ですから、それを節約する私の作戦はコースの全部で5週間のコースを5日間で終わらせることでした。

f:id:gryqhon:20210111191655p:plain

そのために、毎夜2時から3時ぐらい勉強したし、大事な定義をメモしました。例えば:

f:id:gryqhon:20210111192950p:plain

コースの終了後、閲覧できなくなるので、復習のため、授業の内容をきちんとメモしたし、パソコンに保存しました。

https://www.coursera.org/learn/aws-fundamentals-going-cloud-native/home/notes

Courseraのメモ機能を使っていることだけではなく、私はコースフォーラムにもよく参加しています。フォーラムで他の参加者の意見、質問、答えなどを読んだり、できればいくつかの質問を答えたりしています。

Courseraの勉強が終わってから、本を読みました。

読んだの本

Joe Baron、Hisham Baz、Tim Bixler、Biff Gaut、Kevin E.Kelly、Sean Senior、John StamperのAWS Certified Solutions Architect Official Study Guide Associate Examを読みました。

この本はとてもわかりやすいと思います。なぜかというと、この本の内容はAWSのそれぞれのサービスについて別れられて、イラストが多くて、記述があまり複雑ではないからです。

インターネットのソース:

勉強したあと、このページで練習しました:

https://www.examtopics.com/exams/amazon/

SAA-C01やSAA-C02という2つの無料模擬試験があります。多くの大切な質問があります。

模擬試験以外にも、QuizletのAWS練習課題もやりました。

VPC、RDS、EC2、S3とIAMのFAQsを探しました。もし答えがあれば、その答えを読んで、勉強したことと確認します。答えがない場合、ちゃんと答えて頑張ります。

このマインドマップもとても便利です:

GitHub - gitvani/aws-mindmap: The mindmaps for AWS services to get AWS Certificates easier.

試験の戦術

最終目的はインフラのタスクを担当できるようになることです。

その第一歩としての目標はこの試験を合格することです。

そのために必勝の戦術を考えました。

(もちろん、これは認定試験合格等短期目標を達成するためだけです。合格しても、勉強はずっと続きます!)

私が点数を取りやすいと考えたのは次の質問です:

  • AZについて質問:インスタンス数のソルション場合、ぜひ気にするべきは最低数です。サブネット質問の場合、multiAZサブネットを答えを取り去します(1AZに数のサブネット可能です。1サブネットは1AZだけです)。

  • 一番評価のS3サービスを選ぶ:必要なことはresponse時間条件と値段です。あと、覚えるものは値段-response時間下順番は:Standard→Standard IA→One Zone IA→Glacier→S3 Glacier Deep Archive。

  • VPC:頭の中にいつもVPCを書けることはキーです。

  • AWS SSO:私にとってSSOの質問はポイントを取れるです。実行の経験はアクトインディのAWSはSSOがあるから、会社メールアカウントでAWS consoleをログインできます

休むことも必要

ずっと勉強するのはかえって良くないです。 勉強の合間に、ドラムとかカホンとかを叩いてリラックスしました。そのおかげで体力も集中も持続してよく勉強できました。

結果

全然実務の経験が無かったのですが、2ヶ月間勉強して合格できました!

www.credly.com

ま、この認定は始まりにすぎないです。これからAWSに頑張ります!

最後に

アクトインディではエンジニアを募集しています。

actindi.net

CodeBuildのレポート機能を使ってRSpecの結果を見る

morishitaです。

前回のエントリで、CodeBuild 上で RSpec を実行する環境について紹介しました。

tech.actindi.net

その中で RSpec の結果を CodeBuild のレポートで確認できるようにしてみたらなかなか良かったのでそれについて紹介します。

CodeBuild のテストレポート機能とは

1年ほど前に CodeBuild に追加された機能で、CodeBuild で実行したテスティングフレームワークの実行結果を管理し、見やすく表示する機能です。

例えば、複数回のテストの実行時間やエラー数をグラフにして表示できます。それを見れば、テストケースが増えてきたとか、実行時間がどれくらいとか傾向がわかります。

f:id:HeRo:20201222183141p:plain
テスト結果トレンド

また、1回のテスト実行結果を次のように表示できます。

f:id:HeRo:20201222183235p:plain
1回のテスト結果

主に Java 系のテスティングフレームワークをサポートしていますが、RSpec でも利用可能です。

RSpec の結果を表示できるようにする

大前提として CodeBuild 上で RSpec を実行しているということが必要となります。
その上でこのレポート機能を利用するためにすることは次の2つです。

  1. sj26/rspec_junit_formatter 導入して RSpec の結果を JUnit 形式でも出力する
  2. buildspec.yml にレポート設定を追加する

これらをもう少し詳しく説明します。

RSpec の結果を JUnit 形式でも出力する

rspec_junit_formatterは RSpec の結果フォーマッタの1つです。RSpec の結果を JUnitXML 形式で出力できます。 JUnitXML は CodeBuild のレポート機能でサポートされているので表示できるようになるというわけです。
RSpec では複数の形式で結果出力可能なので、他の形式での出力も必要なら、そちらはそのままにして JUnitXML 形式での出力も追加すればいいです。

Rails アプリであれば、まず Gemfilerspec_junit_formatter を追加します。

Gemfile

group :test do
  gem 'rspec'
  gem 'rspec_junit_formatter' # <= 追加
end

そして、spec/spec_helper.rb に次の設定を追加すれば出力されるようになります。

spec/spec_helper.rb

RSpec.configure do |config|
  config.add_formatter('RspecJunitFormatter', 'junit_format/rspec_xml')
end

出力先は任意ですが、できれば結果ファイルだけを保存する専用ディレクトリに分けたほうがいいかと思います。そのほうが buildspec.yml のレポート設定で結果ファイルを指定する際に楽です。

buildspec.yml にレポート設定を追加する

出力した RSpec の結果をレポート機能に読み込ませるために buildspec.yml には次の設定を追加します。

reports:
  rspec:
    files:
      - 'junit_format/*'
    file-format: JUNITXML

files: で結果ファイルを指定するのですが、ファイル名まで指定する必要はありません。junit_format/* のように glob パターンで指定できます。こうすると結果ファイルを出力するディレクトリ以下全部を対象とするようなざっくりした指定が可能です。

結果ファイルが複数でも大丈夫

テストケースが増えてくるとtest-queueなどを使って並行実行してテスト全体の実行時間を短縮することもあるでしょう。並行実行するとテスト結果は各スレッドごとにバラバラに出力されたりします。
CodeBuild のレポート機能は出力結果が複数ファイルに分かれていてもまとめて読み込んでくれます。
前述のように glob パターンにマッチする結果ファイルを全部読み込んでレポートを作成してくれるので自前でマージする必要はありません。

結果の確認

1 回のテストの結果は次のように表示されます。

f:id:HeRo:20201222183358p:plain
テスト結果

テストの総数やエラーの数などがドーナツグラフで表示されます。
レポート時間は複数スレッドで並行実行しているので全スレッドの合計値になっているのか実際よりかなり大きな数字が表示されてしまっています。実際には十数分しかかかっていません。
グラフの下には各テストケースの一覧が表示されます。どのテストで失敗したのかがわかります。
更に、失敗したテストケースを開くと次のような詳細が表示されます。ちょっとモザイクが多いのでわかりにくそうに見えますが、どのように失敗したのかわかります。コード修正に必要な情報はここで得られます。

f:id:HeRo:20201222183442p:plain
failしたテストの結果

CodeBuild のレポート機能を使う前はアーティファクトに含まれるログファイルを開いて確認していました。それに比べるとずっと確認しやすくなりました。

SimpleCov にも対応しているけれど…

CodeBuild のレポート機能はテストレポートだけでなくコードカバレッジレポートもできます。

Ruby でテストのコードカバレッジを計測する場合、simplecovが使われるのではないかと思います。
SimpleCov はデフォルトで JSON 形式の結果ファイル .resultset.json を出力します。CodeBuild のレポート機能はこのファイルをサポートしています。したがって gem や設定の追加は不要です。

CodeBuild の設定ファイル buildspec.xml には次の設定を追加します。

reports:
  simplecov:
    files:
      - 'coverage/.resultset.json'
    file-format: SIMPLECOV

で、レポートはこんな感じです。

f:id:HeRo:20201222183545p:plain
カバレッジレポート

ラインカバレッジとブランチカバレッジが円グラフで表示され、ファイルごとのカバレッジも一覧で表示されます。

残念ながらラインカバレッジは正しく表示されません。
おそらく複数スレッドで並行実行しているためです。
SimpleCov は複数スレッドで実行しても自動的にマージして .resultset.json 1 ファイルに結果をまとめてくれるのですが、一部ファイルの結果が重複して含まれてしまっています。仕方のない仕様なのか、不具合なのか、または設定方法がどこか間違っているのか不明ですが正しくありません(simplecov-htmlsimplecov-jsonでは重複なく正しく結果が出ています)。
なので総コード行数が実際より大きくなって、結果カバレッジが下がっています。実際にはこれよりカバレッジは良いです。
多分、複数スレッドで実行していなければ正しい結果が表示できると思います。

一方で、ブランチカバレッジのほうは正しく結果が表示されています(同じ .resultset.json を読んで表示しているはずですが simplecov-html と同じ結果が出ています)。
ブランチカバレッジはぼっち演算子もブランチ扱いとなります。
ひょっとして nil になるかもと予防的に使っているところも多くテストで全部カバーするのはなかなか難しいですが、もう少し改善できるかなという気がします。

ラインカバレッジが正しくない問題を解決しようと Cobertura XML 形式で結果を出力できるsimplecov-cobertura を試してみました。CodeBuild のカバレッジレポートは Cobertura XML 形式もサポートしているのでもしかすると解決できるのではと思ったのです。
ラインカバレッジを正しく表示できました。が、今度はブランチカバレッジが表示できない…。完全な解決策にはなりませんでした。

ただ、正しく結果が表示できたとしてもカバレッジレポートの方はちょっと機能不足を感じます。
まあ、カバレッジが急に下がってないかを確認するには役立ちます。
しかし、やはりコード上でどこが通っていないのかが見れなくては改善に役立てられません。simplecov-html相当の表示ができるようになるといいのですが…。

まとめ

CodeBuild のレポート機能を Rails アプリケーションで使ってみました。
RSpec のテストレポートは便利です。一方、カバレッジレポートにはコード上でカバレッジを表示する機能が追加されればいいのになぁと思いました。
これが揃えばサードパーティサービスを組み合わせることなくテスト実行からレポーティングまでオールインワンな魅力ある環境となると思います。

最後に

アクトインディではテストを改善し、プロダクトの品質を向上するのが大好きなエンジニアを募集しています。 actindi.net

参考

CI Test環境を作り直した話

CI Test環境を作り直した話

morishitaです。

いこーよは Rails アプリケーションです。
ユニットテストには Rspec を利用しています。

それなりに大きなアプリケーションなので全スペックを実行するにはそれなりに時間がかかります。
ローカルPCで全スペックを実行したことはないのですが、試みて終わるのを待てなくて途中で中断する程度には時間がかかります。

実装中のクラスを中心に部分的に Rspec を実行しながら実装しますが、思わぬところに影響していたりすることもまれにあるので、コードレビューに出す前には全部通しておきたいところです。

以前は Docker コンテナを動的に作ってその中で実行する自作の Web アプリケーションがあって、社内に立てたサーバで動かしていました。

一方で他のプロダクトではすでに CodeBuild で RSpec を実行していました。

tech.actindi.net

ではどうして今までいこーよだけはやってなかったかというと、他のプロダクトよりテスト数が多く、実行時間が 20〜30 分程度かかっていたためです。
社内サーバの環境は結構パワフルなマシンを使っていたのもあり、早い場合だと 10 分強で実行できていました。ただ、社内サーバ環境も混んでるとリソース不足で起動できないエラーが頻発したり、実行時間が長くなったりしていました。
あんまり快適に使える状況ではなくなってきたし、基本リモート勤務に移行したことで、この社内サーバにトラブルが発生してもすぐには対応できないという状況にもなりました。これらをどうにか改善する手段として、他のブロダクトで実績のある CodeBuild での CI Test をいこーよにもついに導入しました。

今回はそれについて書きます。

作ったもの

他のプロダクトで使っているものをそのまま使うこともできました。しかしもともとスキマ時間を利用して JavaScript から TypeScript にリファクタ中だったものが中途半端に放置していたのでこの機会に CDK で作り直しました。

作ったものの概要はこんな感じです。

  1. テスト実行環境である CodeBuild
  2. 上記 CodeBuild と連携する Lambda 関数
  3. 上記 2 つを cdk deploy で構築できる CDK コード

おんなじものを作り直しても面白くないので、ちょっとだけ改善しました。
今回、加えた改善点は次の2点です。

改善点1 - CDKによるオールインワンなインフラ構築

以前作ったものは実行環境である CodeBuild 自体は別途作成しておく必要がありました。今回は通知用の Lambda 関数に加え CodeBuild も構築するようにしました。ワンデプロイで必要なもの全部構築できるようにしました。

改善点2 - CodeBuild のレポートによる結果確認の改善

Lambda で Slack に通知の仕組みは以前と変わりません。
が、そのままというのも芸がないので CodeBuild のレポートを機能を利用して結果を確認しやすく改善しました。

CDK で構築する CodeBuild と Lambda についてもう少し説明します。

CodeBuild でやっていること

CodeBuild は RSpec を実行する実行環境です。RSpec は直接動かしているのではなく cocker-compose を使ってコンテナ内で実行します。
この docekr-compose にはテストを実行するために必要なミドルウェアも含んでおり、次のコンテナで構成しています。

  • RSpec コンテナ
  • Redis コンテナ
  • MySQL コンテナ
  • Solr コンテナ

docker-compose up --abort-on-container-exit でこれらのコンテナを起動し、RSpec を実行します。--abort-on-container-exit はテスト実行完了後、RSpec コンテナが終了したら他のミドルウェアコンテナも止めるためにつけています。

Lambda でやっていること

Lambda 関数は 2 つありで、それぞれの役割は次のとおりです。

  • トリガー関数:Github の Webhook を受けて CodeBuild でのテストを実行する
  • 結果通知関数:CodeBuild のイベントを受けて Slack に通知する

トリガー関数

API Gateway 経由して Github のプルリクエストへの操作の Webhook を受信して実行されます。
受信した Webhook には PullRequest のデータが含まれています。Lambda 関数はまず「自動テスト対象」というラベルの有無をチェックし、無ければそこで終了。あれば CodeBuild を実行します。
実行するためにはテスト対象のプルリクエストのタイトルやブランチ、コミット ID 等を取り出します。そしてそれらを環境変数として設定しつつ CodeBuild を実行します。
と同時に Github のプルリクエストの状態を更新してテスト中であることがわかるようにします。

結果通知関数

CodeBuild は CloudWatch イベントにイベントを通知します。
CodeBuild Build State Changeイベントを受けて実行が終了したことを検知し、それをトリガーに結果通知用の Lambda 関数が実行されます。
これはテスト結果を Slack と Github に通知します。

CodeBuild Build State Changeイベントに含まれる情報だけでは通知したい情報が足りません。なので、AWS の API を叩いて CodeBuild の実行結果やレポート情報を取得してデータをかき集めます。中でもアーティファクトには RSpec や SimpleCov の結果が入っているのでそれも S3 から取得してテストの成否やコードカバレッジを得ます。
集めた情報を整理して後述する通知を Slack に送ります。 と同時に Github のプルリクエストの状態にもテスト結果を反映します。

テストの実行

構築したこのテスト環境を利用するには Github のプルリクエストに「自動テスト対象」というラベルをつけるだけです。
ラベルを付けたとき、あるいはラベルの付いたプルリクエストに Push するとテストが実行されます1

テストが終了すると Github のプルリクエスト上で次のように表示されます。

f:id:HeRo:20201217000706p:plain
Githubプルリクエストでの表示

Slack にも次のように通知します。

f:id:HeRo:20201217000824p:plain
RSpec OK

通知には次の項目を含みます。

  • プルリクエストのタイトル(プルリクエストへのリンクにもなっています)
  • テストログを含むアーティファクトのダウンロードリンク
  • CodeBuild のレポートへのリンク
  • テスト結果
  • テストカバレッジ
  • 所要時間(と概算の CodeBuild の実行コスト)

上記はテストが成功したときですが、失敗したときには次のように通知されます。

f:id:HeRo:20201217000904p:plain
RSpec NG

テストは 8 スレッドで並行実行しています。成功したときには total だけを表示していますが、失敗したときにはどのスレッドに失敗したテストがあるのかわかるようにしています。というのも「詳細情報」からダウンロードできるアーティファクトにスレッドごとのテストログが含まれているからです。どのスレッドで fail したのかわかれば、どのログファイルを見ればいいのかがすぐわかる仕組みになっています。

実行時間の短縮

このエントリの最初に書いたように、移行できなかった理由は実行時間がかかりすぎるということでした。
せめて平均すると同程度の時間でテストが実行できるようにはしたいと思い、チューニングを試みました。

インスタンスタイプはコストとのバランスで general1.large (8vCPU, メモリ 15GB, $0.02/min) を利用しています2
この中で前述の通り docker-compose で必要なミドルウェアのコンテナと RSpec を実行するコンテナを起動してテストを行います。
Rspec はそのまま実行するのではなく test_queue を使って 8 スレッド並行で実行しています。それでも 20 〜 30 分かかっていました。

実行時間を短縮しようと最初に試したのは test_queue の並行実行数を増やすことでした。8vCPU のインスタンスということで 8 スレッドにしていましたが。これを増やしたり逆に減らしたりしてみました。結果的には効果ありませんでした。

他に、複数の CodeBuild に分散させるとかも検討はしましたが、あまり複雑になってしまうのも避けたかったので採用しませんでした。

結局効果があったのは、MySQL, Solr のコンテナを増やしたことでした。
test_queue を使うには互いに干渉しないように読み書きするリソースについては独立したものを用意する必要があります。つまり、8 スレッドで動かすなら MySQL や Solr、Redis を 8 個づつ用意する必要があります。
最初は次の図のような構成でした。

f:id:HeRo:20201217000941p:plain
改善前の構成

テスト実行中は各スレッドがそれぞれのテストデータを書き込んではロールバックして、また書き込むという状況になります。
内部のリソース消費状況を調べると RSpec のコンテナは思ったほど CPU もメモリも使っていませんでした。ということは RSpec コンテナではなくミドルウェアコンテナにボトルネックがあるのでは? と思い、MySQL, Solr のコンテナを増やしてみました。
コンテナを増やして処理を分散させてやると実行時間を 15 分程度に短縮できました。

コンテナが 1 つづつではコンテナ間の通信 I/O が詰まってしまうのかコンテナに割り当てられるリソースでは能力が不足するのか詳細は調べませんでしたが、速くなったので良しとしました。
最適数を求めてコンテナの数をいくつか試してみて、最終的に次のような構成になりました。

f:id:HeRo:20201217001036p:plain
改善後の更生

こうして、元々の社内サーバのベストタイムには及ばないものの平均すると同程度となり、まあ許容できるかなという時間に収まるようになりました。

CodeBuild のレポート機能

機能自体は以前から知っており、使う機会を伺っていたところでした。
丁度いい機会なので、導入してみました。
詳細は次のエントリで紹介しています。

tech.actindi.net

ここでは結果だけ述べると RSpec の結果を確認できるようにしたのですが導入してよかったです。

移行してみて

旧社内サーバの環境と並行稼動している期間があったのですが、みんな早々に旧環境は使わなくなり新しい CodeBuild 環境を使い始めました。なので作業性、実行時間、結果の確認しやすさを合わせた総合的な使い勝手は向上できたのではないかと思います。

最後に

アクトインディでは開発環境を改善しながらサービスを開発していけるエンジニアを募集しています。 actindi.net


  1. 旧環境では、Webの管理画面でテストするブランチを指定してコンテナを作り、初期化してテストを実行するという手動操作が必要でした。

  2. このサイズの上には general1.2xlarge があります。72vCPU, 144GiB というパワフルスペックなのですが、$0.25/min と実装中に頻繁に使うには高価すぎて使えません。

ZookeeperをAWS ECSで稼働させてみた

こんにちは!!こんにちは!!
インフラエンジニアのyamamotoです。

いこーよのインフラをKubernetesに切り替え、古いサーバーを整理していたのですが、厄介だったのが「Zookeeper」でした。
ZookeeperはSolrのステータスを管理するために利用しているのですが、できれば3台全てが常時稼働していることが望ましいため、Kubernetes環境のように、podやノードの入れ替えなどがあって頻繁に止まってしまうのは問題がありました。
ただ、ダウンした時に自動復旧するようにして管理コストを抑えたいという要望もあり、いろいろ検討した結果、AWS ECSでの運用を試してみることにしました。

ECSにしたときの問題

さっそくZookeeperの公式イメージを使って、ECSの構築をはじめました。
しかし、ZookeeperをECS、つまりコンテナ環境にするには、いくつか重大な問題がありました。

  1. Zookeeperクラスタ内の各Zookeeperノードに固有のIDを振る必要がある
  2. 各Zookeeperノードを個別に認識する必要がある(固定IP・ホスト名)

1.については、環境変数 ZOO_MY_ID にIDを設定すれば、コンテナの起動スクリプトが設定してくれるのですが、設定できる環境変数はタスク定義ごとに1種類だけ。
1つのタスク定義・1つのサービス内ではクラスタ構成は実現できません。

2.についても、ZOO_SERVERS 環境変数で他のノードを指定しますが、1つのサービス内でタスクを展開するとIPアドレスが自動的に割り振られてしまうため、個々のノードを指定することができません。

解決法

そこで、1ノードにつき1タスク定義・1サービス・1タスクという体制にすることにしました。
また、「サービスディスカバリ」という機能を使って、サービス単位で固定のノード名(DNS Aレコード)を取得するようにしました。

f:id:yamamoto-kazuyasu:20200720151315p:plain

このようにすることで、ECSにおいてもZookeeperで必要な個別IDや固定ホスト名の割当が可能になりました。

しかしさらに問題が

しかし実際に試してみるとうまく起動できませんでした。
様子を観察していると、どうもタスクが立ち上がってからZookeeperが立ち上がるまでの間に、AWS側のサービスディスカバリの自動設定が間に合わず、Zookeeperクラスタの他のメンバーを見つけられずに落ちてしまうようでした。
ウェイトを入れればなんとかなりそうだったので、Dockerの起動コマンド設定に無理やりsleepを入れてZookeeperの起動を遅らせるようにしました。

それでもまだうまく起動できないことがありました。原因は、ZOO_MY_ID の不整合でした。
環境変数 ZOO_MY_ID にIDを設定すると、Dockerの起動コマンドdocker-entrypoint.shがIDをファイルに書き出し優先的に使用します。これが残ってしまうと、インスタンスが入れ替わったときに本来指定したいIDとズレることがあります。
この対策として、Dockerの起動コマンド設定にファイルを削除する処理を追加しました。

このようにあれこれ調整して、ようやくECS環境で動作するようになりました。

さいごに

当社ではこの構成をAWS CDKで構築することで、複雑な構成でも何度でも作り直しができるようにしました。
開発環境でもスクラップ&ビルドが簡単にできて検証がとても楽でした。

ECSでZookeeperを立ち上げるというのは初めての経験でしたが、稼働してみると、トラブルが発生しても手を入れること無く自動復旧するのでたいへん助かっています。
現在では、同じくECSでSolrの環境も構築して、障害に強い構成になっています。

バッチ処理をFargateに移行した

morishitaです。

先日、いこーよを Kubernetes に移行した件を紹介しました。

tech.actindi.net

いこーよは Web だけで動いているわけではなく、裏で定期的に実行されるバッチ処理も行っています。
本エントリではそのバッチ処理の実行環境を Fargate ECS に移行した件について書きます。

移行前はどうなってた?

いこーよは Rails で開発しています。
バッチ処理というと、DB を集計したり、一斉に DB の値を書き換えたりするものがほとんどです。
そこで ActiveRecord のモデルを利用したいので、バッチ処理も Rails で実装しています。
つまり、Rails Runner で定期的にバッチ処理を実装したメソッドを実行しています。

移行前は、バッチ処理専用サーバがあって、crond で定期的に実行していました。
バッチ処理そのものやバッチ処理に利用しているモデルに変更があるため、毎回のリリース時にはバッチサーバにもデプロイして変更を反映していました。

なぜバッチ処理も Kubernetes で稼働させなかったか?

当初は、Kubernetes の CronJob を利用しようと思っていました。
でもやめました。

バッチ処理は1日中なにか走っています。
たまたま、Job 開始のタイミングにクラスタにリソースが足りなければクラスタがスケールアウトするのを待って実行されることになり、予定の時間よりその分遅れることになります。
いつも一定のタイムラグがあるならそれを見越して実行すればいいのですが、タイミングによりタイムラグの大きさが変動するというのは御しがたいと思いました。
逆にバッチ処理がクラスターのオートスケールに影響するのも避けたいとも考えました。

さらに Kubernetes クラスタの運用をシンプルに保ちたかったということもありました。
Kubernetes クラスタの運用について Kubernetes アップデートなどクラスタ全体に影響する変更はクラスタのリプレースで行う方針です。
その作業中もバッチ処理は実行される必要がありますし、新旧2つのクラスタが並行稼動している間、重複して実行されることも避けなければなりません。
Kubernetes 上でバッチ処理を実行すると、リプレース作業中のバッチ処理の実行状況にも気を配らなくてはいけなくなります。
バッチ処理が走っていない時間帯に作業スケジュールが縛られたり、いつバッチ処理を新クラスタに移行するかも考えねばならなくなります。 考慮事項を減らし、できるだけカジュアルにクラスタのリプレースを実施したいので、クラスタの稼働自体がステートフルになって作業が複雑になるも避けたいと考えました。

これら理由から別の場所でバッチ処理を実行する方法を検討することにしました。

Fargate ECS によるバッチ処理環境

Kubernetes 上で実行するのはやめたけれどコンテナで実行するようにはしたい。
かといって、バッチ専用サーバを立てるとそのサーバのメンテナンスについて考えないといけなくなります。それも避けたい。
と考えた時、選択できる稼働環境として2つ検討しました。

  • AWS CodeBuild
  • ECS のタスク

今回は ECS のタスクとして Fargate で実行することとしました。 どうして、CodeBuild を利用しなかったかは後述します。

ECS によるバッチ処理実行環境

次の様なバッチ処理の実行環境を AWS CDK を使って構築しました。

f:id:HeRo:20201005104209p:plain
システム構成

① CloudWatch Event を使って定期的に ECS のタスクを実行します。
② タスクではいこーよのサイトで動いているのと同じ Docker イメージを取得します
③ Rails Runner でバッチ処理を実装したメソッドを実行します。 Rails コンテナだけでなく Datadog エージェントのコンテナも動かしてメトリクスを収集しています。
④ バッチ処理の終了を CloudWatch Event で拾って、⑤ 通知用の Lambda 関数を実行します。
⑥ タスクの実行結果をイベントとして Datadog に通知します。

ログは Cloudwatch へ、結果は Datadog へ通知

各バッチ処理の実行ログは awslogs ログドライバーを使って、CloudWatch Logs に出力しています。
タスクの実行ごとにログストリームが作られるので、他のバッチ処理や、前回のログと混ざりません。AWS コンソールでさっと確認できるので便利です。ただ、CloudWatch Logs のコンソールは検索性や一覧性が悪いので、エラーが発生したときなどログが多いときにはダウンロードして参照したほうが確認しやすいです。

実行結果はイベントとして Datadog に送っています。Datadog 上で次の様に見ることができます。

f:id:HeRo:20201005104052p:plain
結果の通知

伏せてありますが、どのバッチ処理が実行されたのか、何時に始まって何時に終わったのかが確認しやすいようにしています。
Datadog のイベントの中にある ECS RunTaskLog というリンクは AWS コンソールへのリンクで、実行ログを開けるようにしています。
エラーが発生した場合には Datadog から Slack に通知するようにしており、すぐに気づくことができます。

NAT 経由の通信に注意

Fargate を Private サブネットで動作させる場合、ECR から Docker イメージ取得するのは NAT ゲートウェイ経由になります。
Rails アプリケーションを含むような Docker イメージはそこそこ大きくなります。そのイメージを Pull するのに高価な NAT ゲートウェイ経由の通信で行われるのでコストに要注意です。
特に1日になんども定期的に動作せるようなバッチ処理があるとその都度イメージを取得するので思った以上の通信量なります。NAT経由になると知らず、移行後の最初の請求でびっくりして NAT 経由にならないように変更しました。

現状はバッチ処理では外にポートを開くようなプロセスを起動しないので Public サブネットで動作させて、NAT 経由になるのを回避しています。
いずれ、PrivateLink に移行できればと思っています。

CDK の Tips

AWS コンソールでタスクを設定して実行するときには「パブリック IP の自動割り当て」を設定できます。しかし、CDK で ECS タスクを表す @aws-cdk/aws-events-targets.EcsTask クラスにはこれを設定する機能は実装されていないようです。常に DISABLED に設定され、パブリック IP が割り当てられません。この状態ではパブリックサブネットから ECR に接続できません。

CDK では便利な機能が実装された高級なクラスだけでなく、CloudFormation のマニフェストを直接操作できる接頭辞 Cfn がついたクラス群も提供されています。パブリック IP 割当を設定するにはこの Cfn クラスを利用します。
それを利用してパブリック IP を ENABLED に設定するには例えば次の様に実装します。

import {Rule, Schedule, CfnRule} from '@aws-cdk/aws-events';

const rule = new Rule(this, 'SomeScheduleRUle', {
    schedule: Schedule.cron(cronUtc),
    targets: [ecsTaskTarget], // ecsTaskTargetは @aws-cdk/aws-events-targets.EcsTask のインスタンス
});
const cfnRule = rule.node.findChild('Resource') as CfnRule;
cfnRule.addPropertyOverride('Targets.0.EcsParameters.NetworkConfiguration.AwsVpcConfiguration.AssignPublicIp', 'ENABLED'); // <== ここ!

CodeBuild をやめた理由

さて、この Fargate ECS への移行は安定稼働を続けていますが、最初は私の大好きな CodeBuild で実現しようと思っていました。 それをやめたクリティカルな理由は唯1つ。

CodeBuild の連続稼働時間は最大8時間なのですが、それ以上に時間がかかるバッチ処理があったためです1

なぜ、CodeBuild が良かったかという、コンテナ起動前に処理を挟みやすいからです。
ECS では新しい Docker イメージをビルドするごとに、 TaskDifinition も更新しないと次回のバッチ実行に反映できません。しかし CodeBuild なら Kubernetes マニフェストリポジトリから最新のイメージタグを取得して、そのイメージを取得してからコンテナを実行できます。つまり、わざわざイメージビルドの度になにか更新する手間をかけずに最新のイメージを実行することができます。

結局、イメージをビルドするごとに Kubernetes へのデプロイと同時に、CDK を利用して TaskDifinition を更新しています。

少々、リリースごとの処理が増えたものの、Fargate のほうがコストが低いので結果的には良かったかと思っています。

まとめ

これまで、EC2 で実行していたバッチ処理を ECS の Fargate タスクで実行するようにしました。
EC2 インスタンスの運用から解放され、バッチ処理それぞれの CPU、メモリの使用量などのメトリクスを収集できるようになり、ログも確認しやすくなりました。
更に結果やエラーの通知も改善できました。
そして、バッチサーバを運用していたときよりコストも下げられたと思います。


  1. Fargateで実行してみると、10時間かかっていたバッチも6時間程度しかかかりませんでした。元々の環境では複数のバッチが同時に実行されていたのでメモリ不足だったのかもしれません。

AWS ElasticBeanstalkで作られるALBに手を加える

こんにちは!!こんにちは!!
インフラエンジニアのyamamotoです。

ElasticBeanstalkを使うと、複雑なインフラ構成も簡単に構築できますよね。
でもちょっと細かいところに手を入れようとすると、なかなか難しかったりするのが難点です。

今回は、ElasticBeanstalkで作られるALBに手を加えてみたいと思います。

ALBの設定を変更する

実はElasticBeanstalkでは、 .ebextensions 以下のファイルでいろいろ細かな設定をすることができます。
一例として、リスナーの設定を変更するには、下記のように設定します。

Resources:
  AWSEBV2LoadBalancerListenerRule:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Conditions:
        - Field: "path-pattern"
          Values:
            - "/notaccess/*"
      Actions:
        - Type: "fixed-response"
          FixedResponseConfig:
            StatusCode: "404"
      ListenerArn:
        Ref: AWSEBV2LoadBalancerListener
      Priority: 1

  AWSEBV2LoadBalancerListenerRule443:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Conditions:
        - Field: "path-pattern"
          Values:
            - "/notaccess/*"
      Actions:
        - Type: "fixed-response"
          FixedResponseConfig:
            StatusCode: "404"
      ListenerArn:
        Ref: AWSEBV2LoadBalancerListener443
      Priority: 1

上記では、特定のパスにアクセスしようとするとALBで404を返す設定が入っています。

キモとなるのは ListenerArn で、本来はARNを指定しなければならないところを、Refで定数を参照しています。
AWSEBV2LoadBalancerListener ならびに AWSEBV2LoadBalancerListener443 はElasticBeanstalkで作られるALBの80ポートと443ポートのリスナーのARNを表しています。
この指定を入れることで、 .ebextensions 以下の定義で、ElasticBeanstalkで作られるALBのリスナーの設定が可能になっています。

ElasticBeanstalkは裏でCloudFormationが動いています。そのため .ebextensions 以下のファイルでもCloudFormationの多彩な定義を使うことが可能になっています。
たとえば、インスタンスを別のALBのターゲットグループに登録したり、CloudWatchのアラームを設定したりすることも可能です。

さいごに

ElasticBeanstalkで設定できないところは、ついついコンソールから設定してしまいがちですが、そうすると再構築する際にまたコンソールから設定するはめになってしまいます。
可能な限り .ebextensions 以下のファイルで設定してInfrastracture as Code化しておくことをおすすめします。