2021年 6月 の投稿一覧

docker-composeからDockerfileへ変数を渡す

docker-compose.ymlで管理するサービスのイメージを自分でビルドするときに、Dockerfileへ変数を渡すのに苦労したのでメモしておきます。

ymlを書き換えるのではなくて、.envを書き換えるだけで済むようにしたかったのです。

コマンド入力するときにオプションで渡すのも面倒だったし…

最終的に、以下のようにしたらできました。

  • .envファイルに変数名=値 の形式で列挙
  • さらに、args に変数を列挙して、Dockerfileに変数を届ける
  • Dockerfile側で、ARGS で変数の利用を宣言する

動作テストのため、以下のdocker-compose.ymlとDockerfikeを作成しました。

まずはdocker-compose.ymlから。

version: "3.9"

services:
  env_test:
    container_name: "env_test"
    build:
      context: "./build"
      dockerfile: "Dockerfile"
      args: # .env で宣言している変数を、イメージビルド時に使えるようにします
       - ENV_FROM_COMPOSE=$ENV_FROM_COMPOSE

つづいて、Dockerfileです。

FROM alpine:latest
ARG ENV_FROM_COMPOSE

RUN echo "env value: ${ENV_FROM_COMPOSE}"

CMD ["/bin/bash"]

.envには変数を設定。

ENV_FROM_COMPOSE=hello_from_docker-image-building!!

ビルドしてみます。

$ docker-compose build
Building env_test
[+] Building 0.5s (6/6) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                           0.0s
 => => transferring dockerfile: 37B                                                                                                                                            0.0s
 => [internal] load .dockerignore                                                                                                                                              0.0s
 => => transferring context: 2B                                                                                                                                                0.0s
 => [internal] load metadata for docker.io/library/alpine:latest                                                                                                               0.0s
 => CACHED [1/2] FROM docker.io/library/alpine:latest                                                                                                                          0.0s
 => [2/2] RUN echo "env value: hello_from_docker-image-building!!"                                                                                                             0.3s
 => exporting to image                                                                                                                                                         0.0s
 => => exporting layers                                                                                                                                                        0.0s
 => => writing image sha256:fc3c6c7aff6c5670a461dd324a2c81b2029389e5e1f0145110e6b50dbbd1b956                                                                                   0.0s
 => => naming to docker.io/library/docker-env-test_env_test

ステージ [2/2]で、データが$ENV_FROM_COMPOSE が展開されて、値が表示されています。

ビルドステージでechoしても、こちらには表示されませんけど…

ビルド時にdocker-composeから値を渡したいときは、この記述方法が使えそうです。

注意点:docker-compose.yml のenv_fileは動作が違う!

2021.08.12 追記です。

docker-compose.ymlには、「env_file」という要素もあります。

これは、docker-compose 経由で立ち上げられたコンテナの中で利用できる環境変数を、外部ファイルに列挙しておくためのものです。

ここでファイルを指定しても、docker-compose.ymlの中で利用することはできません。

たとえば、「.another_env」というファイルを作って環境変数を列挙しておき、docker-compose.ymlにenv_fileで指定しても、.another_envに書かれた変数はdocker-compose.ymlからは利用できませんが、ビルドしたコンテナ内では利用できます。

version: "3.9"

services:
  env_test:
    container_name: "env_test"
    env_file:
      # .another_envに記載された変数は、docker-compose.ymlの中では利用できない!!
      - ./.another_env
    build:
      context: "./build"
      dockerfile: "Dockerfile"

.env 以外のファイルを読み込んでdocker-compose.yml内で利用する方法は、ちょっと調べただけではわかりませんでした。後日、また調査しておきます。。。

以上です。あなたのお役に立てればうれしいです。

別サーバのMariaDBに接続すると、ERROR 2002が発生する

MariaDBを動かしているDockerコンテナに別のコンテナから接続をしたら、以下のエラーが発生しました。

root@a90917286208:/# mysql -h mariadb                        
ERROR 2002 (HY000): Can't connect to MySQL server on 'mariadb' (115)

