y-uchiida

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!!

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

LinuxでSIGBUSを発生させる

C言語を扱う中で、バスエラー(BUSE. SIGBUS)について調べたのでメモを残しておきます。

本記事のコードは、WSL2のUbuntuで動作させた結果を掲載しています。

一応、以下に検証した動作環境を。

環境・バージョンなど

  • Windows 10 Pro 20H2 (OSビルド: 19042.782)
  • Windows Subsystem for Linux Update 5.4.72
  • Ubuntu 20.04.2 LTS

バスエラーは物理メモリやハードウェアバスに関するエラー、、、らしい

バスエラー(BUSE)は、CPUが物理メモリにアクセスしたときに発生するエラーだとのこと。存在しないデバイスや、制限されているデバイスへのアクセスで発生する、との記載も見かけました。

むむ、難しい。確保してないメモリに触ったときはSEGVが発生するけど、そうじゃないメモリのエラーで発生するということですかね。

メモリアライメント違反とかでもおこるようです。

発生させてみる

とりあえず、実際に発生させてみましょう。思いついたものから試してみます。

スタック領域の文字列の範囲外を書き換え

ヒープ領域ではなくスタック領域のメモリの範囲外へのアクセスだとどうなるでしょうか。スタック領域が物理メモリなのかどうかわからないですが。。。

int	main(void)
{
	char str[3] = "abc";

	str[3] = 'd';
	return (0);
}
$ gcc test_buse.c -o a.out
$ ./a.out
*** stack smashing detected ***: terminated
Aborted

思ってたのと違う実行結果でした。これも物理メモリではなかったということですかね。

ていうか、スタック領域のメモリの外側に触れると、ちゃんとその旨表示するんですね。知らなかった。

アライメント不整合のメモリアクセス

次は、調べてみたときに見かけた「メモリアライメント違反」の場合を試してみます。

#include <stdio.h>
#include <stdlib.h>

int	main(void)
{
	long *l;
	char *c;

	l = (long *)malloc(sizeof(long) * 2);

	/* ポインタ変更前 */
	printf("l(%p): %ld\n", l, *l);

	/* longのポインタをcharのポインタに代入して、1バイトだけ動かしてから戻す */
	c = (char *)l;
	c++;
	l = (long *)c;

	/* 変更前から1バイトずれて、アライメント違反になっているはず */
	printf("l(%p): %ld\n", l, *l);

	return (0);
}
$ gcc test_buse2.c -o a.out
$ ./a.out
l(0x562c8fdef2a0): 0
l(0x562c8fdef2a1): 0

あれっ、エラーにならず正常終了してしまいました…明らかに不整合なのに…

もうちょっと調べてみたところ、アライメント違反のチェックフラグがデフォルトでオフになっているようです。

EFLAGSのACをオンにすると検知してくれます。

#include <stdio.h>
#include <stdlib.h>

int	main(void)
{
	/* asmでEFLAGSを変更 */
	asm( "pushf\n\torl $0x40000,(%rsp)\n\tpopf");

	long *l;
	char *c;

	l = (long *)malloc(sizeof(long) * 2);

	/* ポインタ変更前 */
	printf("l(%p): %ld\n", l, *l);

	/* longのポインタをcharのポインタに代入して、1バイトだけ動かしてから戻す */
	c = (char *)l;
	c++;
	l = (long *)c;

	/* 変更前から1バイトずれて、アライメント違反になっているはず */
	printf("l(%p): %ld\n", l, *l);

	return (0);
}
$ gcc test_buse3.c -o a.out
$ ./a.out
Bus error

めでたくバスエラーが発生しました。

アセンブリわかんないな~…近いうち勉強しないと…

バスエラー、めったに見る機会がなさそう

昔は、アライメント違反で普通に発生してたんですかね…

いまはデフォルトでチェック機能がオフになっているので、意図的に検知させないとめったに遭遇しないんじゃないでしょうか。

けっきょく、物理メモリとかハードウェアバスのところまでは理解が及びませんでしたが…

どうやら前提知識が不足しているっぽいので、いまは深追いせずにペンディングにしときます。

説明できるようになったら、また記事追加しようと思います。

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

char *const str と const char *str の違い

execve のmanを読んでいるとき、 プロトタイプ宣言の中で「 char *const 」 と 「 const char * 」が混在して使われているのを見かけました。

int
execve(const char *path, char *const argv[], char *const envp[]);

pathは「 const char * 」で、argv と envp は「 char *const 」になっています。

調べてみたところ、const を置く位置によって、挙動が変わるとのこと。

「 const char * 」の場合は定数データ として扱われ、ポインタの参照先の内容を変更することができません。

一方、「 char *const 」は、定数ポインタ となり、別のポインタのアドレスを別の場所に変えることができなくなりますが、参照先の内容を変更することは可能です。

int main()
{
    /* const char * は「定数データ」 */
    const char *fixed_data = "this data can't change.";

    /* char *const は「定数ポインタ」 */
    char *const fixed_ptr = "this data can change.";

    char *another_str = "this is normal string.";

	fixed_data[0] = "T"; /* 定数データなので、内容を変更できない(コンパイルエラー) */
	fixed_data = another_str; /* ポインタは定数化されていないため、置き換えられる */

	fixed_ptr[0] = 'T'; /* 定数ポインタであり参照先は定数ではないので、内容の変更はできる */
	fixed_ptr = another_str; /* ポインタを変更することはできない(コンパイルエラー) */

  return (0);
}

上記のコードをコンパイルすると、こんな感じにエラーが出ます。

$ gcc test_const_variation.c
test_const_variation.c: In function ‘main’:
test_const_variation.c:11:16: error: assignment of read-only location ‘*fixed_data’
   11 |  fixed_data[0] = "T"; /* 定数データなので、内容を変更できない(コンパイルエラー) */
      |                ^
test_const_variation.c:15:12: error: assignment of read-only variable ‘fixed_ptr’
   15 |  fixed_ptr = another_str; /* ポインタを変更することはできない(コンパイルエラー) */
      |            ^

実にややこしいですね…良い覚え方はないものでしょうか。

とりあえずは、「*const」は定数ポインタ、と覚えておくことにしようと思います。

ウチイダは間接参照演算子(*)を変数名側に寄せる、いわゆる「ポインタ変数記法」を使うことが多いので、constに「*」がくっついたら、定数ポインタを定義しているように読むのが自然な気がしています。

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

Windows Update で WSLが起動しなくなった – レジストリをいじってイベントID 10016を解消

先日、WSLが突如起動しなくなるトラブルに見舞われました。

Twitterのほうで、解消までの顛末をつぶやいていたので、こちらでは解消のために実施すべき手順だけ簡単に記しておきます。

発生した事象

  • Windows Terminalを起動すると、「ユーザー設定の読込中にエラーが発生しました」と表示され、WSLのシェルが表示されなくなる(代わりに、設定ファイルの下位に記述されているPowerShellが起動する)
  • WSL(Ubuntu)を直接実行しても、プロンプトが表示されず反応なし
  • Ubuntu以外のディストリビューションをインストールしてみても、同様の状況
  • 「コンピューターの管理」でシステムイベントログをみると、ID10016が記録されていた

詳しい事象については、Twitterのほうに書いてますので、そちらも併せてご覧ください。

環境・バージョンなど

  • Windows 10 Pro 20H2 (OSビルド: 19042.782)
  • Windows Subsystem for Linux Update 5.4.72
  • Windows Terminal 1.2.2381.0

対処方法

  1. レジストリエディタから、「PerAppRuntimeBroker」の所有者をAdministoratorsに変更
  2. コンポーネントサービスから、「PerAppRuntimeBroker」の「起動とアクティブ化のアクセス許可」にSYSTEMを追加
  3. レジストリエディタから、「PerAppRuntimeBroker」の所有者をTrustedInstallerに戻す

以上です。

今回、事象を解決できたのは、WSL自体で問題が起きているらしいと推測できたことと、そこからWindowsのシステムログを確認しようと思い至ったことがきっかけでした。

原因の切り分けと、ログの確認を地道に行うという、トラブルシューティングの鉄則が効果てきめんだったという事例です。

操作方法については、とても詳しく説明しているWebサイトが多数ありますので、そちらを参照されるとよいかと思います。

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

Makefileでのコマンド実行は $(shell command)

Makefileでdockerのイメージやコンテナをあれこれしたいという事情があって(これです)、その際に躓いたのでメモしておきます。

指定の名称のdocker コンテナがあるかどうかで処理を分ける、ということをしたかったので、以下のような感じでMakefileにターゲットを作成しました

test_container_exist:
	if [ $(docker ps -qa -f name=${NAME}) ]; then \
		echo container $(NAME) is exist. ; \
	else \
		echo contaier $(NAME) is not exist. ; \
	fi

実際に動かしてみると、条件として設定してあるdockerコマンドの実行結果が空っぽになってしまい( if [ ]; then になっている )、コンテナの有無にかかわらず常にelseのほうが実行されてしまうのでした。

コマンドラインから上記のdocker コマンドを実行すると、ちゃんと指定の名称のコンテナIDが返ってくるのですが…

いろいろ調べていたところ、どうやらMakefileの中でコマンドを実行するには $(shell command)とする必要があるとわかりました。

test_container_exist:
	# docker コマンドの前に、"shell" と付記する
	if [ $(shell docker ps -qa -f name=${NAME}) ]; then \
		echo container $(NAME) is exist. ; \
	else \
		echo contaier $(NAME) is not exist. ; \
	fi

できましたできました。こんなことに小一時間もかけてしまった…?

RTFMな事案でした。ちゃんとドキュメントに目を通すようにしたいです。

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