AWS-CodeBuild

CodeBuildとawspecでAWSリソースを自動テストする

はじめに

こんにちは、omkです。
暑い日が続きますね。熱中症には気をつけてどうかご自愛ください。

最近はawspecを用いて構築したサーバのテストを行うコードを作成しています。
その際、EC2上にawspecの動作環境を構築してコマンドを実行しているのですが、テストの結果だけが欲しいのでCodeBuildでテスト時に自動で環境を作成してテストが終われば自動で環境を消すようにしていきます。

こんな感じで。
構成

awspecとは

awspecはAWSのリソースの設定が正しいかテストを行うRubyのツールです。
例えば、このEC2が存在するか、このVPC上に作成されているか、インスタンスタイプが合っているか、などというような正しい設定と実際の設定を比較して正誤を判定してくれます。

awspecでどのようなテストができるか興味があればこちらを御覧ください。

やってみた

テストの内容はなんでもいいので今回はとりあえず対象のEC2インスタンスが存在しているかだけを確認するテストを行います。
対象のEC2インスタンスは存在することを前提とし、作成する手順は省略します。

S3

まず、テストの内容(ec2_spec.rbとします)とBuildspecを配置するS3バケットを用意します。
特にパブリックアクセス等の設定は不要なのでそのまま作ります。

できたバケットの配下にテスト内容とBuildspecを配置する「test」フォルダとテスト結果を配置する「result」フォルダを作成します。

こんな感じです。

S3バケット

CloudWatchLogs

次にCodeBuildの実行ログの出力先を作成します。
CloudWatchLogsでロググループを作成します。

こんな感じです。

CloudWatchLogs

CodeBuild

次にCodeBuildのビルドプロジェクトを作成します。

ソース

ソースプロバイダをS3にして先程作成したバケットを指定します。
このとき、S3フォルダを「test/」を指定します。

環境

実行するコンテナイメージは何でもいいので公式のAmazonLinux2のstandard3.0を選択します。
サービスロールも新規で作ります。

Buildspec

「buildspec ファイルを使用する」を選択します。

アーティファクト

テストの実行結果を吐き出すS3を設定します。
タイプを「S3」にして先程作成したバケットを指定します。
パスに「result/」を指定します。
名前空間のタイプに「ビルドID」を選択してビルドごとに階層を分けるように設定します。

ログ

CloudWatch Logsのオプションをチェックし、先程作成したロググループを指定します。
適当なストリーム名を付けたらOKです。

これで「ビルドプロジェクトを作成する」を選択してCodeBuildの設定が完了です。

IAMロール

ビルドプロジェクト作成時に一緒に作成されたCodeBuild用のIAMロールに対してawspecの実行に必要な権限を渡します。
今回はec2だけをテストするのでロールに「AmazonEC2ReadOnlyAccess」を割り当てます。ブログ用にざっくり割り当てていますが、本来は必要に応じた最小限の権限が割り当てられることが推奨されます。

テスト内容とBuildspec

作成したバケットの「test/」にbuildspec.ymlとテスト内容(ec2_spec.rb)を配置します。
ec2_spec.rbをbuildspec.ymlと同じ領域に置いておくことでCodeBuildの実行時に一緒にコンテナに取り込んでくれます。

*_spec.rb

今回用意したec2_spec.rbの内容はこちら

require 'spec_helper'

describe ec2('omk-ec2') do
    it { should exist }
end

「omk-ec2」が存在するかどうかテストします。

buildspec.yaml

次にBuildspecです。
BuildspecはCodeBuildでコンテナ実行時に行う処理を記述します。
パッケージのインストールや変数の割当、ビルドの実行などの処理が該当します。
buildspecの書き方はこちらを参照ください。

buildspec.ymlの内容はこちら

version: 0.2
phases:
  install:
    commands:
      - echo "install packages"
      - yum install -y ruby
      - yum install -y ruby-devel
      - gem install awspec
      - gem install nokogiri
      - gem install rspec
  pre_build:
    commands:
      - awspec init
      - mv ./*_spec.rb ./spec/
      - curl -qL -o aws_credentials.json 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > aws_credentials.json
      - aws configure set region $AWS_REGION
      - aws configure set aws_access_key_id `jq -r '.AccessKeyId' aws_credentials.json`
      - aws configure set aws_secret_access_key `jq -r '.SecretAccessKey' aws_credentials.json`
      - aws configure set aws_session_token `jq -r '.Token' aws_credentials.json`
  build:
    commands:
      - echo "test start"
      - rake spec > ./result.txt || true
  post_build:
    commands:
      - echo "Build completed"

artifacts:
  files:
      - './result.txt'

install部に関して、
awspecの動作環境の構築には時間がかかるので本来であれば先に動作環境を構築したコンテナイメージを作成してECRに登録するべきですが、今回は内容を明確にするためにすべて1から作ってもらいます。

pre_build部のロールから権限を受け取って割り当てている箇所に関してはこちらの記事からそのままお借りいたしております。

build部でテストを実行して結果を「./result.txt」に出力し、最終的にこれをS3にアーティファクトしてアップロードするようにしています。
また、テストの結果ですが、awspecではテストの結果、リソースの設定が間違っていると終了ステータスでエラーを返します。なのでテストの実行が正常に完了しても結果が不合格であればビルドが失敗して見えるのでtrueコマンドに渡しています。

ビルド実行

ファイルをS3に配置したらビルドプロジェクト上で「ビルドを実行」を選択するとテストが開始されます。
ビルドログにダーっとログが表示されますので上手く行かない場合はこちらを確認します。

ビルドステータスが「成功」になればビルドが完了です。
S3の結果を取りにいきます。

S3のresult配下にビルドIDのフォルダが作成されその中に結果のファイルが出力されます。

/root/.rbenv/versions/2.7.2/bin/ruby -I/root/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rspec-support-3.10.2/lib:/root/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib /root/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/exe/rspec --pattern spec/\*\*\{,/\*/\*\*\}/\*_spec.rb