MariaDBの設定項目にはbind-addressというものがあり、指定した接続元のみアクセスを許可することができるようです。

初期設定では、127.0.0.1 からの接続のみが許可されています。

ウチイダのコンテナでは、/etc/mysql/mariadb.conf.d/50-server.cnf に設定がありました。

#
# These groups are read by MariaDB server.
# Use it for options that only the server (but not clients) should see
#
# See the examples of server my.cnf files in /usr/share/mysql

# this is read by the standalone daemon and embedded servers
[server]

# this is only for the mysqld standalone daemon
[mysqld]

#
# * Basic Settings
#
user                    = mysql
pid-file                = /run/mysqld/mysqld.pid
socket                  = /run/mysqld/mysqld.sock
#port                   = 3306
basedir                 = /usr
datadir                 = /var/lib/mysql
tmpdir                  = /tmp
lc-messages-dir         = /usr/share/mysql
#skip-external-locking

# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#  bind-address            = 127.0.0.1 # <- 初期設定されているので、コメントアウト

~ (以下略) ~

設定を変更して、Mariadb を再起動します。

再度、接続しなおしてみます。

root@a90917286208:/# mysql -h mariadb
ERROR 1698 (28000): Access denied for user 'root'@'app'

エラー内容が ERROR 1698 に変わりました。

MariaDBのcliは、指定しないとrootユーザーで接続を試みます。

初期設定ではrootはlocalhostからのみ接続を許可するので、権限がないためエラーになっています。

MariaDBのuser テーブルに、外部ホストからの接続を許可するユーザーを作成します。

CREATE USER 'app-user'@'app' IDENTIFIED BY 'app-user-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'app-user'@'app' WITH GRANT OPTION;

新たに作成し他ユーザーを指定して接続してみます。

root@a90917286208:/# mysql -h mariadb -u appuser -p 
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 9
Server version: 10.3.29-MariaDB-0+deb10u1 Debian 10

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> 

MariaDBに、外部から接続することができました。

Dockerを利用する場合、MySQL のイメージを使えば最初から良い感じに設定してくれているようなので、通常はあまりつまづくことのない問題かと思いますが…

以上です。あなたのお役に立てればうれしいです。

CentOS 8でXEyesを利用する

Linuxのコマンド操作の練習としてよく利用されているXEyes。

この間もLinuxの研修を行ったときに受講者に試してもらったのですが、CentOS 8では初期状態ではインストールできなくなっていました。

[root@localhost /]# dnf search x11-apps
一致する項目はありませんでした。
[root@localhost /]#

XEyesを含むx11-appsが見つかりません。

調べてみたところ、どうやらpower-tools というリポジトリを有効化しないといけないようです。

[root@localhost /]# dnf config-manager --set-enable powertools
[root@localhost /]# dnf search x11-apps
CentOS Linux 8 - PowerTools                            848 kB/s | 2.3 MB     00:02    
================================= 名前 一致: x11-apps =================================
xorg-x11-apps.x86_64 : X.Org X11 applications
[root@localhost /]# 

x11-appsをインストールすると、xeyes コマンドが利用できるようになりました。

以上です。あなたのお役に立てればうれしいです。

Dockerfileの上位ディレクトリにあるファイルをCOPYする

基本的に、Dockerfileは親ディレクトリにあるファイルを参照することができません。

サンプルとして、以下のようなディレクトリ構造でDockerfileからビルドしてみます。

$ /tmp/copy_test$ tree ./
./
├── dir_in_Dockerfile
│   ├── Dockerfile
│   ├── child_dir
│   │   └── testfile_in_childdir.txt
│   └── testfile.txt
└── testfile_in_parent.txt

2 directories, 4 files

テスト用のDockerfileはこのように作成しました。

FROM alpine:latest

COPY ./testfile.txt /tmp

# 親ディレクトリのファイル
COPY ../testfile_in_parent.txt /tmp

# 子ディレクトリのファイル
COPY ./child_dir/testfile_in_child.txt /tmp 

