GitLab CI


#書いてあること
CI(continuous integration)を導入したくて、使っていたGitプロダクトが「GitLab」だったので、
一番早く実現できそうだったGitLabCIにて構築したときの手順。

#GitLabCIの仕組み
登場人物

- GitLabサーバ
- GitLabCIRunner(以下Runner)
- .gitlab-ci.yml(以下yml)

**1文要約**
GitLabサーバの特定のブランチが更新時に、あらかじめ登録しておいたRunnerを起動して、
Runner上でymlに書かれた指示でデプロイを行う仕組みです。
            
わかりやすい図解などはまた時間がある時に書きます。。。
GitLab CI/CDはデフォルトで有効になっていました。バージョン：GitLab Community Edition 12.9.2

##GitLab CI Runnerの登録
まずはRunnerの登録から行っていきます。
GitLab自身はタスクのコントリビューターでしかないです。実際のタスクを実行するために「Runner」というプロセスが必要になります。
Runnerは様々なプロセスを動かす形態が選べるようですが、今回はdockerでの方法をとりました。
これはDockerやKubernetisなどのコンテナは毎回環境をリセットして行えるので安定性がありそうだなと、自分の技術スタックをもってしては一番理解できそうだったからです。
以下はRunnerを動かすマシンで行います。GitLab本体サーバのリソースがかつかつなら別マシンが良いでしょう。

Dockerのインストール

```shell
$ sudo yum install -y docker
$ sudo systemctl enable docker
$ sudo systemctl start docker
```
GitLab Runnerのインストール

```shell
$ sudo wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
$ sudo chmod +x /usr/local/bin/gitlab-runner
$ sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
$ sudo /usr/local/bin/gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
$ sudo systemctl enable gitlab-runner
$ sudo systemctl start gitlab-runner
```
インストールできたら、念のためGitlab Runnerが稼働していることを確認します。

```shell
$ sudo systemctl status gitlab-runner.service
● gitlab-runner.service - GitLab Runner
   Loaded: loaded (/etc/systemd/system/gitlab-runner.s
(略)
```
GitLab Runnerの登録

```shell
$ sudo /usr/local/bin/gitlab-runner register
Running in system-mode.

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
-->(GitLabのURL)
Please enter the gitlab-ci token for this runner:
-->(※1の方法で確認したトークン)
Please enter the gitlab-ci description for this runner:
[ip-172-31-19-124.ap-northeast-1.compute.internal]: -->(適当 例)for blob)
Please enter the gitlab-ci tags for this runner (comma separated):
-->(区別するためのタグ？なくてもok)
Registering runner... succeeded                     runner=5zhEJRnk
Please enter the executor: docker, shell, docker+machine, docker-ssh+machine, kubernetes, docker-ssh, parallels, ssh, virtualbox:
-->docker(※2)
Please enter the default Docker image (e.g. ruby:2.1):
-->node:latest(※3)
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
[ec2-user@ip-172-31-19-124 ~]$
```
※1 トークンはGitLabの画面の「Settings」→「CI/CD」→「Runnsers　settings」から確認できます。
※2 実行形態
※3 デフォルトのdocker image

Runnerは用途に合わせた２つの形態で管理できる。
形態１）**Shared Runner**
プロジェクト共通で使用できるRunnerです。ジョブのキューは「Fair usage queue」と呼ばれるアルゴリズムで実行されます。
形態２）**Specific Runner**
プロジェクトに固有のRunnerです。ジョブのキューはFIFO形式で実行されます。

GitLab RunnerはデフォルトではShared Runnerとして扱われます。「GitLab / Admin Area」 > 「Runners」のページから「Restrict projects for this Runner」に表示されている「group」を「Enable」にすることで「Specific Runner」に切り替えることができます。ただし、 一度Specific Runnerにした場合、Shared Runnerに戻すことができないため注意が必要です。

##Job定義の作成(yml)
さて、Runnerが準備できたところで次は指示書を書いていきます。
そのプロジェクトのリポジトリ直下に「.gitlab-ci.yml」のファイルで設置する仕様です。
今回はRunnerを動かすマシンとアプリのデプロイ先マシンが別なためそこはSCP転送で実現しました。
ビルド対象はVueアプリになります。自動テストは入れられておりません。

```yml:[.gitlab-ci.yml]
image: node:latest

stages:
  - build
  - deploy

build_site:
  stage: build
  variables:
    GIT_STRATEGY: none
    CLONE_URL: "https://gitlab-runner:{access-token}@myhost.domain.com/gitlab/xxxxx/yyy.git"
    TARGETRESOURCE: "app bootstrap build-package config database nbproject resources routes .env .env.mockup artisan composer.json package.json server.php setup.sh"
  before_script:
    - git clone -b staging $CLONE_URL
    - cd $PROJECTDIR
  script:
    - npm install --progress=false
    - npm run build
  artifacts:
    expire_in: 1 hrs
    paths:
      - $PROJECTDIR
  only:
    - staging
  after_script:
    - mkdir result
    - cd $PROJECTDIR
    - mv $TARGETRESOURCE ../result/.
    - cd ..
    - rm -Rf $PROJECTDIR
    - mv result $PROJECTDIR
    - echo "build complete!!"

deploy_site:
  stage: deploy
  variables:
    GIT_STRATEGY: none
    CLONE_URL: "https://gitlab-runner:{access-token}@myhost.domain.com/gitlab/xxxxx/yyy.git"
    TARGETRESOURCE: "app bootstrap build-package config database nbproject resources routes .env .env.mockup artisan composer.json package.json server.php setup.sh"
  before_script:
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client git -y )'
    - eval $(ssh-agent -s)
    - echo "$SSH_KEY" | tr -d '\r' | ssh-add - > /dev/null
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan $SERVER_IP  >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
    - ssh $USERNAME@$SERVER_IP "if [ -e $DEPLOYDIR/$PROJECTDIR ]; then cd $DEPLOYDIR/$PROJECTDIR; zip -r csf_`date +"%Y%m%d%I%M%S"`.zip $TARGETRESOURCE; mv csf_*.zip $BACKUPDIR/.; cd ..; rm -Rf $TARGETRESOURCE; else mkdir -p $DEPLOYDIR/$PROJECTDIR; fi"
  script:
    - cd $PROJECTDIR
    - scp -pr $TARGETRESOURCE $USERNAME@$SERVER_IP:$DEPLOYDIR/$PROJECTDIR/.
    - ssh $USERNAME@$SERVER_IP "cd $DEPLOYDIR/$PROJECTDIR; chmod 755 ./setup.sh; cat ./setup.sh; ./setup.sh; rm -f setup.sh;"
  only:
    - staging
  after_script:
    - echo "deploy complete!!"
```
SCPを行うためにコピー先サーバにrootユーザで鍵認証設定を行っておく必要があります。
[こちら](https://qiita.com/nnahito/items/dbe6fbfe347cd66ae7e6)を参考にさせて頂きました。

今回はdeployをdockerコンテナで行っているので、scpのログインユーザはrootになる。ただし実行ユーザはgitlab-runnerのようでした。
変数は「variables」セクションでymlに記述することもできます。秘密鍵など目につきやすい場所に入れるのに抵抗があるものは、GitLab側で記述することもできます。
[Project] -> [CI / CD Settings] -> [Variables]で設定可能です。

環境によってはCIのときにCloneするURLを変えたい場合もあるでしょう。Runnerを作成時に一緒に作られる設定ファイルを修正することで対応できるようです。

```diff:[/etc/gitlab-runner/config.toml]
(略)
[[runners]]
  name = "for develop"
  url = "https://domain.com/gitlab/"
  token = "xxxxxxxxxxxxxxxxxxxxx"
  executor = "docker"
+  clone_url = "https://domain.com/gitlab/"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
(略)
```

###403エラー対策
プライベートリポジトリなどでgitlab-ciのジョブでクローンが403になってエラーになる場合は以下で対策しました。

####deploy token作成
GitLabの管理サイト　(対象のプロジェクト)-> Settings -> Repository -> DeployTOkens で任意のユーザ名にて、read_repositoryオプションにチェックを入れて作成。このタイミングでした確認できないパスワードが生成されるのでメモっておきます。

今回はデフォルトURLでは上手くクローンできなかったので、gitlab-ci.ymlで自動クローンを無効にし自分でクローンする処理を入れて対応しました。

```diff:[.gitlab-ci.yml]
image: node:14.17.0

stages:
  - build
  - deploy
build_site:
  stage: build
  variables:
+    GIT_STRATEGY: none
  before_script:
+    - git clone https://{※1}:{※2}@example.com/gitlab/{subdirectory}/{repository_name}.git
    - cd csf
    - ls -l
  script:
(略)
```
※1がDeployToken作成時に自分で入れたユーザ名
※2がDeployToken作成時に自動で生成されたパスワード

###unable resolve host対応
gitlab-runnerがdockerでgitlabが同じサーバに素で上がっている場合は
config.tomlにdnsを設定する。

```diff:[/etc/gitlab-runner/config.toml]
[[runners]]
  name = "develop"
  url = "http://example.com/gitlab/"
  token = "xxxxxxxxxxxxxxxxxx"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
  [runners.docker]
+    dns = ["8.8.8.8"]
+    extra_hosts = ["example.com:xxx.xxx.xxx.xxx", "registry.npmjs.org:104.16.17.35"]
```

###ETIMEDOUT 対策
node install実行時に「registry.npmjs.org」などのライブラリサーバへの接続が「ETIMEDOUT」でできなくなる現象に悩まされました。
原因はメールサーバへの不明IPからのアクセス対策にとiptablesを入れたことにより、dockerのネットワーク環境に影響がでたためのようでした。
[こちらのページ](https://qiita.com/nanan/items/7bb21e3e01b16aee20bb)を参考にさせていただきました。
結論としてはiptables->dockerデーモンの順に起動すればよい。

###証明書エラー対応
CIで「server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none」のエラーでhttpsでリポジトリからクローンできない事象が発生。
おそらくroot証明書がきちんと入っていないようです。
対処療法的に以下で対応。

```diff:[/etc/gitlab-runner/config.toml]
[[runners]]
  name = "develop"
  url = "http://example.com/gitlab/"
  token = "xxxxxxxxxxxxxxxxxx"
  executor = "docker"
+  environment = ["GIT_SSL_NO_VERIFY=true"]
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
  [runners.docker]
```
よろしくない。証明書をきちんと改善したい。

##envの読み替え
ローカル環境と公開環境でenvの内容が異なる場合は、使いたいものを直下に.envにリネームして対応しました。
(phpで用意されているHOSTNAME環境変数で読み替えるように試みました。ただし、CIスクリプトでConfigリロードを行ってもデフォルト名の内容でしか動いてくれなったです。)

migrationまではスクリプトに含めました。
初期データは環境によって内容が変わると思うので、seed実行は手作業としました。