GitlabのCIでnpm+docker+git+awscliの実行環境構築

## 追記
`ecs_deploy.sh`の実行権限がなかったので、`chmod 500 ./ci/ecs_deploy.sh`を追記しました

## 背景
趣味のwebアプリ開発環境を一通り整えたが、最後のCIの部分を全く実装していかなった
GitHubを使っていたら[CodePipeline](https://aws.amazon.com/jp/codepipeline/)を使って楽々ECSにデプロイできたのだが、今回はGitlabで作成してしまった
そしてGitHubに移す気力はない

## 対象読者
- ECS/ECRでの継続デプロイを理解している人

## 目的
**理想は、masterにプッシュしたら、自動でgitlab runnerが走って自動でecrを更新しecsのサービスも更新してほしい(贅沢)**

今回の僕のweb環境は以下のようになっています。
RESTfulなので、フロントエンドとAPIサイドに分かれています。

```
├── Dockerfile
├── README.md
├── docker-compose.yml
├── front     <=フロントエンド側の開発ディレクトリ
├── nginx.conf
└── phalcon     <= webアプリケーションのディレクトリ
    ├── (略)
    ├── public   <= nginxのrootディレクトリ
```

これを踏まえて、CIサーバーでやらせたいことを掘り下げると、、、

- `docker`コマンドで、ビルドしたイメージをECRにプッシュしたい
- `aws`コマンドで、ECSの操作を行いたい
- frontディレクトリ内で、webpackのビルドを行い、その出力ファイル(index.htmlとその他staticファイル)をphalcon(webアプリケーション)ディレクトリ内の`public`ディレクトリ内にブッコミたい

これを実現するには、以下の課題をクリアする必要がある

- npmのインストール
- dockerのインストール
- awsコマンドのインストール


## 調査
gitlabCIについて一つ勘違いしていた。てっきりprivateなサーバーでのみ動かすことしかできないと思っていたが、どうやらGitlabが用意しているCIサーバーも制限があるが存在しているようである。自前サーバーで動かすのを`Specific Runners`というのに対し`Shared Runners`というらしい。１ヶ月１グループあたり2000回まで無料のようである。太っ腹。[参考元](https://about.gitlab.com/gitlab-com/settings/#shared-runners)

さらにさらに、どうやらimageを指定して、その中で動作するらしい。Dockerは新世界の神ではないか。
細かいCIの設定は`.gitlab-ci.yml`に以下のようなフォーマットでかく。以下は最小構成である、
`script`以下に、複数のコマンドを書くことができる。また、stageは複数定義可能であるが、コンテナの状態が引き継がれないので、基本的に1stageで行なっている

```yml
image: python:2.7

stages:
  - deploy

deploy_job:
  stage: deploy
  script:
    - XXXXXXXXXXXX
```

## awsコマンド
先ほどの、npm,docker,awsの中でインストールがめんどくさそうなのは`aws`という直感を元に`python:2.7`イメージを使用。デフォルトでawsコマンドが使えるので。python:2.7はDebianのjessieがベースになっているとのこと。

ここで、みんな大好き`AWS_ACCESS_KEY_ID`と`AWS_SECRET_ACCESS_KEY`を環境変数に登録する。`.gitlab-ci.yml`に書くと、gitに追跡されてしまうので、ここはgitlabの設定であらかじめ登録しておくこと

<img width="1114" alt="スクリーンショット 2018-05-15 0.14.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/158393/4e793da1-6fd6-7c09-a9d5-9a90298736c6.png">


ここまでで`.gitlab-ci.yml`は以下のようになった

```yml
image: python:2.7

stages:
  - deploy

deploy_job:
  stage: deploy
  script:
    - pip install --upgrade pip
    - pip install --upgrade awscli
```

## npm
フロントエンドには、`vue`を使っているが、webpackでビルドする必要がある。

無論`python:2.7`に入っていないので自力で導入。


以下を、install_npm.shとして作成

```sh
curl -sL https://deb.nodesource.com/setup_10.x |  bash -
apt-get install -y nodejs
```

これを踏まえて、`gitlab-ci.yml`は以下のようになる

```yml
image: python:2.7

stages:
  - deploy

deploy_job:
  stage: deploy
  script:
    - pip install --upgrade pip
    - pip install --upgrade awscli
    - sh ./ci/install_npm.sh
    - cd ./front
    - npm install
    - npm run build
```

## docker
ECSで動かすタスクで動くDockerイメージを更新するには、ECSというdockerリポジトリに、新たにプッシュする必要がある。

ぐぐるとよく出てくる方法はめっちゃ長かった。
1. 古いdockerをアンインストール
2. 周辺パッケージをインストール
3. GPG鍵の取得
4. .......以降省略

ダメ元でdocker公式読んだら、もっと簡単な方法があった。やはり[公式](https://docs.docker.com/install/linux/docker-ce/debian/#install-using-the-convenience-script)が正義

```sh
curl -fsSL get.docker.com -o get-docker.sh
sh get-docker.sh
```

これだけでいいとのこと。あっさり。

しかし、これだけでは、プロセスが停止したままなので、`service docker start`を加えて以下が最終形。`install_docker.sh`としてファイル作成

```sh
curl -fsSL get.docker.com -o get-docker.sh
sh get-docker.sh
service docker start
```

これでdockerがすぐに動かせる状況になった

## ECRにプッシュ
プッシュするタグ名は、複数箇所で使用するので、以下のように環境変数として定義。これはシークレットレベルは低いので、`.gitlab-ci.yml`に定義

```yml
deploy_job:
  stage: deploy
  variables:
    TAG_NAME: XXXXXXXXXXXX
  script:
(略)
    - aws_var=`aws ecr get-login --no-include-email --region ap-northeast-1 `
    - $aws_var
    - docker build -t kyujin .
    - docker tag kyujin:latest $TAG_NAME
    - docker push $TAG_NAME
```

### ECSを更新
最後にして最難関。愚直にやろうと思うと以下の手順でやる必要がある

1. タスク定義を先ほどプッシュしたイメージを含んで更新(リビジョン)
2. クラスター内のサービスを更新

非常にめんどくさい。CodePipelineならGUIでサクッとできたのでこちらは地獄のように感じる

以下のスクリプトを使わせてもらった。

https://github.com/silinternational/ecs-deploy

`ecs_deploy.sh`としてファイルを作成し、以下で実行。

```sh
sh ./ci/ecs_deploy.sh -r $REGION -c $CLUSTER -n $SERVICE -i $TAG_NAME -t 1800
```
これだけで`1`も`2`も自動でやってくれる。幸せ。

しかし、shでやってると怒られた

```sh
./ci/ecs_deploy.sh: Syntax error: "(" unexpected
```

どうやら、`Debian`でshをすると、`dash`が起動する影響らしい。微修正

```sh
./ci/ecs_deploy.sh -r $REGION -c $CLUSTER -n $SERVICE -i $TAG_NAME -t 1800
```

jsonのパーサーの`jq`も必要なのでサクッとインストール

```sh
apt-get install jq
```

## 結論
最終形は以下のようになった。めでたしめでたし。

```yml
image: python:2.7

stages:
  - deploy

deploy_job:
  stage: deploy
  variables:
    TAG_NAME: XXXXXXXXX
    REGION: YYYYYYYY
    CLUSTER: ZZZZZZZZZZZZZZZZ
    SERVICE: WWWWWWWWWWWWW
  script:
    - pip install --upgrade pip
    - pip install --upgrade awscli
    - sh ./ci/install_docker.sh
    - sh ./ci/install_npm.sh
    - cd ./front
    - npm install
    - npm run build
    - cd ./../
    - apt-get install jq
    - aws_var=`aws ecr get-login --no-include-email --region ap-northeast-1 `
    - $aws_var
    - docker build -t kyujin .
    - docker tag kyujin:latest $TAG_NAME
    - docker push $TAG_NAME
    - chmod 777 ./ci/ecs_deploy.sh
    - ./ci/ecs_deploy.sh -r $REGION -c $CLUSTER -n $SERVICE -i $TAG_NAME -t 1800
```

## 所管
- 日曜の午前3時には、masterプッシュして30秒後にはCIが走ってたが、月曜の今現在、masterプッシュして30分経っても走らずにPending状態。。。。やっぱり共有している以上、しかたないのか。。、。
- 趣味でやるぶんにはCI用意してくれるだけで御の字
- gitlabのドキュメント、日本語対応してくれんかなあ