# 絶対パスでの記述
COPY /tmp/testfile_in_tmp.txt /tmp 

CMD ["/bin/sh"]

dir_in_Dockerfile でビルドをしてみます。

/tmp/copy_test/dir_in_Dockerfile$ docker build .
[+] Building 0.1s (9/9) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 38B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for docker.io/library/alpine:latest
 => CACHED [1/5] FROM docker.io/library/alpine:latest
 => [internal] load build context
 => => transferring context: 112B
 => CACHED [2/5] COPY ./testfile.txt /tmp
 => ERROR [3/5] COPY ../testfile_in_parent.txt /tmp
 => CACHED [4/5] COPY ./child_dir/testfile_in_child.txt /tmp
 => ERROR [5/5] COPY /tmp/testfile_in_tmp.txt /tmp

親ディレクトリのファイルを指定した部分([3/5] COPY ../testfile_in_parent.txt /tmp)と、絶対パスでファイルを指定した部分(ERROR [5/5] COPY /tmp/testfile_in_tmp.txt /tmp)でエラーが起きています。

どうしてもDockerfileの親階層のファイルを参照したいとき

Dockerfileの上位ディレクトリにあるファイルをCOPYしたい場合、ビルドコンテクストを指定する必要があります。

以下のように、Dockerfileを書き換えます。

# docker buildコマンドで、Dockerfileの親ディレクトリをコンテキストに指定してビルド実行する

FROM alpine:latest

# Dockerfileと同階層のファイル
COPY ./dir_in_Dockerfile/testfile.txt /tmp

# Dockerfileからみて親ディレクトリにあるファイルを、カレントディレクトリとして参照
COPY ./testfile_in_parent.txt /tmp

# 子ディレクトリのファイル
COPY ./dir_in_Dockerfile/child_dir/testfile_in_child.txt /tmp 

# 絶対パスでの記述
COPY /tmp/testfile_in_tmp.txt /tmp 

CMD ["/bin/sh"]

そして、 docker build する際に-f オプションでDockerfileの位置と、コンテキスト(基準になるディレクトリ)を指定します。

/tmp/copy_test/dir_in_Dockerfile$ docker build -f ./Dockerfile ../
[+] Building 0.2s (9/9) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 38B
 => [internal] load .dockerignore  
 => => transferring context: 2B    
 => [internal] load metadata for docker.io/library/alpine:latest
 => [internal] load build context                               
 => => transferring context: 242B                               
 => CACHED [1/5] FROM docker.io/library/alpine:latest           
 => CACHED [2/5] COPY ./dir_in_Dockerfile/testfile.txt /tmp     
 => CACHED [3/5] COPY ./testfile_in_parent.txt /tmp             
 => CACHED [4/5] COPY ./dir_in_Dockerfile/child_dir/testfile_in_child.txt /tmp
 => ERROR [5/5] COPY /tmp/testfile_in_tmp.txt /tmp                            
------
 > [5/5] COPY /tmp/testfile_in_tmp.txt /tmp:
------
failed to compute cache key: "/tmp/testfile_in_tmp.txt" not found: not found

今度は、絶対パスで指定した部分(ERROR [5/5] COPY /tmp/testfile_in_tmp.txt /tmp)だけがエラーになりました。

コンテキストはデフォルトではDockerfileのある階層になるため、そのままだと親階層のファイルが使えません。

明示的に親ディレクトリを指定することで、Dockerfileの親階層のファイルも参照できます。

一応この方法で実現できますが、Dockerfikeに記載されたパスと実際に参照されるファイルの所在が変わってしまううえ、Dockerfile上のすべてのパスを書き換える必要があります。

加えて、ビルドの際に適切にオプションと引数を指定しないと意図した内容がビルドされません。

しかも、、、コンテキストに指定されたディレクトリ以下にあるファイルは、すべてDockerデーモンに送信されてしまい、ビルドに時間がかかる原因にもなったりするみたいです。

あくまで「一応できる」というものであり、副作用が多いですね。あまり多用しない方がよさそうです。

