【Elixir 1.11】Phoenix Framework + DB開発をDockerでやる #2（CIパイプライン導入）

この記事は「[CircleCI Advent Calendar 2020](https://qiita.com/advent-calendar/2020/circleci)」21日目の記事です。
きのうは@tmasuyamaさんの「[【CircleCI】初めて書いた愚直な設定ファイル(config.yml)を解説してみる](https://qiita.com/tmasuyama/items/d3a9965614b047e221fe)」でした！

# 概要

複数のカスタム__Docker__コンテナで構成されたWebアプリケーションの開発環境に、__CIパイプライン__を導入する。

- __[CircleCI](https://circleci.com/)__のMachine Executorによるコンテナビルド〜テスト実行CIパイプラインを構築し、__GitHub__リポジトリと連携します。
- 対象アプリケーションは、__[Elixir](https://elixir-lang.org/)__製のWebフレームワーク__[Phoenix](https://www.phoenixframework.org/)__で実装したCRUD WebUIとします。
  - 関連: シリーズ第1回目記事  
    [#1（コンテナ開発環境構築~CRUD実装~テスト）](https://qiita.com/im_miolab/items/84d9d71d109d689d267d)

- 次回シリーズ第3回目で、CDパイプラインを追加し自動デプロイ機能を導入します。
  - 後日リンクを貼ります

## 実行環境

| | バージョン | 備考 |
|:--|:--|:--|
| macOS | | |
| Docker (docker-compose) | | |
| __CircleCI__ | __2.1__ | Linux Machine Executor Image<br>Ubuntu 1604 |
| Elixir| 1.11.2 | compiled with Erlang/OTP 23 |
| Phoenix | 1.5.7 | | 
| PostgreSQL | 12.4 | |
| Node.js | 14.15.1 |　|

## ディレクトリ構成

```terminal:terminal
.
├── .circleci
│   └── config.yml
├── .env
├── app
│   ├── Dockerfile
│   └── my_app
├── db
│   ├── Dockerfile
│   └── postgres
└── docker-compose.yml
```

- 一部ファイルは表記省略しています（`.gitignore` など）

### コード

#### CI設定ファイル

```yml:.circleci/config.yml
version: 2.1
jobs:
  build:
    machine:
      image: ubuntu-1604:202010-01

    steps:
      - checkout

      - run:
          name: Build Docker containers
          command: |
            set -x
            docker-compose build
            docker-compose run --rm app bash -c "mix --version"
            docker-compose run --rm app mix archive
            docker-compose run --rm db bash -c "psql --version"
            docker-compose run --rm app bash -c "node --version && npm --version"

      - run:
          name: Mix deps.get
          command: |
            set -x
            docker-compose run --rm app bash -c "cd my_app && ls && mix deps.get && mix deps.compile"

      - run:
          name: Npm install in assets directory
          command: |
            set -x
            docker-compose run --rm app bash -c "cd my_app/assets && ls && npm install && node node_modules/webpack/bin/webpack.js --mode development"

      - run:
          name: Run Docker containers
          command: |
            set -x
            docker-compose up -d
            sleep 5
            docker ps -f status=running
            docker-compose logs

      - run:
          name: Mix format
          command: |
            set -x
            docker-compose exec app bash -c "cd my_app && mix format --check-formatted"

      - run:
          name: Mix test
          command: |
            set -x
            docker-compose exec app bash -c "cd my_app && mix test"

      - run:
          name: Down Docker containers and Finish pipeline
          command: |
            set -x
            docker-compose down --rmi all --volumes --remove-orphans
            echo "Finish pipeline"
```

#### 環境変数設定ファイル

```terminal:.env
PROJECT_NAME=my_app

POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_DB=testdb
POSTGRES_PORT=5432
```

#### Docker関係ファイル

```docker:app/Dockerfile
FROM elixir:1.11.2-slim

WORKDIR /usr/src/app

RUN apt-get update -y && \
  apt-get upgrade -y && \
  apt-get install -y \
  build-essential \
  curl \
  git \
  gzip \
  inotify-tools \
  ssh \
  sudo \
  tar

RUN apt-get install -y nodejs npm
RUN npm install -g n
RUN n 14.15.1
RUN apt-get purge -y nodejs npm

RUN yes | mix local.hex
RUN mix local.rebar --force
RUN mix archive.install hex phx_new 1.5.7 --force

RUN apt-get clean

CMD [ "mix", "phx.server" ]
```

```docker:db/Dockerfile
FROM postgres:12.4-alpine

ENV LANG ja_JP.utf8
```

```yml:docker-compose.yml
version: "3"

networks:
  backend:
    driver: bridge

services:
  app:
    build: ./app
    volumes:
      - ./app:/usr/src/app
    ports:
      - 4000:4000
    stdin_open: true
    tty: true
    command:
      sh -c "cd ${PROJECT_NAME} && mix phx.server"
    networks:
      - backend

  db:
    build: ./db
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    ports:
      - ${POSTGRES_PORT}:5432
    volumes:
      - ./db/postgres/tmp/data:/var/lib/postgresql/data
    restart: always
    networks:
      - backend
```

- 以下ディレクトリ（ファイル）は、後述の手順で別途生成します。
  - `app/my_app`
  - `db/postgres`


## コンテナ開発環境構築（まだの人向け）

__すでにシリーズ第1回目記事の環境構築が済んでいたら、ここは飛ばしてください。__

まだの場合は、 [第1回目記事の手順](https://qiita.com/im_miolab/items/84d9d71d109d689d267d)に沿って環境構築をしていきます。
ここでは、やることの概要だけを簡単に書きます。

- 前述のコード
  - `.circleci/config.yml`
  - `.env`
  - `app/Dockerfile`
  - `db/Dockerfile`
  - `docker-compose.yml`  
  を開発環境に準備します。

- コンテナビルドやPhoenixプロジェクト生成等のコマンドを実行していきます。

- 設定ファイルのコードを書き換えていきます。

# CIパイプライン導入セットアップ

ここから、CircleCIのセットアップをしていきます。

1. __GitHub__上に対象リポジトリを作っておきます。
  - ブランチを切っておきましょう。そちらにcommit pushしていきます。

1. __CircleCI__と連携します。
  - [CircleCI Projects](https://app.circleci.com/projects)ログイン画面から、__「Project」__を選択します。
    ![CircleCI Projects](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211052/0c1abb7c-b854-214f-483c-abb8d6954dd6.png)
  - 対象リポジトリの__「Set Up Project」__を選択します。  
      - 上記画像では「Unfollow Project」と表記されている箇所が、設定前には以下アイコンのように「Set Up Project」と表示されています。これを選択。  
      ![Set Up Project](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211052/5fe732d1-0603-4575-a3f0-2e1c04b2fb6c.png)
  - 画面の右あたりにある__「Use Existing Config」__を選択します。
    ![Use Existing Config](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211052/2e4ee603-c401-595f-e55c-74d5a9a338ed.png)
  - 以下のポップアップが出たら、__「Start Building」__を選択します。
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211052/658ef831-582a-fbcd-17fa-65a32becb202.png" width="400px">  
    「Pipeline」ページに移ったら、CircleCIとGitHubリポジトリの連携は設定完了です！  

    （イメージ）
        ![All Pipeline](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211052/3e69f67a-f925-3658-1d8f-8e2660a01183.png)

1. __CircleCI__側で環境変数を設定します。
  - 画面の右上にある__「Project Settings」__を選択します。  
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211052/644f2c78-2eee-1220-1f56-30c0e3a8e010.png" width="300px">
  - 左カラム__「Environment Variables」__を選択します。  
    登録する画面に切り替わったら、`.env`で設定しておいた環境変数を登録してください。  
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211052/e4ded4c9-b843-885c-7482-36fb7e2f6b1a.png">

1. リモートリポジトリにプルリクエスト作成します。

これで、`push`や`merge`することでCIパイプラインが走るようになりました！:rocket:

![スクリーンショット 2020-12-03 20.43.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211052/e5c0e15a-923b-a047-fd60-2c95adfde83a.png)

# 補足

### Machine Executor 

Executorのタイプに__`Machine`__を適用することで、LinuxVMイメージ（Ubuntu）内でjobを走らせることができます。

```yml:.circleci/config.yml
jobs:
  build:
    machine:
      image: ubuntu-1604:202010-01
```

Machine Executorを使用すると、Dockerプロセスにフルアクセスできるようになります。
これにより、カスタムDockerコンテナのビルドや実行をしています。

なお、記事執筆時点（2020年12月8日）では、Machine ExecutorはFree planでの使用も可能ですが、
> 将来の料金改定で Machine Executorの使用に追加料金が必要になる可能性があります

という公式アナウンスがされています。

- 参考 [Using machine (CircleCI)](https://circleci.com/docs/2.0/executor-types/#using-machine)

### Elixir / Phoenix

Elixirは、

- __膨大なアクセスやデータの高速処理__
- __高い開発効率と育成容易__

を両立できるプログラミング言語です。
とくにデータ変換に強みがあります。

ErlangVM上で動作し、__高度な耐障害性、並行処理性能、分散処理性能に優れている__といった特徴を持っています。

- 導入事例
    - 大量アクセスのあるWeb・APIサーバ構築
    - ビッグデータ分析／AI・機械学習向けデータ加工
    - IoT連携
    - FinTech

Phoenixは、ElixirのWebアプリケーションフレームワークとなります。

# おわりに

今回、複数のDockerコンテナで構成されたWebアプリケーションの開発環境に、CIパイプラインを導入しました。

アプリケーション側技術選定の例としてPhoenix + PostgreSQLコンテナでの構築を取り上げていますが、CircleCIで複数コンテナを扱う基本的な設定は同様の手順で可能です。
本記事で参考になる部分などがあれば幸いです。

次回は、こちらの実行環境にCDパイプラインを追加し、アプリケーションのデプロイ自動化環境を構築します。

明日の「CircleCI Advent Calendar 2020」22日目は、@makotomさんです。お楽しみに！

### kokura.ex

おしまいに、Elixirコミュニティの宣伝をすこしだけ。

- 北九州小倉のElixirコミュニティ__「kokura.ex」__は、「高速処理性能」と「高い開発効率性」を両立できるプログラミング言語「Elixir」と、そのWebアプリケーションフレームワーク「Phoenix」を北九州を起点として広め、ワイワイと盛り上げていく技術コミュニティです。
- オンラインでのもくもく勉強会を、毎月ペースで開催しておりますのでぜひどうぞ。
- これから先端技術をやりたい方や、最新のプログラミングを学びたい方、未来に向けてITに強くなりたい方など、技術への興味レベルが高い方や、プログラミングに関心が高い方のご参加を歓迎します。  
はじめてElixirに触れる方も、お気軽にご参加ください！
  - connpassイベントページconnpass __[fukuoka.ex](https://fukuokaex.connpass.com/)__  
    （イベント一覧より、kokura.exのページをご覧いただけます）

### Elixir アドベントカレンダー

「[Elixir Advent Calendar 2020](https://qiita.com/advent-calendar/2020/elixir)」もありますので、こちらもよろしかったらどうぞ！