FargateのコンテナでOSコマンドやsshで入りたい!! それssm-agentで解決できます

Fargate便利ですよね。煩わしいインスタンス管理から開放させて素早く環境が用意できます。
ですがECSと違って一点大きな問題があります。

## docker exec や SSH が実行できない!!

EC2に慣れている人にはSSHで調べられないのはちょっとうっ。。。。って思います。(少なからず自分はそうでした)
ではできないのか？というとそうでもないらしくいくつか方法はあるようです。

* SSHの環境を設定してそこからコンテナに入る
* ssm-agent経由でコンテナに入る

SSHの環境を整えても良いのですが、それだとせっかくのセキュアな環境が勿体ないので今回はssm-agentを経由したコンテナの入り方を構築したのでメモ代わりに残しておきます。

## 環境

* AWS Fargate

## 参考にした記事

* [[AWS ECS]Fargateのcontainerにシェルで入りたい(sshd無しで！)](https://qiita.com/pocari/items/3f3d77c80893f9f1e132)
    * これしかまともな情報がなかったので助かりました!!

## やること

* DockerImageにssm-agentをインストールする
* Systems managerのハイブリッドアクティベーションの機能を使ってコンテナをmanagedする
* ssm-agentの起動コマンドを使ってマネージド化する
* Systems Managerのセッションスタート及びRun Commandができるか確認する

## Dockerfileにssm-agentをインストールする処理を追加する

今回のコンテナはnodeJSのコンテナでPythonがなかったのでPythonのインストールも含めています(ssm-agentがPythonを使用するため)

```Dockerfile:Dockerfile
FROM node:10.16.3-stretch-slim

RUN apt-get update && apt-get install -y \
    curl \
 && apt-get clean \

# Python Install
RUN apt-get update && apt install -y \
    zlib1g-dev \
    libssl-dev \
    libreadline-dev \
    libsqlite3-dev \
    libbz2-dev \
    libncurses5-dev \
    libgdbm-dev \
    liblzma-dev \
    tk-dev zlibc \
    libffi-dev \
    zip \
    unzip \
 && apt-get clean \
 && curl https://www.python.org/ftp/python/3.7.5/Python-3.7.5.tgz | tar zx -C /usr/local/src/ \
 && cd /usr/local/src/Python-3.7.5 \
 && ./configure \
 && make && make install \
 && ln -s /usr/local/bin/python3 /usr/local/bin/python

RUN curl https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/debian_amd64/amazon-ssm-agent.deb -o /tmp/amazon-ssm-agent.deb \
    && dpkg -i /tmp/amazon-ssm-agent.deb \
    && cp /etc/amazon/ssm/seelog.xml.template /etc/amazon/ssm/seelog.xml
```

## AWS Systems Managerのアクティベーションを使用してコンテナインスタンスをマネージドインスタンスにする

このアクティベーションという機能がよくできているやつで物理のオンプレミスインスタンスやラズパイ、ローカルのdocker等ssm-agentがあればssm-agent経由でSystems Managerの機能が使用できます。

ただし注意事項でEC2等はAWSの管理化に置かれているインスタンスなので料金はかかりませんが
それ以外のサーバーを登録すると時間当たりの料金が発生します。

[Systems Manager#オンプレミスインスタンス管理](https://aws.amazon.com/jp/systems-manager/pricing/)

試算するとssm-agent経由でマネージドインスタンスに登録した時点で

```
【計算式】料金      = (マネージドインスタンス管理時間単価 x 24(時間)× 日数
【コスト】5.004 USD = (0.00695 x 24) x 30
```

となって１コンテナタスクあたり月額約5 USD程かかるのでご注意してください。


### 起動スクリプトを記載する

こんな感じの起動スクリプトを記載します

```shell:start.sh
#! /usr/bin/env bash

set -e

if [ "$SSM_ACTIVATE" = "true" ]; then
  ACTIVATE_PARAMETERS=$(aws ssm create-activation \
    --default-instance-name "$RESOURCE_STAGE-$SERVICE_NAME-$RESOURCE_VERSION" \
    --description "$RESOURCE_STAGE-$SERVICE_NAME-$RESOURCE_VERSION" \
    --iam-role "service-role/AmazonEC2RunCommandRoleForManagedInstances" \
    --registration-limit 5)

  export ACTIVATE_CODE=$(echo $ACTIVATE_PARAMETERS | jq -r .ActivationCode)
  export ACTIVATE_ID=$(echo $ACTIVATE_PARAMETERS | jq -r .ActivationId)
  amazon-ssm-agent -register -code "${ACTIVATE_CODE}" -id "${ACTIVATE_ID}" -region "ap-northeast-1" -y
  nohup amazon-ssm-agent > /dev/null &
fi
.
.
## この後にコンテナで実行したい処理を記載する
```


インストールしたssm-agentのコマンドでマネージド化しますとこんな形で登録済みインスタンスとして登録されます

![アクティベート化された後](https://user-images.githubusercontent.com/1619084/70023259-36859780-15da-11ea-94dd-55d05fcbc051.png)

## ECS Task Definitionsの修正

TaskRoleには

* AmazonSSMManagedInstanceCore
* AmazonSSMAutomationRole
* AmazonSSMDirectoryServiceAccess

が最低限必要になります


CloudFormationで記載していますがお好きな形で適宜修正してください

```yaml:template.yml
Parameters:
  TaskDefinitionEnvSSMActivate:
    Type: String
    Description: true is ssma-agent start mode task definitions
    Default: true

Resources:
  ECSTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "{your role name}"
      Path: /
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: [ "ecs-tasks.amazonaws.com" ]
            Action: sts:AssumeRole
          - Effect: Allow
            Principal:
              Service: [ "ssm.amazonaws.com" ]
            Action: sts:AssumeRole

      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AmazonSSMDirectoryServiceAccess

  ECSTaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      Cpu: {your vCPU}
      Memory: {your Memory}
      ExecutionRoleArn: {yourExecutionRoleArn}
      TaskRoleArn: !GetAtt ECSTaskRole.Arn
      Family: {Your family name}
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: {your container name}
          Image: !Sub
            - "${AWS::AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${ECRRepository}"
          Environment:
            - Name: SSM_ACTIVATE
              Value: !Ref TaskDefinitionEnvSSMActivate
            - Name: SERVICE_NAME
              Value: !Ref ServiceName
            - Name: RESOURCE_STAGE
              Value: !Ref Prefix
            - Name: RESOURCE_VERSION
              Value: !Ref CFVersion
            - Name: SERVICE_STAGE
              Value: !Ref ServiceStage

          Command: [ "sh", "start.sh" ]
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSTaskLogGroup
              awslogs-region: !Ref AWSLogRegion
              awslogs-stream-prefix: "ecs"
          MemoryReservation: 128
          PortMappings:
            - HostPort: 80
              Protocol: tcp
              ContainerPort: 80
            - HostPort: 10443
              Protocol: tcp
              ContainerPort: 10443
```

抜粋していますが
* Environment SSM_ACTIVATEのフラグで起動コンテナのssm-agentモードでの起動か否かをハンドリングしています。

デフォルトはfalseで運用してトラブルシューティングをしたい場合はtrueにフラグを立てて実行する戦略です。


### 繋いで見る

ではこの状態で

* セッションマネージャー
* コマンドの実行

をやってみましょう

登録したインスタンスはハイブリッドアクティベーションからアクティベーションIDをコピーして
検索をかけるとわかります。

![マネージドインスタンス画面](https://user-images.githubusercontent.com/1619084/70024360-29b67300-15dd-11ea-903b-1fe48f391342.png)

![スタートセッションのボタン](https://user-images.githubusercontent.com/1619084/70024489-96ca0880-15dd-11ea-8a45-9adca02626f8.png)

これで接続ができます。

## 補足: テストが終わった後のマネージドインスタンスの掃除

aws-cliでいけます

```
aws ssm delete-activation --activation-id {ActivateId}
aws ssm deregister-managed-instance --instance-id "mi-12345abcd"
```



## 最後に

最後の手段で使う処理なので常時。というよりかはどうしても困ったら実行する手段として覚えて置くとよいですね。