Modern DevOps with Docker on Amazon VPC Private Subnets #01

 Docker VPC
2015.12.11

弊社で開発・運用させていただく案件も、最初から最後まで Dockerなものが増えてきました。
本番稼動している 業務システム、ゲームタイトルもいまや複数 展開しており、
コンテナのさまざまな恩恵にあずかる日々です。

 

ちなみにこれは、Container with AWS Advent Calendar 2015 11日目の記事です。
 
 
AWS × コンテナで、ある程度 大きなサービスを稼動させるなら ECS が素晴らしいのですが
ELBでコンテナをオーケストレートするという前提もあり、評価・ステージング環境も含めて
小さな EC2インスタンスだけで安く!といったプロジェクトには若干不向きです。
 
となると Elastic Beanstalk かな?という意見もあるかと思いますが
オススメは docker machine × docker compose × EC2インスタンスです。
 
 

おすすめの理由

 
開発もデプロイも、ローカルの端末からDockerのコマンドだけで 行えるからです。

それに環境変数など、一切の秘密情報をサーバにアップしたり残したりする必要はなく
AWS利用料もおそらく最小です。パブリックサブネットでよければさらに安いです。
 
 

当記事の守備範囲

 
今回の内容は チームでの開発 を想定しています。
docker machineと docker composeだけで 開発しながら、
AWSプライベートサブネット上のサービスを更新するところまでです。

 

1. 構成・環境セットアップ

 
こんな構成です。

 
ローカルの開発環境は

ことが必要です。
また、AWSについては

状況だとします。
もしコンテナをパブリックサブネット上で動かすなら後半は不要です!
 
 

2. Dockerホストを起動する

 
図でいうと、AWSのプライベートサブネットにいる Docker Hostを起動します。
よくある手順であれば、ここで docker-machineを使って起動するのですが、
今回は チームで同じ SSL証明書を使えるようにするため に別の手順でやってみます。
 

2.1. SSL証明書の生成

 
(docker machineで起動すれば意識しない部分ですが)
そもそもなんのための SSL証明書かというと、これのためです。
つまり、Dockerホストを外部から操作するときの通信を暗号化するためです。
 
今回は CoreOSを使って進めようと思うので、Docker公式ではなく
CoreOSの示す手順に沿って SSL証明書を取得してみます。
 

2.1.1. CloudFlareの暗号ツールキット cfsslのインストール

go get -u -tags nopkcs11 github.com/cloudflare/cfssl/cmd/cfssl

または cfsslの Webサイト からバイナリを落としてきましょう。
 
2.1.2. 認証局とサーバの設定を JSONで定義
 
以下、例は弊社の定義になっていますので、そのあたりは編集しつつ
3つのファイルを作業フォルダに作ってください。
 
ca-config.json

{
    "signing": {
        "default": {
            "expiry": "43800h",
            "usages": [
                "signing",
                "key encipherment",
                "server auth"
            ]
        }
    }
}

ca-csr.json(要編集)

{
    "CN": "supinf.co",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "JP",
            "ST": "Tokyo",
            "L": "Shibuya-ku",
            "O": "SUPINF Inc.",
            "OU": "MSP Division"
        }
    ]
}

server.json(要編集)

{
    "CN": "server",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "JP",
            "ST": "Tokyo",
            "L": "Shibuya-ku",
            "O": "SUPINF Inc.",
            "OU": "MSP Division"
        }
    ]
}

2.1.3. SSL証明書の生成
 
cfsslを使って各種証明書を作ります。
Dockerファミリー間で SSL証明書の命名規則に統一感がないという残念な理由で
最後に別名で 2ファイル複製しています。お忘れなきよう・・

# Generate CA's with defined options
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -

# Generate server certifications
export ADDRESS=service.supinf.co,127.0.0.1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -hostname="$ADDRESS" server.json | cfssljson -bare server

# for docker remote API
cp server.pem cert.pem
cp server-key.pem key.pem

 
 

2.2. Amazon VPCなどの作成

 
先ほどの構成図を一発起動ができる CloudFormationテンプレートを用意しました。
こちら をクリックして進めれば、数分で VPCが完成です。
 
(注:踏み台と NATの役割を兼務する t2.microインスタンスが 1台起動します。
  SSH接続のセキュリティグループも検証用の甘いものです!!)
 
 

2.3. Dockerホストとなる EC2インスタンスの起動

 
先ほど生成した SSL証明書と user-dataを使って EC2インスタンスを起動します。
(CoreOS以外の OSで起動したい場合は後半を編集してください)

#cloud-config