以上です。あなたのお役に立てればうれしいです。

プリペアドステートメントでLIKE検索

つい忘れて何度も悩むんですよね。プリペアドステートメントでLIKEするときの注意点。

PHPでプリペアードステートメントを作るとき、検索ワードをそのままクエリに入れるとうまくいきません。

$word = "a"
$stmt->dbh.prepare("SELECT * FROM fruits WHERE name LIKE '%?%'"); /* これはダメ */
$stmt.execute([$word]);

?に展開されるときに、値をシングルクオートで囲むためのようです。

あらかじめワイルドカードとくっつけておいてから、クエリに渡します。

$word = "%a%" /* 検索文字列に、ワイルドカードを含める */
$stmt->dbh.prepare("SELECT * FROM fruits WHERE name LIKE ?");
$stmt.execute([$word]);

以上です。あなたのお役に立てればうれしいです。

docker-compose でホスト側の任意のディレクトリをマウントできない

動作環境

  • Windows 10 Pro
  • WSL2 ubuntu
  • Docker Desktop for Windows

dockerが自動で作ってくれるディレクトリではなく、ホスト側で任意に作成したディレクトリをマウントさせたいという状況がありました。

調べてみたところ、トップレベルでvolumesを定義するとよいみたいです。

しかし、いろんなところに書いてあるやり方でymlを記述してもエラーがでます。。。

version: "3.9"

services:
  mount_test:
    container_name: "mount_test"
      image: alpine
      tty: true
      volumes:
        - volume_test:/tmp/mounted_from_host

volumes:
  volume_test:
   driver_opts:
     type: none
     device: /var/docker_test/volume # このディレクトリはあらかじめ作成されており、いくつかのファイルが入っている
     o: bind

エラーの内容は以下のような感じ。

$ docker-compose up -d
Creating network "docker-compose-test_mount_from_host_default" with the default driver
Creating volume "docker-compose-test_mount_from_host_volume_test" with default driver
Creating mount_test ... error

ERROR: for mount_test  Cannot start service mount_test: error while mounting volume '/var/lib/docker/volumes/docker-compose-test_mount_from_host_volume_test/_data': failed to mount local volume: mount /var/docker_test/volume:/var/lib/docker/volumes/docker-compose-test_mount_from_host_volume_test/_data, flags: 0x1000: no such file or directory

ERROR: for mount_test  Cannot start service mount_test: error while mounting volume '/var/lib/docker/volumes/docker-compose-test_mount_from_host_volume_test/_data': failed to mount local volume: mount /var/docker_test/volume:/var/lib/docker/volumes/docker-compose-test_mount_from_host_volume_test/_data, flags: 0x1000: no such file or directory
ERROR: Encountered errors while bringing up the project.

なぜかdockerが自動生成したディレクトリにマウントしようとしています。

修正:ボリュームの定義にdriverを追加する

「driver: local」と追加しました。この項目が必要なようです。いつからか仕様が変わったんですかね…

version: "3.9"

services:
  mount_test:
    container_name: "mount_test"
      image: alpine
      tty: true
      volumes:
        - volume_test:/tmp/mounted_from_host

volumes:
  volume_test:
   driver: local # ここを追加
   driver_opts:
     type: none
     device: /var/docker_test/volume
     o: bind

修正したymlで再度試したら、ちゃんとできました。

実行前に、前回のエラーで作成されたネットワークとボリュームを片付けておきます。

$ docker-compose down && docker volume rm docker-compose-test_mount_from_host_volume_test
Removing mount_test ... done
Removing network docker-compose-test_mount_from_host_default
docker-compose-test_mount_from_host_volume_test
$
$ docker-compose up -d
Creating network "docker-compose-test_mount_from_host_default" with the default driver
Creating volume "docker-compose-test_mount_from_host_volume_test" with local driver
Creating mount_test ... done
$
$ docker-compose exec mount_test cat /tmp/mounted_from_host/helloworld.txt
hello world from mounted from host!!

以上、あなたのお役に立てれば幸いです。