Windows 10 にDockerをいれてPythonを実行して、その勢いでCloudFormation一発でECS上でも実行してみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

梶原大使(ambasad)です。
Windows 10 上にUbuntu18.04をインストールしPython3を実行するまでを公開したところ、Dockerが便利ですよ!というコメントを頂いたのでさっそく、弊社お作法にのっとりブログでお返事したいと思います。

今回、Docker, AWS CLI, CloudFormationなど各コマンドにの詳細については割愛させていただいてます。
Docker を触ったことがない方、またAWS ECSの敷居を下げることができたら幸いです。

では、サクサク行きます。

Docker for Windows でやってみる

環境

  • OS: Windows10 Pro
  • Hyper-vに対応したCPU、また有効化が必要になります
  • 要ブロードバンド

Hyper-Vの有効化

管理者権限でPowerShellを起動します

PS C:\WINDOWS\system32> Get-WindowsOptionalFeature –Online -FeatureName Microsoft-Hyper-V-Hypervisor

FeatureName      : Microsoft-Hyper-V-Hypervisor
DisplayName      : Hyper-V Hypervisor
Description      : Hyper-V Hypervisor を提供します。
RestartRequired  : Possible
State            : Disable
CustomProperties :

無効になっていた場合は、有効にしてください

PS C:\WINDOWS\system32> Enable-WindowsOptionalFeature –Online -FeatureName Microsoft-Hyper-V-Hypervisor

Docker Community Edition for Windowsのインストール

Docker Community Edition for Windows のインストールを行います。
下記URLから
https://store.docker.com/editions/community/docker-ce-desktop-windows

※Docker IDを持たれていない場合は登録が必要になります
[Please Login to Docker]
を押下して、Docker IDの登録またログインを実施してください

から、[Get Docker!]を押下

ダウンロードした Docker for Windows Installer.exe を実行してインストール行います。 必要に応じて、再起動を実施して、インストールを完了してください。

インストール確認

Docker が起動すると、タスクバーにクジラのアイコンが見えると思います。
さてコマンドプロンプト or PowerShell より

PS > docker --version
Docker version 18.03.1-ce, build 9ee9f40

何はともあれ Hello World

PS > docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
~ 中略 ~
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

correctly. の文字が嬉しいですね。

DockerでPyton3.6の環境構築

早速、目的のDocker上でPython 3.6の環境が欲しいのですが 公式のイメージがありますのでそちらを取得して使います。 pull コマンドを使って取得します

PS > docker pull python:3.6
3.6: Pulling from library/python
cc1a78bfd46b: Pull complete
~ 中略 ~
Status: Downloaded newer image for python:3.6

早速Dockerで取得したPython3.6のイメージを起動します 今度は run です。

PS > docker run --name python36 -i -t python:3.6 /bin/bash
root@34cead30fd93:/# python --version
Python 3.6.5
root@34cead30fd93:/# exit

初回はダウンロード時間が若干かかるものの、あっという間のうちに入るのではないでしょうか また、Dockerでイメージでの利用ができる為、環境の再利用なども手軽にできます。

ついでと言って何ですが、Python 2.7 環境も同じようにしてみます。
今回はpullせずに実行してます、イメージがない場合取得して実行してくれます。(すでにあるものはスキップして必要なものだけ取得します)

PS > docker run -it python:2.7 /bin/bash
Unable to find image 'python:2.7' locally
2.7: Pulling from library/python
cc1a78bfd46b: Already exists <= (これはすでにあるからスキップ)
~ 中略 ~
Status: Downloaded newer image for python:2.7
root@c6b156d6d697:/# python --version
Python 2.7.15
root@c6b156d6d697:/# exit

おお。
Docker すばら。 さて、これだけですと、AWSの話がでてこなくて、BOSSがやってきそうなので
この勢いでAWS上のAmazon Elastic Container Service (Amazon ECS) で同じように実行してみます。

ECSでやってみる

本項内容を実際に実行する場合は AWS アカウントとアクセスキーとシークレットキーが必要になるので こちらの記事などを参考に取得しておいてください。
(※実際にアクセスキー、シークレットキーを多用する場合はgit-secretsなどの使用もご検討ください)

また課金が発生しますので、各コマンドについてご理解、ご注意の上実行ください。
ざっくりいうと、t2.microのインスタンスが立ち上がります。
また、秘密鍵で制限されているとはいえ、SSHポートがオープン状態となりますので ご注意ください。

また、手順の最後に課金対象のインスタンスは消すようにしますが 改めて消し忘れ等がないかAWSコンソールなどでご確認ください。

では、早速。

AWS環境初期設定