write_files:
    - path: /etc/docker/ca.pem
      permissions: 0644
      content: |
        -----BEGIN CERTIFICATE-----
        MIID0jAxFDASBAcTCTAeFwYDVQpTaGEiMA0GbeRA2cVJQ7ojlSJd6vqxH8QmxeIJ
        ...
        -----END CERTIFICATE-----
    - path: /etc/docker/server.pem
      permissions: 0644
      content: |
        -----BEGIN CERTIFICATE-----
        MIIEHjCxFDASBgcTClNoaWJ1NTEyMDkTVVBJTkt1MQ4wDAYDVQ8AMIMrISi54+mS
        ...
        -----END CERTIFICATE-----
    - path: /etc/docker/server-key.pem
      permissions: 0600
      content: |
        -----BEGIN RSA PRIVATE KEY-----
        MIIEowIBAAyeeG87BdLzPJRm9S3qd+W9jQfl5QIQl1sOxI9Yi2c9eo5mMrpRSxyk
        ...
        -----END RSA PRIVATE KEY-----
coreos:
  units:
    - name: docker.service
      drop-ins:
        - name: 10-tls-verify.conf
          content: |
            [Service]
            Environment="DOCKER_OPTS=-H=0.0.0.0:2376 --tlsverify --tlscacert=/etc/docker/ca.pem --tlscert=/etc/docker/server.pem --tlskey=/etc/docker/server-key.pem"
      command: start

 

3. docker machineに Dockerホストを追加する

 
EC2は起動しましたか?
ではローカルの docker machineに Dockerホストを追加しましょう!
 

3.1. SSH ポートフォワーディング

 
踏み台サーバを経由して、プライベートサブネット上の Dockerホストに対し
Docker Remote APIが届くように、SSHを使ってポートをフォワードします。

ssh -i ec2.pem -C -N -L <local-port>:<docker.host.private.ip>:2376 ec2-user@bastion.host

 

3.2. docker machineへマシンを追加

 
登録するマシン名、SSL証明書のあるフォルダ、フォワードしたポートを編集しつつ
以下のようなコマンドで Dockerホストが docker machineに登録されます。

MACHINE_NAME=<my-service>

DOCKER_CERT_PATH=<$HOME/somewhere/cfssl>
DOCKER_HOST="tcp://127.0.0.1:<local-port>"

docker-machine --tls-ca-cert=$DOCKER_CERT_PATH/ca.pem \
      --tls-ca-key=$DOCKER_CERT_PATH/ca-key.pem \
      --tls-client-cert=$DOCKER_CERT_PATH/server.pem \
      --tls-client-key=$DOCKER_CERT_PATH/server-key.pem \
      create -d "none" --url=$DOCKER_HOST $MACHINE_NAME

# for docker remote API
cp $DOCKER_CERT_PATH/cert.pem $HOME/.docker/machine/machines/$MACHINE_NAME/
cp $DOCKER_CERT_PATH/key.pem $HOME/.docker/machine/machines/$MACHINE_NAME/
cp $DOCKER_CERT_PATH/ca.pem $HOME/.docker/machine/machines/$MACHINE_NAME/

# for docker-machine commands
cp $DOCKER_CERT_PATH/server-key.pem $HOME/.docker/machine/machines/$MACHINE_NAME/
cp $DOCKER_CERT_PATH/server.pem $HOME/.docker/machine/machines/$MACHINE_NAME/

eval "$(docker-machine env $MACHINE_NAME)"
docker-machine ls

 

4. DevOps with Docker

 
やっと準備が整いました!!
(2人目以降の開発参加者は、手順 2を飛ばせるので設定は楽です)
 
ここまでくれば、もうプライベートネットワーク内のサーバだろうと
docker composeで開発したことのある方は慣れた作業です。
ローカル環境で作業したいときは

eval "$(docker-machine env default)"

で docker-composeコマンドはローカル環境に対して動作しますし、
EC2環境を操作したければ

eval "$(docker-machine env <my-service>)"

で OKです。すごい!便利!!!!
 
 
 
ちなみに 前回の記事でご紹介した通り、
docker compose 1.5以降なら、docker-compose.ymlのオーバーライドが使えるので
社内では、ローカルでの開発時は

eval "$(docker-machine env default)"
docker-compose --x-networking up -d

を使って、開発用に定義を上書いている docker-compose.override.yml を参照させ、
EC2環境のサービスは

eval "$(docker-machine env <my-service>)"
docker-compose --x-networking -f docker-compose.yml up -d

と設定ファイルを指定して起動したりしています。
 
 
次回は、今回の構成をベースに、Amazon VPCのプライベートサブネット上で
swarmクラスタを組んだり、デプロイするときの具体的な手法をご紹介します!