CentOS7/8+PostgreSQL12でSQL練習用Dockerコンテナ・イメージを作成してみた。


CentOS7/8上にPostgreSQL12をインストールしたDockerイメージを作成したときの手順の記録です。(Dockerイメージを作ってみようとしたら、想定より苦戦して試行錯誤することが多かったので備忘録として投稿しました。)

#経緯
半年ぶりにバックエンド開発業務関連に携わることになり、色々と忘れていたことが多く(汗)...復習するためにSQLやデータベース構築・設定の実験・操作ができる環境を作ろうとしたことがきっかけです。SQLの操作やPostgreSQLの構築・設定を理解するだけならば、素晴らしい環境がWeb上に多々整っていますが、今回は以下の理由からあえて、Dockerコンテナ上で動作するPostgreSQLのコンテナおよびイメージを作ることにしました。

  - コンテナ(Docker)操作の復習がしたい。
  - 後日、コンテナ間でのデータベースのマルチクラスタ環境構築を試してみたいと考えていて、いつでも簡単に構築できるようにイメージ化しておきたい。
  - ホストOS環境を汚したくない。

#今回のゴール
今回、こんな感じの環境を構築する。
<img width="571" alt="スクリーンショット 2020-01-30 午前6.12.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194963/590b9515-b3c9-5a4d-4ad5-5a6880b72239.png">

PostgreSQLサーバがインストールされているCentOSのコンテナイメージから、コンテナを生成し、PostgreSQLサーバを起動。ホストOS側からクライアントソフトを使ってSQLを入力し、コンテナのPostgreSQLサーバ内のデータベースを更新できるようにする。

今回の構築には以下を考慮しない。

  - コンテナ間の接続はしない。
    - 今後マルチクラスタ環境の構築を実現するために、コンテナ間のネットワークも考慮する必要はある。しかし、今回は設定せず、ホストOSのみからネットワーク経由でコンテナOSにSQLを要求するようにする。
  - セキュリティやアクセス制御
    - 同一PCとはいえ、セキュリティやアクセス制御は設定することが望ましい。しかし、今回はSQL、データベースサーバ内部の学び用環境なので考慮しない。
  - データの永続化
    - DBデータ永続のためにホストOS上のディレクトリを共有化(マウント化)することも可能だが、今回は考慮しない。
 
#Dockerイメージ作成環境
今回の環境は以下の通りです。
ホストOSをMacOS、コンテナOSをCentOSとしました。

  - ホストOS: Mac v10.15.2
  - Docker Engine: v19.03.5
  - コンテナOS:(CentOSはVer7とVer8の両方を試してみました。)
    - CentOS-7  v7.7.1908 (ベースイメージ元は"centos:7")
    - CentOS-8  v8.1.1911 (ベースイメージ元は"centos:8")
  - PostgreSQL: v12.1 (構築時のタイミングでは、CentOS標準リポジトリにver12がないようだったので、PostgreSQL標準リポジトリから取得することにしました。)

#Dockerイメージ化の手段
イメージ化手段は以下の２通りの方法が考えられます。

- 方法1 Commitによるイメージ生成
  - ベースとなるイメージからコンテナを生成起動し、コンテナ上で手動によりインストール・設定を行った状態のものをイメージ化する。
- 方法2 DOCKERFILEによるイメージ生成
  - ベースとなるイメージを基にして、インストール・設定手順をDOCKERFILEにより記述し、イメージ化する。

コンテナからターミナルで手動でインストールすることは推奨されておらず、
Code as Infrastructureに従い、構成・設定の明確化および再利用性を考慮し、
(個人的に)方法2を習慣つけたいため、方法2によるイメージ化をゴールとしました。

進め方ですが、以下の理由から、まずは事前準備として方法１を行い、方法2を実施することにしました。

- コンテナ上で正常に動作するのか、Dockerfileにどのような順番で書けば実現できるかわからず、確認したかったこと
- 方法２のために既存イメージから設定ファイルの取得が必要。

##方法1.Commitによるイメージ生成
以下の手順により、イメージ化していきます。

