Docker Composeの使い方（WebアプリケーションをDockerで構築する）

## 注意
今回は、docker-composeの使い方をメインに解説していきますので
Railsの説明が不十分になっております。
そのため、先にお詫びさせていただきます。

## Dockerを学ぶきっかけ
現在、機械学習エンジニア1年目として日々学習に取り組んでおり、
本来であれば、ML基盤の構築をKubeflowなどでやりたいのですが、
肝心なDockerの知識をまずしっかり入れないとなと思い、投稿しました！

## 参考にした教材
https://www.udemy.com/course/aidocker/

今回はこちらを参考にして学習しました。
Dockerfileの書き方から、
AWSのEC2を使って実際にディープラーニング環境を作っていたりと、本当に良い教材でした。
ぜひみなさんも買ってみてください。

## 1.DockerComposeとは？
Docker Composeは、Dockerを使用して複数のコンテナを定義し、実行するためのツールです。Docker Composeを使用すると、複数のDockerコンテナを単一のアプリケーションとして定義でき、複雑なマルチコンテナ環境を簡単に構築および管理することができます。

Webアプリケーションの開発をする上でも、とても便利なものになります。
図にするとこんな感じです。

もし、WebアプリフレームワークにRailsを使うのであれば
Rubyのコンテナ
DBは、Postgresqlを使うのであれば、
DB用のコンテナ
この２つを使って、実際にWebアプリケーションを作成します。

ここでの鉄則は、1つのコンテナには１つのアプリケーションになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2526914/1fe1a897-820a-022f-18cf-70fc29f1dbad.png)
## 2.Docker-composeを使ってWebアプリケーションを作っていこう。
### 2.1作業ディレクトリの作成
まずは作業ディレクトリを作成しましょう。
mkdirなどでDesctop上にproject-registerという作業ディレクトリを作成しました。

### 2.2 Dockerファイルの作成（全体像）
まずは、Dockerfileから作成していきましょう。細かい部分は追って説明します。

```Dockerfile
FROM ruby:2.5

RUN apt-get update && apt-get install -y \
  build-essential \
  libpq-dev \
  nodejs \
  postgresql-client \
  yarn

WORKDIR /product-register
COPY Gemfile Gemfile.lock /product-register/
RUN bundle install
```
#### 2.2.1 FROM
FROM
ベースとなるイメージを決定しています
今回の場合、Docker Hubにあるrubyの2.5を取得してきています。

#### 2.2.2 RUN apt-get update && apt-get install -y
apt-get update && apt-get insatall
apt-getというのは、Ubuntuのパッケージ管理ツールになります。

これは下に記載のそれぞれのパッケージを新しいものに更新（update）し、installでそれらのパッケージをインストールしています。
-yは、それらのパッケージを強制的にインストールさせるコマンドになります。

rubyを動かす上では、これらのパッケージが必要なのでインストールしております。
実務では、これらの依存パッケージなどは手探りで探す必要があります。
今回は、必要なパッケージが何かがわかっているので、このように記述しております。

##### RUNについて細かく説明します
実は、上の記述はうまくまとめていますが、こんなふうに記述もできます。
```Dockerfile
RUN apt-get update && apt-get install -y build-essential
RUN apt-get update && apt-get install -y libpq-dev
RUN apt-get update && apt-get install -y nodejs
RUN apt-get update && apt-get install -y postgresql-client
RUN apt-get update && apt-get install -y yarn
```
ではなぜ、このように書かないのか？
それはDocker image自体が大きくなってしまうからです。
さらにいうと、Image layerが積み木状態になってしまうのです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2526914/31cc3f62-6578-e5d4-d374-c8d44f4b5400.png)

Docker Imageの作成のベストプラクティスとして、
☑︎Docker ImageのLayer数は最小限にする

これが大切になっています。

##### cashも重要
例えば、hogehogeというパッケージを追加したいなーって思う時があったとします。

```Dockerfile
FROM ruby:2.5

RUN apt-get update && apt-get install -y \
  build-essential \
  libpq-dev \
  nodejs \
  postgresql-client \
  yarn

RUN RUN apt-get update && apt-get install -y hogehoge
```

このDocker image　を使ってまたImageを作成する際は、cashが使われます。
すでに上の部分はインストールされているので、わざわざインストールする手間が省けますよね。

失敗例として、新たにhogehogeをバックスラッシュの後に追加してしまうと、新たなレイヤーが作成されることになります。

つまり、すでにあるパッケージも新たにapt-getを実行してしまいます。
パッケージが20,30なんてあると、Imageを作成するにも1日かかってしまう場合があります。

なので、新たにパッケージをインストールする場合は、cashを使うようにしましょう。

#### 2.2.3 WORKDIR
これは単純にDocker上での作業する場所のディレクトリを指定しています。

#### 2.2.4 COPYコマンド
これは、HOST側にあるデータをDocker上にコピーするコマンドになっています。

COPY <コピーさせたいもの> <どこに配置させるか？>

#### 2.2.5 RUN bundle install

bundle installは、Rubyプログラムに使用される依存関係の管理ツールであるBundlerを使って、RubyアプリケーションのGem（Rubyのライブラリやパッケージ）をインストールするコマンドです。

Gemfileというものを読み込み、Gemfileに記述された依存関係を解決し、必要なGemのバージョンを特定します。これは、指定されたGemのバージョンがGemfileに記述された範囲内にあるかどうかを確認し、依存関係を満たすようにGemのバージョンを選択する作業です。

Webアプリケーションの構築について、詳しくないのでこれ以上細かく説明はできませんが、こんな感じです。

WORKDIRとCOPYで移したディレクトリが一致していますね。
そこにGemfileを持ってきているので、これらのコマンドは正常に動いています。

### 2.3 Gemfileの作成

```
source 'https://rubygems.org'
gem 'rails' , '~>5.2'
```
source:どこからGemを取ってくるのか
gem どのバージョンのrailsを使うか？

これらを記載しています。

### 2.4 Docker build
これでDockerfileを作成することができました。

Dockerのコンテナは、Imageを元に作成されるので、
まずはDocker fileを元に、Docker buildでImageを作っていきます。

```
docker build .
```
カレントディレクトリを指定していますが、DockerfileやGemgile.lockがある作業ディレクトリを指しています。
うまく作成することができたら、
```
docker images
```
これを使って、Imageが作成されたことを確認してください。

### 2.5 docker-compose

Dockerを学んだことがある方は、
「あぁ、この後は docker run をすればいいんでしょ？」
と思うかもしれません。
確かに正しいです。それでは実際に、docker runをしていきましょうか。。。

```docker
docker run -v ~/Desctop/product-register:/product-register -p 3000:3000 <Image ID>
```
#### docker run -v
これは、マウントの作業でホストのディレクトリとDocker上のディレクトリをつなげる役割をしています。

#### docker run -p
ポート番号の指定です。
Railsのデフォルトのポートは3000なので、3000:3000としています
これでホストとコンテナがつながります。


#### 長くない？

そうなんです、docker runコマンドってこう書くととても長いですよね。
それを簡単に実行させることができるのが、docker-composeなんです。

また、次に紹介しますが、複数のコンテナを実行させるのが一番の魅力だったりします。

docker-composeを使って、runと同じことをすると
```
docker-compose up 
```
これで終わります。
「ええ？？楽すぎん？？」
そうなんですよね。楽なんです。

### 2.5.1 Docker-composeを書いていく

それでは書いていきます。と言っても、docker-runで書いたものとあまり変わらないんですよ。

```docker
version: '3'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '3000:3000'
    volumes:
      - '.:/product-register'  
    tty: true
    stdin_open: true

```

#### version
これはdocker-composeのバージョンになります。大体は3らしいです。
これからdocker-compose書くよ〜みたいな宣言だと思ってください。

#### services

yamlファイルというのは、キーバリュー形式で書きます。
なので、これは親のキーだと思ってください。

この親のキーに対して、runしたいものを記載していきます。
サービスの名前としてwebをつけています。

#### build
docker-compose upでは、実はbuildもしてくれます。
imageを作っていないのであれば、Docker fileを参照してImageを作ってくれます。

#### port
これはパブリッシュしたいポート番号を指定しています。

#### volumes
マウントさせたい場所を指定しています。
これは相対パスとして、カレントディレクトリを指定してあげてください。

なぜかというと、他の人が扱いやすいようにするためです。
自らの絶対パスで必ずしも他の人が使うわけではないので、これはそういうものだと覚えておきましょう。

#### tty,stdin

これはdocker run -itのitを示しています。
細かいことは省略しますが、
stdinという入力チャネル
ttyというのはコマンとの出力結果を綺麗に表示させる役割を持っています。
そのため、おまじないとして入力しておいてください。

### 2.5.2 docker-compose up を実行していく。

では実際に書いていきます。
```
docker-compose up -d
```

-d オプションは、Docker Compose コマンドの中で、コンテナをデタッチモードでバックグラウンドで実行するためのオプションです。

具体的には、docker-compose up -d コマンドを実行すると、Docker Compose は定義されたサービスのコンテナをデタッチモードで起動します。デタッチモードでは、コンテナはバックグラウンドで実行され、コンソールにログが表示されません。これにより、コンソールをブロックされることなく、バックグラウンドでサービスを実行することができます。

これで、docker-composeの使い方を一通り説明はしましたが、
まだ、Railsのコンテナしか作っていないですよね。

複数のコンテナを動かせるのが、最大のdocker-composeの利点なので、そこまで説明していきます。

まずは、Railsの設定をしていきます。

### 3.Railsの設定
この設定をする前に、まずはコンテナに入る必要があります。
先ほどでコンテナを立てたかと思うので、その中に入るコマンドはこの通りです。
```
docker-compose exec web bash
```
サービス名を指定したかと思うので、そのwebを入力し、bashコマンドで実行させるためにbashを指定しています。

今回は、DBにPostgresqlを使っていくんですが、その際に、
```
rails new 
```
というコマンドを使っていきます。全て書くとこんな感じになります。

```
rails new . --force --database=postgresql --skip-bundle
```
#### --force
これは全てを上書きしてくれることを指しています。
今回は、最初なので特に意味はないですが、書いていきます。

#### --database
どのDBサーバーを使うかを設定します。
これを書くことによって、Postgre用のアプリを作ってくれるのでめっちゃ便利です。

#### --skip-bundle
Gemfileが新しくなれば、bundle installを行なわれますが
これはDocker file側でbundle installを設定しているので、ここではスキップしています。

それでは上のコマンドを書いていきます。
そうすると、
![スクリーンショット 2023-04-17 18.16.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2526914/38575b08-af9a-61c2-c9ea-06269bf900fa.png)
こんなにファイルが作られたのがわかります。
今回は、docker-composeの説明なので、細かいことは省略しますが、
自動的にPostgresql用のアプリを動かすためのアイテムを取ってきています。

#### 3.1 Gemfileが更新されたので、docker-composeを再度行う。
先ほど、--skip_bundleを行なったので、一度、コンテナから抜けて、再度docker-composeを行う必要があります。
ですが、先ほどdocker-composeを行なったので、先ほど作ったImageがそのまま使われることになってしまいます。
そのため、docker-compose upのオプションで--buildがあります。
それをすることによって、再度強制的にbuildを行なってくれます。

```
docker-compose up --build -d
```
これで再度コンテナが作られました。
コンテナに戻って、
```
rails s -b 0.0.0.0
```
これを実行すると、Railsのサーバーを立てることができます。
実際に、ブラウザでlocalhost:3000と入力してみると、

エラーでconnectionエラーが起きます。
当然ですが、DBのコンテナを作っていないので当たり前ですよね。

次に、docker-compose内に、DB用のコンテナを作っていきます。

### 4.DBのコンテナの作成と⇨RailsとDBのコンテナを接続してあげる。

まずは、作業ディレクトリの各ファイルを設定してあげる必要があります。
該当の箇所をこの通り記述してください。
この説明は省略します。
/config/database.yml
```
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  user: postgres
  port: 5432
  password: <%= ENV.fetch("DATABASE_PASSWORD") %>
  # For details on connection pooling, see Rails configuration guide
  # http://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
```
/config/routes.rb
⇨localhost:3000とブラウザで入力する際に、表示のページを変えるための設定。
```
Rails.application.routes.draw do
  root 'products#index'
  resources :products
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

```

早速ではありますが、docker-composeを作っていきます。


```Dockerfile
version: '3'

volumes:
  db-data:

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '3000:3000'
    volumes:
      - '.:/product-register'  
    environment:
      - 'DATABASE_PASSWORD=postgres'
    tty: true
    stdin_open: true
    depends_on:
      - db
    links:
      - db

  db:
    image: postgres
    volumes:
      - 'db-data:/var/lib/postgresql/data'

    environment:
      - 'POSTGRES_USER=postgres'
      - 'POSTGRES_PASSWORD=postgres'

```

dbのサービス名の部分について説明します。

#### image
これはすでにpostgresというイメージが作られているので、そのまま指定しています。

#### volumes
ローカルの場所とコンテナの場所を接続しています。
コンテナにDBがあるとします。
何もしなければ、コンテナが消えたら当然DBも消えます。そのため、ローカル上の場所と接続することが大切です。

ですが、db-dataという場所はどこにもまだ作っていないですね。

作ってあげるのが、上に記載されている
volumesになります。
これを指定してあげることによって、ディレクトリが作られることになります。

#### environment
これはPostgresqlを使ったことがある方はわかるかと思いますが、ユーザー名やパスワードを指定する必要があります。
それを与えているわけです。

次にWebのサービスを見ていきましょう。いくつか追加されています。

#### depends_on:
これは先にdbのコンテナを作ってくれよと指定しています。
WebサービスはDBがないと作成されないので、順番を決める必要があります。


#### links:
WebサービスとDBを接続してあげる役割ですね。これで２つのコンテナが接続されることになります。

こんな感じで、docker-compose.ymlを変更します。
Railsの説明はかなり省いてしまい申し訳ないです。

### 4.1 最終の設定を行う。
それでは最終の設定を行なっていきます。

①docker-composeが変更されたので、再度入力していく
```
docker-compose up -d
```

②docker-compose exec web bashでコンテナに入っていく。
この時点ではもうDBコンテナと接続がされている。
```
docker-compose exec web bash
```
③rails db:createでDBサーバーを作る。
```
rails db:create
```
④Webアプリを構築していく
これについては、詳しく説明しません。簡単にWebアプリを一括で構築するものとして考えてください。

```
rails g scaffold product name:string price:integer vendor:string
```
⑤テーブル定義していく
```
rails db:migrate
```
⑥Webアプリを起動する
```
rails s -b 0.0.0.0
```
![スクリーンショット 2023-04-17 19.12.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2526914/cfbeffad-a1d9-7c51-2050-48f1ffe89055.png)


こんな簡単なWebアプリを構築することができました。

![スクリーンショット 2023-04-17 19.13.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2526914/300eb977-42f6-a6fc-445f-fa1afe1b6db7.png)

![スクリーンショット 2023-04-17 19.16.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2526914/73569e61-7c9a-5f16-2916-fac0dde39a59.png)


これで、docker-composeで簡易的なWebアプリケーションを構築することができました。

## 5.次回はCICDの構築をしていく。

今回は、Dockerを使って、Webアプリケーションの構築を行いましたが、
次は、このアプリケーションを使ってCICDの構築をしていきたいと思います。

### 5.1　CICDとは
CICDは、「Continuous Integration and Continuous Deployment（継続的インテグレーションと継続的デプロイメント）」の略称で、Webアプリケーションの開発プロセスにおける自動化されたプラクティスを指します。これにより、開発者がコードの変更を効率的に統合し、テストを実行し、デプロイを自動化することができます。

CICDは、以下の3つの主要なステップから構成されます。

Continuous Integration（継続的インテグレーション）: 開発者がコードの変更を共有リポジトリに統合するプロセスを指します。これにより、複数の開発者が同時に作業する場合でも、コードの競合や互換性の問題を早期に検出することができます。

Continuous Testing（継続的テスト）: コードの変更が統合された後、自動化されたテストスイートを実行して、アプリケーションの品質を確認します。これにより、バグやエラーを早期に発見し、品質を確保します。

Continuous Deployment（継続的デプロイメント）: コードの変更がテストを通過した後、自動的に本番環境にデプロイされるプロセスを指します。これにより、新しい機能や修正を素早くリリースし、ユーザーに提供することができます。

CICDの利点には、開発プロセスの効率化、品質の向上、デプロイの自動化によるヒューマンエラーの低減、迅速なリリースの実現などがあります。これにより、Webアプリケーションの開発とデプロイのスピードと信頼性を向上させることができます。

### 5.2 次回の展望

次は、このアプリケーションの開発フローを自動化していきたいと思います。
それではまた。