AWS Systems Manager Run Commandを使ってCircleCIからEC2上でコマンドを実行する

## はじめに
CircleCIからセキュリティグループでアクセスが制限されているEC2上で何かしらのコマンド（ChefやAnsibleたたくなど）を実行したいということがあり、**Systems Manager Run Command**を使って実現しました。
Systems Manager Run Commandは初めてさわりましたが手軽で便利だったのでおすすめです。

## アクセス制限の問題
多くの場合はEC2へのアクセスはセキュリティグループ等で制限されているため、CircleCIからsshで接続しようとしてもそのままでは接続できない。
また、CircleCIのインスタンスのIPアドレスは動的に設定されるためIPアドレスの制限を許可するのも手間がかかります。（[こちらの記事](https://qiita.com/rintaro-ishikawa/items/02e6a63dbc90ea67a991)であるような方法で実現は可能）

![circle_ci_ec2_ssh.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132008/d30c0725-b2fd-6d69-9d76-1d2239ab472a.png)


## Systems Manager Run Commandを使った方法
こういった場合にSystems Manager Run Commandを使えば、IAMで適切なアクセス権を設定することでSSHを使用せずに（セキュリティグループ問題を気にせずに）EC2上でコマンドを実行できます。

### やること
- EC2にSSMにエージェントのインストール（使用するAMIによってはデフォルトでインストールされている）
- EC2のIAMロール設定
- コマンドを実行するIAMユーザー作成
- CircliCI上の設定・処理の追加

### EC2にSSMエージェントのインストール
Systems Manager Run Commandを使うためにEC2にSSMエージェントをインストールする必要があります。
SSMエージェントは使用するAMIによってはデフォルトでインストールされており、今回使用した `Ubuntu Server 18.04` にはデフォルトでインストールされていためインストールは割愛します。

インストールが必要な場合は[こちら](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-manual-agent-install.html)を参照ください。

### EC2のIAMロール（インスタンスプロファイル）設定

`AmazonEC2RoleforSSM` のポリシーをアタッチしたIAMロールを作成し割り当てます。

インスタンスの設定 > IAMロールの割り当て/置換 からさきほど作成したロールを割り当てます。

<img width="567" alt="ec2_iam_role_setting.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132008/87b885a5-d9df-ba48-0f1e-97e44868aee9.png">

### CircleCIからコマンドを実行するユーザーのポリシーの設定
テスト用に`AmazonSSMFullAccess`のポリシーがアタッチされた`test_ssm_user`というIAMユーザーを作成します。
<img width="748" alt="ssm_iam_user.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132008/378d87cf-73ac-71aa-8f5c-677d0047a2d1.png">

### CircleCI上の設定・処理の追加
ここまででコマンドを実行する準備ができたので、CIで実行する処理を追加していきます。
今回はRubyの`aws-sdk`を使ってスクリプトを書いていきます。

```ruby:ec2_run_command.rb
require 'aws-sdk'

Aws.config.update(
  access_key_id:     ENV['SSM_ACCESS_KEY_ID'],
  secret_access_key: ENV['SSM_SECRET_ACCESS_KEY'],
  region:            'ap-northeast-1'
)

Aws::SSM::Client.new.send_command(
  document_name: "AWS-RunShellScript",
  instance_ids: [ENV['EC2_INSTANCE_ID']],
  parameters: {
    commands: ["echo Hello > /tmp/hello.txt"], # この部分がEC2上で実行するコマンド
    executionTimeout:["3600"]
  },
  timeout_seconds: 600,
)
```

#### 環境変数の設定
コマンドを実行するユーザー（test_ssm_user）のcredentialの情報とコマンドを実行するインスタンスの情報を環境変数に追加
<img width="959" alt="ci_env_vars.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132008/f12e458f-21d3-4fac-9a5e-3b820449f197.png">



### config.yml
CircleCIのworkflowを定義します。

```.circle/config.yml
version: 2.1
executors:
  default:
    working_directory: ~/ec2_sample
    docker:
      - image: ruby:2.6.3-stretch
        environment:
          BUNDLE_GEMFILE: /root/ec2_sample/Gemfile
          BUNDLE_PATH: /root/ec2_sample/vendor/bundle/ruby/2.6.0
jobs:
  run-command:
    executor:
      name: default
    steps:
      - attach_workspace:
          at: ~/ec2_sample
      - checkout
      - restore_cache:
          keys:
            - gf-bundle-1-{{ .Branch }}-{{ checksum "Gemfile.lock" }}
            - gf-bundle-1-{{ .Branch }}-
            - gf-bundle-1-
      - run:
          name: bundler install
          command: gem install bundler:2.0.1
      - run:
          name: bundle install
          command: bundle install --path=./vendor/bundle
      - run:
          name: deploy
          command: bundle exec ruby ec2_run_command.rb # scriptを実行

workflows:
  ec2-ci:
    jobs:
      - run-command
```

scriptをrubyで書いたのでrubyのDockerイメージを使ったり色々書いてますが、`bundle exec ruby ec2_run_command.rb` でさきほど書いたscriptを実行しているだけのjobです。


### 動作確認
`run command` のjob実行後
<img width="469" alt="スクリーンショット 2019-08-25 19.01.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132008/e0f3e16e-7ef8-e488-bf7b-8e251af3935b.png">

さきほどコマンドに書いた`echo Hello > /tmp/hello.txt`がec2上で実行され、hello.txtが作成されていることが確認できました。

```sh
$ cat /tmp/hello.txt 
Hello
```

## まとめ
以上、CircleCIからSystems Manager Run Commandを使ってEC2上でコマンドを実行する方法でした。
今回はCircleCIから実行するという使い方をしましたが、他の場面でもちょっとしたことをやるのにRun Commandは使えそうだなという印象です。

## 参考
[チュートリアル: Run Command で AWS CLI を使用する](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/walkthrough-cli.html)
[SSM エージェント の使用](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/ssm-agent.html)
[Amazon EC2 Linux インスタンスに SSM エージェント を手動でインストールする](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/sysman-manual-agent-install.html)
[CircleCI2.0からEC2にアクセスするときだけ特定のIPを許可したい](https://qiita.com/rintaro-ishikawa/items/02e6a63dbc90ea67a991)