SSH 接続できる Amazon Linux 2 の Docker イメージを作成してシステム構築してみる

# はじめに

オークファンでは多くのシステムを AWS で稼働しており、OS は特別な理由がない限り以下の理由で Amazon Linux 2 を選択しています。

- AWS が公式にサポートしている
- セキュリティアップデートやメンテナンスアップデートが継続的に提供される
- 追加料金なし

Amazon Linux 2 は RedHat ベースですが本家とは違っている部分もあるので、システム構築のための各種コマンドを試したくなる場面があります。そのたびにコスト管理をしてくれているインフラグループの顔色を伺いながら実際の AWS 環境に EC2 インスタンスを立ち上げるのも申し訳ないので、開発 PC の Docker 上に実際の EC2 と近い状態の Amazon Linux 2 を起動して動作検証を実施しています。

# Docker イメージの作成

まずはベースとなる EC2 と近い状態の Docker イメージを作成します。
Amazon Linux 2 の Docker イメージは [公式の Docker Hub](https://hub.docker.com/_/amazonlinux/) に公開されているのですが、まっさらな状態なので必要なものをインストールします。設定の大枠は以下となっています。

- 必要なパッケージをインストール
- SSH サーバを起動するように設定
- `ec2-user` を作成

具体的なディレクトリ構成はここでは以下とします。あとで SSH 接続するための公開鍵 (ここでは `id_rsa.pub` としています) を `Dockerfile` と同じ階層に配置しています。

```
amazonlinux2
├── Dockerfile
└── id_rsa.pub
```

`Dockerfile` に以下を記述します。

```Dockerfile
# ベースイメージ
FROM amazonlinux:2

# 同一ディレクトリの公開鍵ファイルを Docker イメージの /tmp ディレクトリにコピーする
COPY ./id_rsa.pub /tmp/

# インストール済みのパッケージを最新版にアップデート
RUN yum -y update && \
    # 追加で必要なパッケージをインストール
    yum -y install \
        sudo \
        shadow-utils \
        procps \
        wget \
        openssh-server \
        openssh-clients \
        which \
        iproute \
        e2fsprogs && \
    # キャッシュを削除
    yum clean all && \
    # setuptools をインストール
    wget https://bootstrap.pypa.io/ez_setup.py -O - | sudo python && \
    # SSH サーバを起動
    systemctl enable sshd.service && \
    # パスワード認証による SSH アクセスを禁止
    echo "PasswordAuthentication no" >> /etc/ssh/sshd_config && \
    # ec2-user を追加
    useradd ec2-user && \
    # ec2-user で sudo コマンドを使用できるようにする
    echo "ec2-user ALL=NOPASSWD: ALL" >> /etc/sudoers && \
    # ec2-user のホームディレクトリ下に公開鍵を配置
    sudo -u ec2-user mkdir -p /home/ec2-user/.ssh && \
    mv /tmp/id_rsa.pub /home/ec2-user/.ssh/ && \
    chmod -R go-rwx /home/ec2-user/.ssh && \
    # 公開鍵を登録
    cat /home/ec2-user/.ssh/id_rsa.pub >> /home/ec2-user/.ssh/authorized_keys && \
    # ロケールを設定
    echo "export LANG=en_US.UTF-8" >> /home/ec2-user/.bash_profile
```

他にも必要なパッケージがある場合は `yum -y install` の部分に追加してください。ただし、あくまでも環境構築を検証するためのイメージなので、EC2 にデフォルトで入っていないものはここではインストールしないようにしています。

Docker イメージをビルドします。ここではタグ名を `amazonlinux2-sshd` としています。
`amazonlinux2` ディレクトリに移動して以下のコマンドを実行します。

```shell

$ docker build -t amazonlinux2-sshd .
```

ビルド完了後に `docker images` コマンドで `amazonlinux2-sshd` が作成されていることを確認してみてください。

正常にビルドができたことを確認できたら以下のコマンドで起動してみます。ここではコンテナ名を `amazonlinux2-sshd-container` とし、`22` 番ポートに `2222` 番ポートから接続できるようにしています。

ここで `--privileged` を付加しないと SSH 接続が許可されないようです。さらに `--rm` を付加して終了時にコンテナを削除するようにしていますが、削除しない場合はこのオプションを除外してください。

```shell

$ docker run --privileged --rm -d -p 2222:22 --name amazonlinux2-sshd-container amazonlinux2-sshd /sbin/init
```

起動したコンテナに SSH 接続してみます。普通に SSH 接続してもいいのですが、コンテナを作成するたびに信用するサーバを登録するのは面倒なので、無条件にサーバに接続するための alias コマンド `ssh-igk` (他の別名でも OK) を登録してそちらで接続することにします。

こちらの作業はホスト OS 側で実施します。ホームディレクトリにある `.bashrc` の末尾に `alias` の行を追加します。

```shell
$ vi ~/.bashrc
```

```~/.bashrc
# (前略)

alias ssh-igk='ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'
```

登録した alias を使用するために以下を実行しておきます。

```shell

$ source ~/.bashrc
```

登録した `ssh-igk` コマンドを使用して `ec2-user` で SSH ログインしてみます。

```shell
$ ssh-igk -i ~/.ssh/id_rsa ec2-user@localhost -p 2222
Warning: Permanently added '[localhost]:2222' (ECDSA) to the list of known hosts.
Last login: Wed Jul  3 12:21:43 2019 from gateway
[ec2-user@98a69d756c81 ~]$
```

警告のメッセージが出ますが問題なくログインできました。

以上で準備ができたので以下のコマンドでコンテナを停止しておきます。

```shell
$ docker stop amazonlinux2-sshd-container
```

# システムを構築してみる

ベースとなる EC2 に近いイメージが作成できたので、こちらを使用してシステム構築を検証する環境を構築できます。ここでは `sample-system` を作成する例を説明します。

ディレクトリ構成は以下とします。
(あらかじめ前述の `amazonlinux2-sshd` をビルドしておく必要があります。)

```
sample-system
├── docker-compose.yml
├── sample-core-api
│   └── Dockerfile
└── sample-front-ui
    └── Dockerfile
```

以下の要素でシステムが構成されている前提とします。

- `sample-front-ui`: フロント画面を提供するシステムを想定
- `sample-core-api`: データを提供する API システムを想定
- `sample-db`: 永続化のための RDB を想定 (実際の AWS 環境では RDS や Aurora など)

作成したそれぞれのファイルに以下を記述します。

sample-front-ui/Dockerfile

```sample-front-ui/Dockerfile
# あらかじめ amazonlinux2-sshd をビルドしておく必要があります
FROM amazonlinux2-sshd

# 以下はサンプルです
RUN amazon-linux-extras enable php7.3 && \
    yum install -y php
```

sample-core-api/Dockerfile

```sample-core-api/Dockerfile
# あらかじめ amazonlinux2-sshd をビルドしておく必要があります
FROM amazonlinux2-sshd

# 以下はサンプルです
RUN amazon-linux-extras install -y java-openjdk11
```

```docker-compose.yml
version: '3'

services:

  # フロント画面を提供するシステムを想定
  sample-front-ui:
    build:
      # sample-front-ui/Dockerfile のイメージを使用
      context: ./sample-front-ui
    container_name: sample-front-ui
    restart: always
    privileged: true
    command: /sbin/init
    ports:
      # ホスト OS からの SSH ログイン用
      - "2223:22"
      # ホスト OS からの Web ページ動作確認用
      - "8880:80"
    networks:
      samplenet:
        ipv4_address: 172.20.0.10
  
  # データを提供する API システムを想定
  sample-core-api:
    build:
      # sample-core-api/Dockerfile のイメージを使用
      context: ./sample-core-api
    container_name: sample-core-api
    restart: always
    privileged: true
    command: /sbin/init
    ports:
      # ホスト OS からの SSH ログイン用
      - "2224:22"
      # ホスト OS からの API 動作確認用
      - "8881:8080"
    networks:
      samplenet:
        # フロントシステムからの接続を想定
        ipv4_address: 172.20.0.11

  # 永続化のための RDB を想定
  sample-db:
    # その時点の Aurora で使用できる PostgreSQL のバージョンと合わせていたりします
    image: postgres:10
    container_name: sample-db
    restart: always
    environment:
      POSTGRES_PASSWORD: changeit
    ports:
      # ホスト OS からの DB 接続用
      - "5444:5432"
    networks:
      samplenet:
        # API システムからの接続を想定
        ipv4_address: 172.20.0.12

# ネットワーク設定
networks:
  samplenet:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/24
```

`sample-system` ディレクトリに移動して以下のコマンドを実行します。

```shell

$ docker-compose up -d
```

イメージがビルドされていない場合はビルドが実行され、各システムが実行されます。
以下のコマンドで稼働状況を確認できます。

```shell
$ docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                                          NAMES
a4acb453f41a        sample-system_sample-front-ui   "/sbin/init"             2 minutes ago       Up 2 minutes        0.0.0.0:2223->22/tcp, 0.0.0.0:8880->80/tcp     sample-front-ui
acf6c2276129        sample-system_sample-core-api   "/sbin/init"             2 minutes ago       Up 2 minutes        0.0.0.0:2224->22/tcp, 0.0.0.0:8881->8080/tcp   sample-core-api
6980309c6d57        postgres:10                     "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        0.0.0.0:5444->5432/tcp                         sample-db
```

フロントシステムに SSH ログインして環境構築作業を検証する場合は以下を実行します。

```shell
$ ssh-igk -i ~/.ssh/id_rsa ec2-user@localhost -p 2223
Warning: Permanently added '[localhost]:2223' (ECDSA) to the list of known hosts.
[ec2-user@a4acb453f41a ~]$
```

検証できたコマンドを `sample-front-ui/Dockerfile` や `sample-core-api/Dockerfile` などの `RUN` に追記していけば最終的に開発 PC 上にシステム全体を自動構築することも可能です。

立ち上げたシステムをまとめて停止する場合は以下を実行してください。

```shell
$ docker-compose down --rmi all
```

上記では停止時にイメージをすべて削除するので、削除しない場合は ` --rmi all` の部分を除外してください。

# おわりに

今回ご紹介した内容は Docker の一般的な使用方法とは少し異なるかと思います。一般的には 1 つのコンテナには 1 プロセスを実行させるように構成するので、Amazon Linux 2 にいろいろインストールするのではなく、PHP、Nginx、Java、PostgreSQL などをそれぞれの Docker イメージから起動していく方針となるはずです。あとそもそもコンテナに SSH ログインすることはあまりないと思います。

また、今後 AWS において EC2 を直接起動するのではなく Fargate、ECS、EKS を使用することが多くなりそうなので、今回のような EC2 を前提としたシステム構築を検証する頻度は減ってきそうです。

と、最後に記事を全否定ですがw 小さなシステムなどはまだ EC2 を使用して構築する場合もあるかと思いますのでその際はコストを気にすることなく動作検証をする方法の一つとして試してみてください。

最後に今回の手順はすべて Windows 10 Pro で実施していますが、Mac や Linux でも同様に実施できると思います。もしうまく動かない部分や記事に誤りなどありましたらご指摘ください。:bow: