Ruby3.0×Rails6.1×Docker×CentOS Streamで環境構築した際の備忘録

# 目的
できる限り最新の環境を、最低限で構築したい。

# 前提条件
## 開発環境
- macOS Catalina(ver. 10.15.7)
- Docker Desktop(ver. 3.03)
- Docker(ver. 20.10.0)

## お断り
- あくまでも試験的に構築した環境です。
- 諸事情により、1つのコンテナに複数の役割を持たせています。
- 実際の実行時に「#」である場合であっても、「$」で表記しています。
- パスワードを直書きしていますが、これは試験的な環境であるためです。

# 実施
## ベースになるイメージの作成
### ディレクトリの作成
```shell
$ mkdir -p containers/base_image
$ cd containers/base_image
```

### Dockerfileの記述

Dockerfileを記述する。

```shell
$ vi Dockerfile
```

[Docker Hubの記述](https://hub.docker.com/_/centos)を参考に、systemdを使用可能にする。
[CentOS Streamの公式情報](https://www.centos.org/centos-stream/)を参考に、CentOS8からCentOS Streamに移行。

[Dockerfile]

```docker
FROM centos:latest
MAINTAINER tora_tz

ENV CONTAINER_NAME local/cos-stream
ENV CONTAINER_VERSION 20210104_01

# Centos Stream に移行
# CentOS Streamのリポジトリ情報をインストール
RUN dnf install -y centos-release-stream

# リポジトリ情報を削除
RUN dnf swap -y centos-{linux,stream}-repos

# 最新のパッケージに同期
RUN dnf distro-sync -y
```

### イメージのビルドを実行

```shell
# ビルド
$ docker build --rm -t local/cos-stream .
# ビルドされたことを確認
$ docker images
REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
local/cos-stream   latest    f0ccd16bd6fc   16 seconds ago   526MB
```

## 開発のベースとなるイメージを作成
Rubyが入っている、最低限開発に必要なイメージを作成します。
### Dockerfileの作成
まずは最低限の内容での確認

```shell
$ mkdir ../dev_base
$ cd ../dev_base
$ vi Dockerfile
```

[Dockerfile]

```docker
FROM local/cos-stream
MAINTAINER tora_tz

# system update
RUN dnf -y upgrade; dnf clean all;
```

### イメージのビルドを実行
```shell
$ docker build --rm -t local/dev_base:0.1 .
(中略)
Successfully tagged local/dev_base:0.1
$ docker images
REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
local/dev_base     0.1       cab625bee967   10 seconds ago   532MB
local/cos-stream   latest    f0ccd16bd6fc   10 minutes ago   526MB
```

### コンテナを作成
```shell
# 作成、起動する。
$ docker run -dit --name dev_base_container local/dev_base:0.1
# 作成されたコンテナを確認する。
$ docker ps
CONTAINER ID   IMAGE                COMMAND       CREATED         STATUS         PORTS     NAMES
ae0b8ebe78b2   local/dev_base:0.1   "/bin/bash"   5 seconds ago   Up 5 seconds             dev_base_container
```
※以降、起動するときは以下のようにします。

```shell
$ docker start dev_base_container
```

### デフォルトで入っているものの確認

```shell
# コンテナの中に入る
$ docker exec -it dev_base_container bash

# CentOS Streamに移行できていることを確認。

$ cat /etc/centos-release
CentOS Stream release 8

# 入っているものを確認
$ dnf list installed
```

削除するときは

```
$ docker stop dev_base_container
$ docker rm dev_base_container
```

Dockerfileを以下のように書き直しました。
具体的には、以下の点を修正しました。

- Rubyのバージョン管理を行うための`rbenv`をインストール。
- Node.jsのバージョン管理を行うための`nodenv`をインストール。
- コンテナがどのバージョンか、内部から確認可能なように、環境変数の追加。

```docker
FROM local/cos-stream
MAINTAINER tora_tz
ENV CONTAINER_VERSION 0.2

# system update&install
RUN dnf -y upgrade && dnf install -y git && dnf clean all

# anyenv install
SHELL ["/bin/bash", "-c"]
RUN git clone https://github.com/anyenv/anyenv ~/.anyenv
ENV PATH ~/.anyenv/bin:$PATH
RUN echo 'eval "$(anyenv init -)"' >> ~/.bash_profile
RUN anyenv install --force-init
RUN anyenv install rbenv
RUN anyenv install nodenv
RUN exec $SHELL -l
```

### rbenv、nodenvのパスが通っているか確認
```shell
# ビルド
$ docker run -dit --name dev_base_container local/dev_base:0.2
# --loginをつけないと、.bash_profileが動かない。
$ docker exec -it dev_base_container /bin/bash --login
$ rbenv install --list
2.5.8
2.6.6
2.7.2
3.0.0
$ nodenv install --list
(中略)
15.2.1
15.3.0
15.4.0
15.5.0
```
Ruby 3.0が入っていることを確認。

[ビルド時の依存ライブラリ](https://github.com/rbenv/ruby-build/wiki#centos)を参考にDockerfileを修正。

[Dockerfile]

```docker

FROM local/cos-stream
MAINTAINER tora_tz

ENV CONTAINER_NAME dev_name
ENV CONTAINER_VERSION 0.3

# change bash
SHELL ["/bin/bash", "-c"]

# system update&install
RUN dnf upgrade -y \ 
  && dnf install -y dnf-plugins-core \
  && dnf config-manager  --enable powertools \
  && dnf install -y sudo which git gcc make bzip2 openssl-devel libyaml-devel libffi-devel readline-devel zlib-devel gdbm-devel ncurses-devel \
  && dnf clean all

ENV DOCKER_USER developer
RUN useradd -mU ${DOCKER_USER}
# sudo設定追加
RUN echo '${DOCKER_USER} ALL=(ALL:ALL) ALL' | EDITOR='tee -a' visudo
# RUN echo '${DOCKER_USER}    ALL=(ALL)      NOPASSWD:ALL' >> /etc/sudoers
# sudo権限を付与
RUN gpasswd -a ${DOCKER_USER} ${DOCKER_USER}
# sudo可能なグループに所属させる。
RUN usermod -aG wheel ${DOCKER_USER}
# パスワードを設定
RUN echo "${DOCKER_USER}:dev_pass" | chpasswd
# 実行ユーザを変更
USER ${DOCKER_USER}
ENV WORKSPACE_PATH /home/${DOCKER_USER}/workspace
RUN mkdir -p ${WORKSPACE_PATH}
WORKDIR ${WORKSPACE_PATH}

# anyenv install
RUN git clone https://github.com/anyenv/anyenv ~/.anyenv
ENV PATH ~/.anyenv/bin:$PATH
RUN echo 'eval "$(anyenv init -)"' >> ~/.bashrc
RUN anyenv install --force-init
RUN anyenv install rbenv
RUN anyenv install nodenv
RUN exec $SHELL -l

# Ruby&Node.js Install
RUN source ~/.bashrc \ 
  && rbenv install 3.0.0 \
  && rbenv global 3.0.0 \
  && nodenv install 15.5.0 \
  && nodenv global 15.5.0
```

### イメージの作成・コンテナの起動

```shell
$ docker build --rm -t local/dev_base:0.3 .
$ docker stop dev_base_container
$ docker rm dev_base_container
$ docker run -dit --name dev_base_container local/dev_base:0.3
$ docker exec -it dev_base_container /bin/bash --login
$ ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]
$ node -v
v15.5.0
$ gem -v
3.2.3
$ bundler -v
Bundler version 2.2.4
```

## Rails開発のベースとなるイメージの作成
### Dockerfileの作成
```shell
$ mkdir ../dev_rails_base
$ cd ../dev_rails_base
$ vi Dockerfile
```

[Dockerfile]

```docker
FROM local/dev_base:0.3
MAINTAINER tora_tz
ENV CONTAINER_NAME dev_rails_base
ENV CONTAINER_VERSION 0.1
USER ${DOCKER_USER}
ENV APP_ROOT ${WORKSPACE_PATH}/rails_test
RUN mkdir -p ${WORKSPACE_PATH}/rails_test
WORKDIR ${APP_ROOT}
USER root
# yarnのリポジトリを追加して、Railsの依存ライブラリをインストールする。
# see https://classic.yarnpkg.com/ja/docs/install/#centos-stable
RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo && dnf install -y yarn sqlite gcc-c++
# MariaDBパッケージリポジトリセットアップスクリプトを使い、リポジトリをセットアップする。
# see https://mariadb.com/kb/en/yum/
RUN curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash
# MariaDBをインストールする
RUN dnf upgrade -y && dnf install -y MariaDB-server galera-4 MariaDB-client MariaDB-shared MariaDB-backup MariaDB-common MariaDB-devel && dnf clean all

USER ${DOCKER_USER}
# Gemfileを追加する。
COPY --chown=${DOCKER_USER}:${DOCKER_USER} Gemfile ${APP_ROOT}

# Gemファイルを生成する。
RUN source ~/.bashrc \
  && bundle config set path 'vendor/bundle' \
  && bundle install \
  && bundle exec rails new  . --force -d mysql --minimal

COPY --chown=${DOCKER_USER}:${DOCKER_USER} database.yml ${APP_ROOT}/config/database.yml

EXPOSE 3000
```

### Gemファイル修正
```shell
$ vi Gemfile
```

[Gemfile]

```ruby
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "rails", '>= 6.1'
```

### DB接続設定ファイルの修正
```shell
$ vi database.yml
```

[database.yml]

```ruby
# MySQL. Versions 5.5.8 and up are supported.
#
# Install the MySQL driver
#   gem install mysql2
#
# Ensure the MySQL gem is defined in your Gemfile
#   gem 'mysql2'
#
# And be sure to use new-style password hashing:
#   https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html
#
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: rails_user
  password: test_password
  host: localhost

development:
  <<: *default
  database: rails_test_development
  port: 3315

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: rails_test_test

# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password or a full connection URL as an environment
# variable when you boot the app. For example:
#
#   DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
#
# If the connection URL is provided in the special DATABASE_URL environment
# variable, Rails will automatically merge its configuration values on top of
# the values provided in this file. Alternatively, you can specify a connection
# URL environment variable explicitly:
#
#   production:
#     url: <%= ENV['MY_APP_DATABASE_URL'] %>
#
# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full overview on how database connection configuration can be specified.
#
production:
  <<: *default
  database: rails_test_production
  username: rails_test
  password: <%= ENV['RAILS_TEST_DATABASE_PASSWORD'] %>
```

### イメージの作成・コンテナの起動

```shell
$ docker build --rm -t local/dev_rails_base:0.1 .
$ docker run -dit --name dev_rails_base_container -p49152:3000 local/dev_rails_base:0.1
$ docker exec -it dev_rails_base_container /bin/bash --login
# MariaDBの起動
$ echo dev_pass | sudo -S nohup mysqld_safe &
```

別タブで以下を実行する。

```shell
# MariaDB初期セットアップ
$ docker exec -it dev_rails_base_container /bin/bash --login
$ sudo mysql_secure_installation
Enter current password for root (enter for none):test_password
Switch to unix_socket authentication [Y/n]:n
Change the root password? [Y/n]:n
Remove anonymous users? [Y/n]:Y
Disallow root login remotely? [Y/n]:Y
Remove test database and access to it? [Y/n]:Y
Reload privilege tables now? [Y/n]:Y
Thanks for using MariaDB!
$ sudo mysql -u root -p
MariaDB [(none)]> use mysql;
MariaDB [mysql]> SELECT user, host, plugin FROM user;
+-------------+-----------+-----------------------+
| User        | Host      | plugin                |
+-------------+-----------+-----------------------+
| mariadb.sys | localhost | mysql_native_password |
| root        | localhost | mysql_native_password |
| mysql       | localhost | mysql_native_password |
+-------------+-----------+-----------------------+

# Railsからアクセスするためのユーザを作成する。
MariaDB [mysql]> CREATE USER 'rails_user'@'%' identified by 'test_password';
MariaDB [mysql]> GRANT ALL PRIVILEGES ON *.* TO 'rails_user'@'%' WITH GRANT OPTION;
MariaDB [mysql]> SELECT user, host, plugin FROM user;
+-------------+-----------+-----------------------+
| User        | Host      | plugin                |
+-------------+-----------+-----------------------+
|(中略)　　　　 |           |                       |
| rails_user  | %         | mysql_native_password |
+-------------+-----------+-----------------------+

MariaDB [mysql]> flush privileges;
MariaDB [mysql]> quit;
$ bin/rails db:create
Created database 'rails_test_development'
Created database 'rails_test_test'
# Railsを起動する。
$ bin/rails s -b 0.0.0.0
```

# コンテナで起動したRails環境へのアクセス
一旦ここまで構築できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/858028/69d8f3ce-def9-b958-cb14-e05191db306c.png)


# 今後の課題
- systemdを使えるようにする。(特権を持たせないとダメなようなので、場合によっては断念)

※一応試しましたが、ビルド時に

```
[OK]Started Update UTMP about System Runlevel Changes.
```

で止まるので、実施する場合はこの辺りの調査が必要。

- Docker Composeに乗り換え、コンテナを分割する。
- Visual Studio Codeから開発できるようにする。
- 手動でセットアップしているところも自動化する。
- MariaDBの自動起動設定を有効にする。(systemctlが使用できないので、mysqld_safeを使って……ですが、起動する時間を考慮する必要があるとのことなので、要対策)
- Sequel AceなどのクライアントからDBにアクセスできるようにする。
(ポート設定を追加して実施してみたができなかったので、要調査)
- GitHubとの連携(アカウントは引数で渡す)
- ベタ書きしているパスワードなどの情報を、外から引数で渡せるようにする。
- ログインシェルを指定しなくて済むようにする。(ENTRYPOINTを使うなど……)

# その他参考にしたもの

- [日本語環境のCentOS(Docker image)をDockerfileで書いてみた](https://qiita.com/OHAS/items/1cc3caa8065787e8f34f)
- [CentOSの公式リポジトリ](https://hub.docker.com/_/centos) 
- [よく使いそうなDockerfileの記法一覧](https://qiita.com/yu-croco/items/9b190f99fae982202c19)
- [Dockerイメージとコンテナの削除方法](https://qiita.com/tifa2chan/items/e9aa408244687a63a0ae)
- [既存イメージの CMD を確認する](https://qiita.com/YumaInaura/items/19d1f5eaefb9fb1ec6de)
- [Dockerfileについて(日本語訳)](http://docs.docker.jp/v17.06/engine/reference/builder.html)
- [DockerfileのRUNやCMDをBashのログインシェルで実行させる](https://qiita.com/clomie/items/235192bd62c60b0d0806)
- [Dockerで開発環境を仮想化する](https://qiita.com/Riliumph/items/3b09e0804d7a04dff85b)