さて、丁度いいところに、Python 3.6の実行環境があるので AWS CLI をインストールします

PS > docker start python36
PS > docker attach python36
root@34cead30fd93:/# pip install awscli --upgrade --user
root@34cead30fd93:/# echo 'export PATH=~/.local/bin:$PATH' >> ~/.bashrc
root@34cead30fd93:/# source ~/.bashrc
root@34cead30fd93:/# aws --version
aws-cli/1.15.40 Python/3.6.5 Linux/4.9.87-linuxkit-aufs botocore/1.10.40
root@34cead30fd93:/# exit
PS > docker commit python36 my_awscli

AWS CLIの認証設定

AWS CLIの認証設定を実施します。
尚、ここからローカルのCドライブ上のdataディレクトリを コンテナと共有していきます。(ディレクトリは任意です) また、先ほどこそっと最後に commit した "my_awscli" のコンテナイメージを使用して新しくコンテナを起動します。 自分で作ったawscliのイメージが使いまわせるって、Dockerいいですね。 コンテナを起動し、アクセスキーとシークレットキーを設定します。

PS > docker run -it -v C:\\data:/root/data my_awscli /bin/bash
root@df2f35df4933:/# aws configure
AWS Access Key ID [None]: HOGEHOGE  <== ここ
AWS Secret Access Key [None]: FUGAFUGA <== ここ
Default region name [None]:ap-northeast-1
Default output format [None]:(Enter)

(疎通確認)
root@df2f35df4933:~# aws s3 ls

認証設定がうまくいっていれば、S3バケットのリストが出るはずです(バケットがない場合はエラーがでないことをご確認ください)

ECS クラスタ環境構築

後程コンテナインスタンスへログインを実施するのでキーペアを作成します。 キーの名前は[ecs]としています。すでにあるキーを使用される場合は後でパラメータで指定します。

# cd
# aws ec2 create-key-pair --key-name ecs --query 'KeyMaterial' --output text > /root/data/ecs.pem
# cp /root/data/ecs.pem ~/ecs.pem
# chmod 400 ~/ecs.pem

さてここからは、DOP認定の試験勉強を兼ねてAWS Cloud Formationを使って、一発でECSのインスタンスを作ります。 尚、こちらのCloud Formationスタックは、ECSの初回実行ウィザードを元にして、変更を加えています。

EC2用のRole, Profile, VPC,Subnet,Internet Gateway, AutoScaleGroup,ECSクラスタなどを作成し、最終的にはEC2ベースのECS環境ができあがります。

Cloud Formation テンプレートの取得

ファイルはGitHub上に置いてますので、インスタンス上から取得するか githubから取得してコンテナと共有しているディレクトリ(ここでは:C:\data)に保存してください。

CloudFormationテンプレート本体: EC2ContainerService.json
CloudFormationパラメータ: EC2ContainerService-param.json

# cd /root/data/
# wget https://raw.githubusercontent.com/ambasad/devio-blog-cfn/master/ecs/EC2ContainerService.json
# wget https://raw.githubusercontent.com/ambasad/devio-blog-cfn/master/ecs/EC2ContainerService-param.json

CloudFormation テンプレートのチェック

# aws cloudformation validate-template --template-body file://EC2ContainerService.json

一応執筆時点で(2018/06/23)動くことは確認していますが、もしエラーがでたらご容赦ください。 また、SSHポートをオープンしますので、必要に応じてEC2ContainerService-param.jsonを編集し 接続元のIPアドレス制限を実施ください。

パラメータファイル(EC2ContainerService-param.json)の[SourceCidr]になります。

小ネタ。自分のIPアドレス取得

# curl -s https://checkip.amazonaws.com/
# xxx.xxx.xxx.xxx

EC2ContainerService-param.json の下記部分を修正ください

{
    "ParameterValue": "xxx.xxx.xxx.xxx", <= 取得した自IPに変更してください
    "ParameterKey": "SourceCidr"
}

あとは、うまく動くことを願ってCloudFormationの実行をします。

# aws cloudformation create-stack --stack-name EC2ContainerService-python \
--template-body file://EC2ContainerService.json \
--parameters file://EC2ContainerService-param.json --capabilities CAPABILITY_IAM

状況確認

# aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE
{
    "StackSummaries": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:NNNNNNNNNNNN:stack/EC2ContainerService-python/XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX",
            "StackName": "EC2ContainerService-python",
            "TemplateDescription": "AWS CloudFormation template to create a new ECS First Run stack",
            "CreationTime": "2018-06-23T03:10:16.274Z",
            "StackStatus": "CREATE_COMPLETE"
        },

構築正常が終了していれば、画面上に表示されると思われます。
(AWS CloudFormationのコンソールなどでも確認できるかと思います)