ec2 'omk-ec2'
  is expected to exist

Finished in 0.21033 seconds (files took 6.16 seconds to load)
1 example, 0 failures

テストも問題なく行われ、結果もばっちりです。

おまけ CodeBuildとawspecで別のAWS環境のリソースを自動テストする

ついでに別のAWSアカウントのリソースもテストもできるようにしました。
個人的にはどちらかといえばこちらが本命です。

対象アカウントでスイッチ用IAMロール作成

まずテスト対象のAWSアカウントでスイッチ用のロールを作成します。
信頼されたエンティティに元のアカウントIDを指定し、ポリシーにおなじく「AmazonEC2ReadOnlyAccess」を割り当てます。
作成されたロールのARNをコピーしておきます。

ビルドプロジェクトに環境変数設定

ここでは、先程作成したロールのARNをビルドプロジェクトの環境変数に設定します。このとき、SSMのパラメータストアにARNを置いてセキュアに取り扱えるようにします。

まず、ビルドプロジェクトでビルドの詳細を選択します。環境で「編集」を選択し、追加設定から環境変数に「パラメータの作成」で追加します。これでパラメータストアにARNが設定されます。
名前に適当な環境変数名を設定して値にARNを入力します。

設定を保存すると環境変数の値にパラメータストアのパスが設定されますが、ビルド実行時にはパスに設定したパラメータの値(ここではARN)が割り当てられます。

ビルド用IAMロール編集

ビルドプロジェクトの設定で「AWS CodeBuild にこのサービスロールの編集を許可し、このビルドプロジェクトでの使用を可能にする」を有効にしていればパラメータの取得の権限がロールに付与されています。

なのでここで必要なのはロールをスイッチするための権限の設定のみとなります。
ポリシーについてはこちらを参照ください。
で、作ったポリシーをロールに割り当てます。

これでスイッチする準備が完了です。

Buildspec編集

version: 0.2
phases:
  install:
    commands:
      - echo "install ruby and other gems"
      - yum install -y ruby
      - yum install -y ruby-devel
      - gem install awspec
      - gem install nokogiri
      - gem install rspec
      - awspec init
      - mv ./*_spec.rb ./spec/
  pre_build:
    commands:
      # Get Natural IAMRole
      - curl -qL -o aws_credentials.json 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > aws_credentials.json
      - aws configure set region $AWS_REGION
      - aws configure set aws_access_key_id `jq -r '.AccessKeyId' aws_credentials.json`
      - aws configure set aws_secret_access_key `jq -r '.SecretAccessKey' aws_credentials.json`
      - aws configure set aws_session_token `jq -r '.Token' aws_credentials.json`
      # Switch Role
      - token=`aws sts assume-role --role-arn ${omk_test_role} --role-session-name AWSCLI-Session`
      - export AWS_ACCESS_KEY_ID=`echo $token | jq -r .Credentials.AccessKeyId`
      - export AWS_SECRET_ACCESS_KEY=`echo $token | jq -r .Credentials.SecretAccessKey`
      - export AWS_SESSION_TOKEN=`echo $token | jq -r .Credentials.SessionToken`
  build:
    commands:
      - echo "test start"
      - rake spec > ./result.txt || true
  post_build:
    commands:
      - echo "Build completed"
      - export AWS_ACCESS_KEY_ID=""
      - export AWS_SECRET_ACCESS_KEY=""
      - export AWS_SESSION_TOKEN=""

artifacts:
  files:
      - './result.txt'

pre_build部の# Switch Role以下でロールを切り替えます。
環境変数${omk_test_role}にロールのARNを割り当てているのでこれをもとにスイッチします。

これをS3に上げます。

ビルド実行

先程と同じ内容のテストを実行します。
テスト先のアカウントには「omk-ec2」は作成していないのでロールの切り替えが正常に行われればテスト結果にFailureが出てくるはずです。

では同じように実行します。

/root/.rbenv/versions/2.7.2/bin/ruby -I/root/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rspec-support-3.10.2/lib:/root/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/lib /root/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/rspec-core-3.10.1/exe/rspec --pattern spec/\*\*\{,/\*/\*\*\}/\*_spec.rb

ec2 'omk-ec2'
  is expected to exist (FAILED - 1)

Failures:

  1) ec2 'omk-ec2' is expected to exist
     Failure/Error: it { should exist }
       expected ec2 'omk-ec2' to exist
     # ./spec/ec2_spec.rb:4:in `block (2 levels) in <top (required)>'

Finished in 0.59306 seconds (files took 6.02 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/ec2_spec.rb:4 # ec2 'omk-ec2' is expected to exist

結果をS3に取りに行ってEC2のテストが「Failure」となっていたので正しくスイッチできたことが確認できます。

おわりに

これでファイルさえ用意すればいつでもどこでもテストができますね。
今回S3にファイルを置いて手動でビルドを実行する運用をとりましたがCodeCommitに変えてPipelineでビルドを自動化したりというようなこともできると思うのでそちらにも挑戦してみたいです。

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA