GolangのWebアプリケーションをECSにデプロイするCI環境をCodeCommit、CodeBuild、CodePipelineでつくる【cloudpack大阪ブログ】

cloudpack大阪の佐々木です。
内部で開発しているGolangのWebアプリをECSで稼働させるように、CI環境をつくってみました。
パブリックのgithubとかであれば、CircleCIとかでもう少しいい感じにできるとおもうのですが、ローカルのGitリポジトリで開発しているため、パブリックアクセスできないCodeCommitを使う形にしました。

概要

動作イメージは下記のような感じです。

Kobito.HvgCcI.png

  1. CodeCommitにプッシュ
  2. CodePipelineでコミットを検知し、CodeBuildを起動
  3. CodeBuildでコンパイルし、実行ファイルをビルド、S3に保存
  4. S3に保存後、ECSのサービスの既存タスクを停止
  5. サービスが新しいタスクを起動
  6. 新しいコンテナがS3から実行ファイルをダウンロードし、実行

環境

アプリケーション

もとのアプリケーションのファイルは下記のような感じです。

.
├── app
│   ├── model
│   ├── util
│   └── view
├── bindata.go
├── glide.yaml
├── server.go
└── static
    └── js

リージョン

CodeCommitが東京リージョン未対応のため、すべてus-east-1で作成します。

設定

CodeCommitリポジトリの作成

リポジトリの作成

リポジトリ名をgotestにします。
Kobito.WZjQG3.png

CodeCommitリポジトリにアクセスするユーザを作成

IAMでユーザを作成します。
AccessKeyを使ったHTTPSでのアクセスも可能ですが、SSH接続でやってみます。
ユーザを作成したら、認証情報のタブを開いて、AWS CodeCommitのSSHキーSSH 公開キーのアップロード をクリックし、SSH公開鍵を貼り付けます。
Kobito.zYMfwr.png

アップロードすると、SSHキーIDが発行されます。
Kobito.7LDjWt.png

SSHのconfigに情報を追加します。

~/.ssh/config
Host git-codecommit.*.amazonaws.com
    User `SSH キー ID`
    IdentityFile ~/.ssh/秘密鍵

コードをpushしておきます。

$ git push origin master                                                          
Counting objects: 41, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (34/34), done.
Writing objects: 100% (41/41), 73.27 KiB | 0 bytes/s, done.
Total 41 (delta 3), reused 0 (delta 0)
To ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/gotest
 * [new branch]      master -> master

Buildしたファイル保存用のS3バケットを作成

保存用S3バケットを作成します。

Kobito.hjTh2t.png

CodeBuildプロジェクトを作成

下記のようなプロジェクトを作成します。

Kobito.KHdvNj.png

環境イメージはあらかじめ用意されているGolangのものを使用しました。
当初、自分でコンテナを作成し、ビルド+Dockerコンテナ作成+ECRにプッシュという感じでやりたかったのですが、独自コンテナでDocker on Docker はできない(下記参照)、DockerイメージにGolangをインストールするとビルドのたびに時間がかかりすぎるということで、独自コンテナはあきらめました。
https://forums.aws.amazon.com/thread.jspa?messageID=761184

ロールには自動で生成されるPolicyに、ECSのタスクをコントロールするためにAmazonEC2ContainerServiceFullAccessを追加しています。

CodeBuild用のファイルを作成

下記の2つのファイルを作成します。

buildspec.yml
version: 0.1

phases:
  install:
    commands:
      - go get github.com/Masterminds/glide
      - go get -u github.com/jteeuwen/go-bindata/...    
  build:
    commands:
      - ./build.sh
  post_build:
    commands:
      - aws s3 cp /go/src/git.local/sasaki/gotest/server s3://gotest-pkg/server
      - aws ecs stop-task --task `aws ecs list-tasks --cluster $ECS_CLUSTER_NAME --service-name $ECS_SERVICE_NAME --region $AWS_DEFAULT_REGION --query taskArns --output text` --cluster $ECS_CLUSTER_NAME --region $AWS_DEFAULT_REGION
build.sh
mkdir -p $GOPATH/src/git.local/sasaki/gotest
cp -r app $GOPATH/src/git.local/sasaki/gotest
cp -r static $GOPATH/src/git.local/sasaki/gotest
cp server.go $GOPATH/src/git.local/sasaki/gotest
cp bindata.go $GOPATH/src/git.local/sasaki/gotest
cp glide.yaml $GOPATH/src/git.local/sasaki/gotest
cd $GOPATH/src/git.local/sasaki/gotest
glide up
go-bindata app/view
GOOS=linux GOARCH=amd64 go build server.go bindata.go

buildspec.yml がCodeBuildの設定ファイルになります。
CodeBuildeでGolang用のコンテナを起動しますので、goコマンドは使える状態になっています。
まずinstallフェーズでgolangのglidego-bindataをインストールします。

次にbuildフェーズでbuild.shを実行します。
開発時はローカルにあるGitリポジトリを使っているので、glide up でエラーになることを回避するために、ローカル環境と同じパスにソースをコピーしています。
そのあと、go build でコンパイルしています。

post_buildフェーズでは、S3に実行ファイルをアップロードし、ECSの現在のタスクを停止しています。
artifactでもアップロードはできるのですが、post_buildのECSタスク停止後にアップロードされ、タスクに新しいファイルが反映されないため、このようにしています。

ECRにDockerイメージを作成

下記のDockerfileでDockerイメージを作成し、ECRにプッシュしておきます。

Dockerfile
FROM ubuntu:16.04
RUN apt-get -y update
RUN apt-get install -y python-pip
RUN yes | pip install --upgrade awscli
RUN apt-get remove -y python-pip
ADD start_gotest.sh /
ENTRYPOINT ./start_gotest.sh
start_gotest.sh
aws s3 cp s3://gotest-pkg/server ./
chmod +x server
./server

UbuntuにS3でビルドしたファイルをダウンロードし、実行するだけです。
Alpineでやると、 ./server がどういうわけか、not foundになるので、断念・・・

ECS設定

ECRに保存したイメージを常時稼働するように、タスク定義、サービスの設定します。

CodePipeline

CodePipelineを作成します。

ソースの場所 は作成したCodeCommitリポジトリを指定します。
Kobito.H6ZYi8.png

ビルド は作成したCodeBuildを指定します。
Kobito.xAGCCv.png

デプロイはCodeDeploy等は使用しないので、デプロイなしを選択します。
Kobito.ppkLhz.png

実行

作成したリポジトリに、buildspec.yml と、build.shを追加し、プッシュすると、CodePipelineが反応し、ECSのタスクが再起動されるまで自動実行されます。

Kobito.NZkYFB.png

イケてないところ

  • CodeBuildで使うコンテナが融通がきかない

    • 独自コンテナではDocker on Dockerができないとか
    • Docker + Go等 みたいなコンテナがないとか
    • 本来はビルドプロセスでコンテナイメージつくってECRにプッシュするまでやりたい
  • Artifactが使えてない
    • そもそもあんまりよくわかってない・・・
  • ECRに保存しているアプリケーションを動かすだけのコンテナのサイズがデカすぎ
    • S3からダウンロードするためにubuntuにpipインストールして、aws-cliをpipインストールしただけで、こんなに・・・
ubuntu              16.04               117 MB
gotest-image        latest              462 MB

続きを読む

AWSにDjango環境を構築する

versions

Python : 2.7
Django : 1.10

Environment

EC2インスタンス : Amazon Linux
WSGI : gunicorn
Proxy : nginx

install python packages

sudo yum update
sudo yum install gcc
sudo yum install python-devel
sudo yum install python-psycopg2
sudo yum install postgresql-devel
pip install -r requirements.txt

reverse proxy

sudo yum install nginx
sudo vi /etc/nginx/nginx.conf
  • nginx.confに追記 line : 49
location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# http://mydomain/static/ をDjangoのsettings.pyでSTATIC_ROOTに指定したディレクトリへルーティング
location /static/ {
    autoindex on;
    alias   /usr/share/nginx/html/django-project-name/staticfiles/;
}
sudo chmod 777 -R /usr/share/nginx/html ※1
sudo service nginx restart
gunicorn django-project-name.wsgi -D

Memo

python manage.py runserver 0.0.0.0:8000  # 全てのホストアドレスからのアクセスを許可
python manage.py migrate
python manage.py createsuperuser

# 事前にsettings.pyにSTATIC_ROOTを設定する (defaultでは記載されていない)
python manage.py collectstatic  # STATIC_ROOTに指定したディレクトリに静的ファイルが生成される

※1 : このディレクトリ下にDjangoプロジェクトを設置
当初はSTATIC_ROOTに設定したディレクトリのシンボリックリンクのみをここに貼ったが
静的ファイルを読み込む際に403エラーが発生してしまうためプロジェクトごとこのディレクトリに設置
所有がrootのディレクトリに対して、パーミッションを777に設定してしまうのは多少疑問が残るところ

SSL証明書

証明書はAWS Certificate Managerで発行し、Elastic Load Balancerにインストール
ELBとEC2間の通信はhttpとなるが、ELBとEC2が同じリージョンにある場合セキュリティの問題はなさそう

続きを読む

Amazon Dash ButtonをHackするのはJust do itでしょ、と思って手を出したら落とし穴にどハマった件

Exective Summary

そもそもやりたかったこと:

Amazon Dash Buttonを押して、AWS上の全EC2を停止する。
(利用料が個人レベルでない上に、お小遣いが少ないもので…)

前提・制約:

(甲) 我が家にはWindows10マシンしかない(Linuxマシンはない、という意味)。
(乙) 一方、AWS Dash ButoonをHackするツールとしては、Linux/OS X向けNode.js系ツールが主流らしい([1],[3],[5],[9])。
⇒Amazon Dash Buttonは、ボタンを押すとARPリクエストを送出(ブロードキャスト)する。それを同じLANで待ち受けて検知できるのがDasher。その検知をトリガーにして任意の処理(WebAPIを呼び出す、等)を起動できる。
(丙) 我が家にはAWS Dash Buttonが2つある。それは、購入商品の指定/設定を、
  (A) 意図的に途中でやめてあるモノ
  (B) 無邪気に最期まで完了した(知らずに済ませてしまった)モノ
  の2種類だ。
(丁) 呼び出されたら「全EC2を停止する」というAPIは、AWS IoT/API Gateway/Lambdaのサーバレスアーキで作成済み。
⇒世間の情報も豊富だし、小1時間程度でできるっしょーとたかをくくる。

俺的に考え得た・試した選択肢

①Windows10+Dasher・・・×
②Windows10+Bash on Windows+Dasher・・・×
③Windows10+Cygwin+Dasher・・・×
④Windows10+win-node-dash-button・・・×
⑤Windows10+Vagrant+VirtualBox+Ubuntu+Dasher・・・○
⑥Windows10+Docker-Machine+VirtualBox+Boot2Docker+Dasher・・・×
⑦Windows10+Docker-Machine+VirtualBox+Boot2Docker+Docker+Ubuntu+Dasher・・・未

紆余曲折の結果が…コレだ…ワン・ツー・スリー…

⑤Windows10+Vagrant+VirtualBox+Ubuntu+Dasher

学べる(学べた)コト

・Git
・node.js/npm/node-gyp/node_pcap
・python/pip
・Bash on Windows/Cygwin/Ubuntu
・Docker-Machine/Vagrant/VirtualBox
・Wireshark/tcpdump
・NW/ARP/DNS

嵐の後の所感

やってみた結果として、Amazon Dash Buttonは幅広く技術を体験するのに良いテーマだったという実感。
(「フェルマーの最終定理」と同じ構図?定理の意味の理解はトリビアルだが、その証明を理解するには色々な数学的知識が必要!)
次は『Dash Button(丙)-(B)』のHackを試みる…(方向性としては『DNSサーバをポジティブに偽装する』)
…まあ、AWS IoT Buttonが日本でも発売されたら、それを使うけどね…

俺が落ちた落とし穴の各論

以下、詳細:(細々と続く)

参考情報

[1] Amazon Dash ButtonをただのIoTボタンとして使う
[2] node-dash-button@GitHub
[3] Amazon Dashボタンのハックにおける期待と落胆(Wi-Fi編)
[4] dasher@GitHub
[5] Amazon Dash Buttonを(正しくない方向で)使ってみた
[6] Dash Button for Node@GitHub
[7] node_pcap
[8] node_pcapのWindows7対応Folk
[9] win-node-dash-button
[10] Windowsでnpm installしてnode-gypでつまずいた時対処方法
[11] これはすごい!分解とパケット解析で分かったAmazon Dash Buttonの「本気度」
[12] これはすごい!Amazon Dash Buttonをプレゼンに使う
[13] これはすごい!Amazon Dash Buttonでツイートできた

続きを読む