続いて、ECS起動用のインスタンスができたのでECSのタスク定義を行います。

タスク定義

Python 2.7用のタスク定義(パラメータで定義)

# aws ecs register-task-definition --family python27 --container-definitions "[{\"name\":\"python27\",\"image\":\"python:2.7\",\"command\":[\"tail\",\"-f\",\"/dev/null\"],\"memory\":32}]"

Python 3.6用のタスク定義(jsonファイルで定義)
個人的にはこちらの方が見やすいので好きです。

# cat << EOT > python36.json
{
    "containerDefinitions": [
      {
        "name": "python36",
        "image": "python:3.6",
        "command": [
          "tail", "-f", "/dev/null"
        ],
        "memory": 32
      }
    ],
    "family": "python36"
}
EOT
# aws ecs register-task-definition --cli-input-json file://./python36.json

上記タスク定義を公開されている他のコンテナなどで置き換えれば さまざまなコンテナを起動することができます。 また、自分で作成したコンテナイメージ等も使用できますので 次回ECRを使用した作成方法なども検討したいと思います。

タスク一覧

# aws ecs list-task-definitions

タスク実行

# aws ecs run-task --cluster python --task-definition python27 --count 1
# aws ecs run-task --cluster python --task-definition python36 --count 1

上記のcountを2などにすると、自動で2つコンテナが起動されます。 挙動を見たい方はいろいろと変更してみてください。

EC2への接続

先ほどCloudFormationで起動したECSのコンテナインスタンスのホスト名を取得します。 タグ名で検索かけます。

# aws ec2 describe-instances --filters "Name=tag:Name,Values=ECS Instance - EC2ContainerService-python" --query Reservations[].Instances[].PublicDnsName
[
    "ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com"
]

出力されたホスト名がECSのインスタンスになります。

Pythonの実行

早速ですが、EC2に接続してPythonを実行してみます。

ssh -i ~/ecm.pem ec2-user@ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com

[ec2-user@ip-xxx-xxx-xxx-xxx ~]$ docker ps
CONTAINER ID        IMAGE                            COMMAND               CREATED             STATUS              PORTS               NAMES
7a995118a762        python:2.7                       "tail -f /dev/null"   9 minutes ago       Up 9 minutes                            ecs-python27-2-python27-c4c8aee9cbeff2b54800
2038b3d784f4        python:3.6                       "tail -f /dev/null"   9 minutes ago       Up 9 minutes                            ecs-python36-10-python36-ecf4aacc9b93dc844400
bc2bb882aab9        amazon/amazon-ecs-agent:latest   "/agent"              25 minutes ago      Up 25 minutes                           ecs-agent

[ec2-user@ip-xxx-xxx-xxx-xxx ~]$ docker exec -it 7a995118a762 /bin/bash
root@7a995118a762:/# python --version
Python 2.7.15
root@7a995118a762:/# exit
[ec2-user@ip-xxx-xxx-xxx-xxx ~]$ docker exec -it 2038b3d784f4 /bin/bash
root@2038b3d784f4:/# python --version
Python 3.6.5
root@2038b3d784f4:/# exit

作成したECSインスタンスが1つしかないため、すべてのおなじインスタンス上にある状態 となります。AutoScaleのインスタンスのパラメータを2に変更すると複数EC2インスタンが起動され分散されます。 AWSのコンソールなどから、いろいろとパラメータを変更したりして、挙動を確認するとなかなか面白いのではないでしょうか。

後片付け

では、課金されてしますので、さくさくと消していきます。

作成したECS環境を削除

aws cloudformation delete-stack --stack-name EC2ContainerService-python

ただここで、作成したAWS環境が消えるはずですが、執筆時点ではクラスタの削除がうまくいきません。(時間をおいて何度かリトライすると消えます) ここまできて、若干負けた気がしますが、AWSコンソールから作成したクラスタを削除した方が正常に終了しますのでそちらもご案内します。

AWS コンソールから削除

AWS コンソールからAmazon ECSのコンソールへ移動し 今回CloudFormationから作成したPythonのクラスタを選択します。

選択したクラスタのページより、「クラスタの削除」をクリックします。

削除確認のポップアップが表示されますので、押下し数分待ちますと削除されます。

まとめ

さて、pythonの環境が欲しいだけの理由であまりこういった使い方はしないのですが ECSを使ってみる垣根が少しは下がれば幸いです。 dockerやAWS CLI等のコマンドの説明をしていませんが各コマンドを調べてみてみるのも勉強になるかと思います。 次回はAWS ECRを使用したCloudFormation一撃作成などもやっていきたいと思います。