1-1.公式Cent OSのイメージのダウンロード、コンテナの生成
1-2.PostgreSQL標準リポジトリパッケージのダウンロード・インストール
1-3.PostgreSQLサーバ(および周辺)のインストール
1-4.データベースの初期化
1-5.データベースサーバの起動
1-6.PostgreSQLの設定変更
1-7.動作確認
1-8.ホストOSからの接続確認
1-9.コンテナのイメージ化
1-10.作成したイメージの動作確認

CentOSにPostgreSQLをインストールする手順は以下の記事を参考にさせていただきました。
>(参考)[PostgreSQL を CentOS にインストールするには](https://qiita.com/tom-sato/items/e1903cb974fb6c6d5664)


###1-1.公式Cent OSのイメージのダウンロード、コンテナの生成
ターミナルを立ち上げ、"docker container run"コマンドを使って、ベースイメージのダウンロード、コンテナを生成します。

     CentOS-8のイメージを使う場合  
         $ docker container run -d --privileged -v (ホストOS内のディレクトリ):(コンテナOS内のディレクトリ) -p 10864:5432 --name='method1_cp12' centos:8 /sbin/init

     CentOS-7のイメージを使う場合  
         $ docker container run -d --privileged -v (ホストOS内のディレクトリ):(コンテナOS内のディレクトリ) -p 10864:5432 --name='method1_cp12' centos:7 /sbin/init

動作確認用のSQLファイルや方法2で必要となる、コンテナ側で作成・変更する設定ファイルについて、ホスト側とコンテナ側で共有ができるように"-v"で設定しました。今回は試しに以下の場所をしました。

  - ホストOS側ディレクトリ：/Users/(ユーザ名)/sampleDocker/work/shared_directory
  - コンテナOS側ディレクトリ：/var/shared_directory

また、CentOSのコンテナ上ではsystemctlコマンドが利用できません。コンテナ上で利用するためには"docker container run"のオプションに"--privileged"を追加すると利用できるようです。今回はPostgreSQLの自動起動やサービスの動作確認に使用したり、"1-4.データベースサーバの初期化"に実行する初期化シェルスクリプト内でsystemctlが実行されていたりするので、Systemctlコマンドをコンテナ上で実行できるようにします。

> (参考) [CentOS7のDockerコンテナでsystemctlを使えるようにする](https://qiita.com/mikene_koko/items/4c71c969f55e3fe24190)

次にコンテナ内で作業をするため、以下のコマンドを実行し、コンテナ内にアクセスします。

         $ docker container exec -it method1_cp12 /bin/bash

###1-2.PostgreSQL標準リポジトリパッケージのダウンロード・インストール
  せっかくなので...以下、CentOS-8ではdnfコマンドを使って、インストールすることにしました。

     CentOS-8の場合  
         # dnf -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
         # dnf -qy module disable postgresql

     CentOS-7の場合
         # yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

###1-3.PostgreSQLサーバ(および周辺)のインストール
PostgreSQLのインストールをします。以降の作業にて、ユーザのパスワードを作成する作業がありますが、"passwd"がCentOSに入っていなかったため、インストールします。

     CentOS-8の場合  
         # dnf -y install postgresql12-server postgresql12 postgresql12-contrib
         # dnf -y install passwd

     CentOS-7の場合
         # yum -y install postgresql12-server postgresql12 postgresql12-contrib
         # yum -y install passwd

###1-4.データベースの初期化
データベースの初期化をします。

         # PGSETUP_INITDB_OPTIONS="-E UTF8 --no-locale" /usr/pgsql-12/bin/postgresql-12-setup initdb

###1-5.データベースサーバの起動
PostgreSQLサービスを起動します。    

        # systemctl start postgresql-12.service

サービスが動作しているか確認します。

        # systemctl status postgresql-12.service

実行後、"postgresql-12.service”のActive項目が"active(running)”と表示されていればOKです。

###1-6.PostgreSQLの設定変更
ホストOSからSQLクライアントソフトでアクセスできるように設定を変更します。
また、方法2でイメージを作るために設定ファイル・スクリプトをホストOS側にコピーをします。(ここで説明する設定はあくまで実験用のため、セキュリティの観点を考慮しておりません。利用目的・構築環境によって、各自調整してください。)

/var/lib/pgsql/12/data/postgresql.confをテキストエディタで開きます。
(この例ではテキストエディタとしてviを使っています)

        # vi /var/lib/pgsql/12/data/postgresql.conf

「listen_addresses = 」行を見つけ出し、以下の変更を行います。

- #listen_address = 'localhost'のコメント部分(#)を消す。
- #port = 5432のコメント部分(#)を消す。
- listen_address = 'localhost'の'localhost'を'*'に変更。

        /var/lib/pgsql/12/data/postgresql.conf
        [変更前]
              # - Connection Settings -
              #listen_addresses = 'localhost'         # what IP address(es) to listen on;
                                                      # comma-separated list of addresses;
                                                      # defaults to 'localhost'; use '*' for all
                                                      # (change requires restart)
             #port = 5432                            # (change requires restart)
             max_connections = 100                   # (change requires restart)

        [変更後]
              # - Connection Settings -
              listen_addresses = ‘*'         # what IP address(es) to listen on;
                                                      # comma-separated list of addresses;
                                                      # defaults to 'localhost'; use '*' for all
                                                      # (change requires restart)
              port = 5432                            # (change requires restart)
              max_connections = 100                   # (change requires restart)


postgresql.confを保存し、テキストエディタを終了します。
(上記の設定でlisten_addresses = '*'とすることによって、どんな接続も受け取ってしまいますが、今回は実験用のため、この設定にします。)

/var/lib/pgsql/12/data/pg_hba.confをテキストエディタで開きます。

        # vi /var/lib/pgsql/12/data/pg_hba.conf

以下の１行を追加します。
”host    all             all             0.0.0.0/0               md5"

        </var/lib/pgsql/12/data/pg_hba.conf>

        [変更前]
              # "local" is for Unix domain socket connections only
              local   all             all                                     peer
              # IPv4 local connections:
              host    all             all             127.0.0.1/32            ident
              # IPv6 local connections: 
              host    all             all             ::1/128                 ident

        [変更後]
              # "local" is for Unix domain socket connections only
              local   all             all                                     peer
              # IPv4 local connections:
              host    all             all             127.0.0.1/32            ident
              host    all             all             0.0.0.0/0               md5

              # IPv6 local connections: 
              host    all             all             ::1/128                 ident

pg_hba.confを保存し、テキストエディタを終了します。

postgresユーザのパスワード設定をします。
postgresユーザはOSユーザとしてもPostgreSQLユーザとしても登録されており、
外部からアクセスできるように両方のパスワード登録を行います。
(ここの例ではデフォルトユーザの'postgres'のパスワードを'postgres'として登録します。)

方法2の作業ではイメージ生成時に同じコマンド実行をしなくてはならないので、スクリプト化し、PostgreSQLユーザの設定を変更します。
２つのシェルスクリプトを用意します。ここでは以下のファイル名にします。
 - setting_postgresql.sh
 - setting_postgresqluser.sh

"setting_postgresql.sh"を新規作成します。

        # vi /var/lib/pgsql/setting_postgresql.sh

"setting_postgresql.sh"ファイルの中を以下のように記述して保存し、テキストエディタを閉じます。

        #!/bin/bash

        echo 'postgres' | passwd --stdin postgres
        su -l postgres -c "./setting_postgresqluser.sh"

"setting_postgresqluser.sh"を新規作成します。

        # vi /var/lib/pgsql/setting_postgresqluser.sh

"setting_postgresqluser.sh"ファイルの中を以下のように記述して保存し、テキストエディタを閉じます。

        #!/bin/bash

        psql -c "ALTER ROLE postgres WITH password 'postgres'"

２つのシェルスクリプトの実行権限を設定します。

       # chmod +x /var/lib/pgsql/setting_postgresql.sh
       # chmod +x /var/lib/pgsql/setting_postgresqluser.sh

以下を実行します。これによりユーザ"postgres"にパスワードが登録されます。

       # /var/lib/pgsql/setting_postgresql.sh

今回、変更した設定ファイルや作成したシェルスクリプトは、この後に作業する方法２で必要となるため、1-1で作成した共有ディレクトリ(/var/shared_directory)に保存します。

       # cp /var/lib/pgsql/12/data/postgresql.conf /var/shared_directory/
       # cp /var/lib/pgsql/12/data/pg_hba.conf /var/shared_directory/
       # cp /var/lib/pgsql/setting_postgresql.sh /var/shared_directory/
       # cp /var/lib/pgsql/setting_postgresqluser.sh /var/shared_directory/

また、方法２を実施する上で事前に以下のファイルを修正する作業が必要になるため、コピーします。

       # cp /usr/pgsql-12/bin/postgresql-12-setup /var/shared_directory/

コンテナ起動時にPostgreSQLサービスが自動起動(常時起動)できるように登録設定します。

       # systemctl enable postgresql-12.service

これまでの設定を反映させるため、再起動します。

       # systemctl restart postgresql-12.service
     
###1-7.動作確認
動作確認を行います。
データベースサーバの動作確認のため、試しにテーブル生成とデータ挿入済みの状態にするSQLファイルを作成してみました。

       # vi /var/shared_directory/sampleSQL.sql

SQLファイルの中に以下を記述し、保存。テキストエディタを閉じます。

      sql:/var/shared_directory/sampleSQL.sql

       CREATE TABLE 果物 (
           ID INTEGER PRIMARY KEY,
           名前 VARCHAR(40) UNIQUE
       );
       CREATE TABLE 果物売買取引 (
           取引ID SERIAL PRIMARY KEY,
           取引日付 DATE,
           果物ID INTEGER REFERENCES 果物(ID),
           メモ VARCHAR(100) DEFAULT '不明',
           買取額 INTEGER DEFAULT 0 CHECK(買取額>= 0),
           売却額 INTEGER DEFAULT 0 CHECK(売却額>= 0),
           個数 INTEGER DEFAULT 0 CHECK(個数>= 0)
       );
       INSERT INTO 果物(ID, 名前)
          VALUES (0, 'オレンジ');
          INSERT INTO 果物(ID, 名前)
          VALUES (1, 'アップル');
       INSERT INTO 果物(ID, 名前)
          VALUES (2, 'バナナ');
       INSERT INTO 果物売買取引(取引日付, 果物ID, 買取額, 個数)
          VALUES ('2018-04-02', 1, 1200, 10);
       INSERT INTO 果物売買取引(取引日付, 果物ID, 買取額, 個数)
          VALUES ('2018-04-05', 2, 1500, 6);
       INSERT INTO 果物売買取引(取引日付, 果物ID, 買取額, 個数)
          VALUES ('2018-04-06', 0, 2000, 8);


SQLファイルを実行します。

       # su - postgres
       $ cd /var/shared_directory/
       $ psql -f ./sampleSQL.sql

テーブルと挿入されたデータを確認します。

       $ psql -d postgres -c "select * from 果物売買取引"

データが確認できたら、"postgres"ユーザをログアウトします。

       $ logout

コンテナ内にログインしている状態なので、一旦抜けます。

        # exit

###1-8.ホストOSからの接続確認

ホスト側から接続確認します。ホスト側にpsqlをインストールしてもよいのですが、今回、クライアントソフトとして、[TablePlus](https://tableplus.com)を使用して確認しました。

TablePlus上の設定はこんな感じです。

<img width="498" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194963/fd2e7fe9-c67d-bf68-a71d-3c429985fb99.png">

接続して、先ほど作成したテーブルの確認・SQL操作が確認できたら完了です。

###1-9.コンテナのイメージ化

"docker container commit"コマンドを使って、動作しているコンテナのDockerイメージを作成します。オプションの-aは作成者、-cはコンテナ内部ポート番号(docker container runコマンド実行時にポート指定がなかった場合に適用)を示します。最後にイメージ名を入力します。"gomadofu/method1_cp12:1"だと"gomadofu/method1_cp12"がイメージ名でタグが"1"がタグになります。

         $ docker container commit -a "GOMA DOFU" -c "EXPOSE 5432" -p method1_cp12 gomadofu/method1_cp12:1

###1-10.作成したイメージの動作確認

これまで動作していたコンテナを停止します。

         $ docker container stop method1_cp12

作成した"gomadofu/method1_cp12:1"イメージを使って、コンテナ生成・起動します。

         $ docker container run -d --privileged -v (ホストOS内のディレクトリ):(コンテナOS内のディレクトリ) -p 10864:5432 --name='method1_cp12_2' gomadofu/method1_cp12:1 /sbin/init

"1-8.ホストOSからの接続確認"を再実施します。接続確認できたら、完了です。



一応、ここまでの作業でCentOSとPostgreSQLのコンテナ化ができること・簡単な動作が確認できました。また、この後に実施する方法２に必要な設定ファイル・スクリプトも用意できました。

##方法2.DOCKERFILEによるイメージ生成

以下の手順でイメージを作成します。

2-1.イメージ構成用ディレクトリの準備
2-2.DOCKERFILEの作成
2-3.initdbスクリプトの修正
2-4.ビルド実行
2-5.コンテナ起動(動作確認)

今回作成してみて厄介だったのが、"systemctl"コマンドの存在です。
方法１ではコンテナ起動時に"--privileged"を実行することでコンテナ内で利用することができました。しかし、"systemctl"コマンドをDockerfile中に記述してもビルド時にエラーが発生します。

それを回避する手段として、２つのイメージを作成することでdockerfile内でもsystemctl（正確には"systemctl　enable"）が使えるようです。

>(参考) https://hub.docker.com/_/centos?tab=description

"systemctl"を使わなくても起動・終了・再起動することに支障は無いのですが、コンテナ起動時に自動でPostgreSQLサービスが立ち上がるようにするため、今回"systemctl enable"が使えるようにイメージを２つ作成しようと思います。

また、方法1の"1-4.データベースの初期化"で実行した、"postgresql-12-setup"ですが、このスクリプトの中でsystemctl showが実行されており、ビルド時にエラーが発生します。そのため、"2-3.initdbスクリプトの修正"でスクリプト内容を修正します。

###2-1.イメージ構成用ディレクトリの準備
以下はイメージを作成するためのディレクトリ・ファイル構成です。
<img width="546" alt="スクリーンショット 2020-01-30 午前11.50.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194963/72ef94ad-4eba-9086-ac2e-e0c67a53b7f7.png">

イメージ毎にディレクトリを用意する必要があるので、今回は２つのディレクトリを用意します。

- CentOS_Systemd ディレクトリ(イメージ名:"gomadofu/method2_csystemd")
  - "systemctl"コマンドが使えるCentOSイメージを構成するDockerfileを格納するディレクトリです。
- CentOS_Postgresql12 ディレクトリ(イメージ名:"gomadofu/method2_cp12")
  - PostgreSQLインストール・設定済みのCentOSイメージを構成するDockerfileを格納するディレクトリです。
  - このディレクトリには方法１の過程で得られた、設定ファイル、スクリプトを格納します。
      - postgresql.conf
      - pg_hba.conf
      - setting_postgresql.sh
      - setting_postgresqluser.sh
      - Postgresql-12-setup

###2-2.DOCKERFILEの作成
DOCKERFILEを作成します。

####systemctlコマンド対応イメージのDOCKERFILE

CentOS_Systemdディレクトリ内にDockerfileを作成します。

         $ cd ./CentOS_Systemd
         $ vi ./Dockerfile 

Dockerfileの中に以下の内容を記述し、保存します。

         FROM centos:8
         #centos:7を使う場合は "FROM centos:7”にする。

         ENV container docker

         RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
         rm -f /lib/systemd/system/multi-user.target.wants/*;\
         rm -f /etc/systemd/system/*.wants/*;\
         rm -f /lib/systemd/system/local-fs.target.wants/*; \
         rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
         rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
         rm -f /lib/systemd/system/basic.target.wants/*;\
         rm -f /lib/systemd/system/anaconda.target.wants/*;

         VOLUME [ "/sys/fs/cgroup" ]

         CMD ["/usr/sbin/init"]

####PostgreSQLイメージのDOCKERFILE

CentOS_Postgresql12 ディレクトリ内にDockerfileを作成します。

         $ cd ../CentOS_Postgresql12
         $ vi ./Dockerfile 

Dockerfileの中に以下の内容を記述し、保存します。

        FROM gomadofu/method2_csystemd:1

        #CentOS-8の場合は以下を使う。
        RUN dnf -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm; \
        dnf -qy module disable postgresql; \
        dnf -y install postgresql12-server postgresql12 postgresql12-contrib; \
        dnf -y install passwd;

        #CentOS-7の場合は以下を使う。
        # RUN yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm; \
        # yum -y install postgresql12-server postgresql12 postgresql12-contrib; \
        # yum -y install passwd;
        
        COPY ./postgresql-12-setup   /usr/pgsql-12/bin/postgresql-12-setup
        RUN chmod +x /usr/pgsql-12/bin/postgresql-12-setup

        RUN  PGSETUP_INITDB_OPTIONS="-E UTF8 --no-locale" /usr/pgsql-12/bin/postgresql-12-setup initdb
 
        COPY ./*.conf /var/lib/pgsql/12/data/
        COPY ./setting_*.sh /var/lib/pgsql/

        RUN chmod +x /var/lib/pgsql/setting_postgres*.sh; \
        chown -v postgres.postgres /var/lib/pgsql/12/data/postgresql.conf; \
        chown -v postgres.postgres /var/lib/pgsql/12/data/pg_hba.conf; \
        /var/lib/pgsql/setting_postgresql.sh;

        EXPOSE 5432

        RUN su -l postgres -c "/usr/pgsql-12/bin/pg_ctl start"; \
        su -l postgres -c "./setting_postgresqluser.sh"; \
        su -l postgres -c "/usr/pgsql-12/bin/pg_ctl stop"; \
        systemctl enable postgresql-12.service;

        CMD [“/usr/sbin/init"]


###2-3.initdbスクリプトの修正
./CentOS_Postgresql12/Postgresql-12-setupを修正します。

       $ vi ./Postgresql-12-setup

以下の箇所を修正し、保存します。

       <Postgresql-12-setup>

       [変更前]
       PGDATA=`systemctl show -p Environment "${SERVICE_NAME}.service" |
                   sed 's/^Environment=//' | tr ' ' '\n' |
                   sed -n 's/^PGDATA=//p' | tail -n 1`
       if [ x"$PGDATA" = x ]; then
          echo "failed to find PGDATA setting in ${SERVICE_NAME}.service"
          exit 1
       fi

       [変更後]
       PGDATA="/var/lib/pgsql/12/data/"
       #PGDATA=`systemctl show -p Environment "${SERVICE_NAME}.service" |
       #                sed 's/^Environment=//' | tr ' ' '\n' |
       #                sed -n 's/^PGDATA=//p' | tail -n 1`
       #if [ x"$PGDATA" = x ]; then
       #    echo "failed to find PGDATA setting in ${SERVICE_NAME}.service"
       #    exit 1
       #fi

###2-4.ビルド実行

systemctlコマンド対応イメージを作成します。

       $ cd ./CentOS_Systems
       $ docker build --rm -t gomadofu/method2_csystemd:1 .

systemctlコマンド対応イメージを作成します。

       $ cd ../CentOS8_Postgresql12
       $ docker build --rm -t gomadofu/method2_cp12:1 .

###2-5.コンテナ起動(動作確認)

作成したイメージからコンテナを起動します。

       $ docker container run -d --privileged -v (ホストOS内のディレクトリ):(コンテナOS内のディレクトリ) -p 10864:5432 --name='method2_cp12' gomadofu/method2_cp12:1 /sbin/init

"1-7.動作確認","1-8.ホストOSからの接続確認"により動作・接続確認を実施し、接続できていれば完了。