CLOVER🍀

That was when it all began.

LocalStackとMinIOで、バケットにアップロードしたファイルをHTTPで参照する

これは、なにをしたくて書いたもの?

LocalStackやMinIOをAmazon S3互換のオブジェクトストレージとしてAWS SDKAWS CLIからアクセスすることはよくあるのですが、
バケットにアップロードしたファイルをHTTPで参照したことってないな、とふと思いまして。

こういう感じで、Webサーバー的に使いたいという話ですね。

Amazon S3 を使用して静的ウェブサイトをホスティングする - Amazon Simple Storage Service

調べたらできそうだったので、ちょっと試してみました。

環境

今回の環境は、こちら。

LocalStack。

$ python3 -V
Python 3.10.6


$ localstack --version
1.3.1

起動。

$ localstack start

MinIOサーバー。

$ minio --version
minio version RELEASE.2023-01-25T00-19-54Z (commit-id=50d58e9b2dbbfbe2f3afa2c16ae701f9c1cf03ec)
Runtime: go1.19.4 linux/amd64
License: GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>
Copyright: 2015-2023 MinIO, Inc.

起動。

$ minio server /var/lib/minio/data --console-address :9001

MinIOのCLI

$ mcli --version
mcli version RELEASE.2023-01-11T03-14-16Z (commit-id=14c2e506fa78b53fb6db88bcf87d8f6d3fb6989e)
Runtime: go1.19.4 linux/amd64
Copyright (c) 2015-2023 MinIO, Inc.
License GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>

MinIOのCLIからアクセスする時は、myminioエイリアスしておきます。ユーザー名とパスワードはminioadminminioadminです。

$ mcli alias set myminio http://localhost:9000 minioadmin minioadmin

AWS CLIも使います。

$ aws --version
aws-cli/2.9.18 Python/3.9.11 Linux/5.15.0-58-generic exe/x86_64.ubuntu.22 prompt/off

クレデンシャルは、環境変数で指定することにします。

$ export AWS_ACCESS_KEY_ID=minioadmin
$ export AWS_SECRET_ACCESS_KEY=minioadmin
$ export AWS_DEFAULT_REGION=ap-northeast-1

LocalStackの場合はアクセスキーIDとシークレットアクセスキーの値はなんでもいいのですが、同じものを使うことにします。
※LocalStack用のAWS CLIは、今回は外しておきます

バケットにアップロードしたファイルを、誰からも参照可能にする

LocalStack+AWS CLIで試す

まずは、LocalStackAWS CLIで操作してみましょう。

バケット作成。LocalStackをローカルで起動しているので、エンドポイントはhttp://localhost:4566です。

$ aws --endpoint-url http://localhost:4566 s3 mb s3://public-bucket
make_bucket: public-bucket

適当に、アップロードするファイルを用意します。

$ echo 'Hello Storage!!' > hello.txt


$ curl -LO https://tomcat.apache.org/res/images/tomcat.png


$ curl -LO https://dlcdn.apache.org/tomcat/tomcat-10/v10.0.27/bin/apache-tomcat-10.0.27.tar.gz

アップロード。

$ aws --endpoint-url http://localhost:4566 s3 cp ./hello.txt s3://public-bucket/hello.txt
upload: ./hello.txt to s3://public-bucket/hello.txt


$ aws --endpoint-url http://localhost:4566 s3 cp ./tomcat.png s3://public-bucket/images/tomcat.png
upload: ./tomcat.png to s3://public-bucket/images/tomcat.png


$ aws --endpoint-url http://localhost:4566 s3 cp ./apache-tomcat-10.0.27.tar.gz s3://public-bucket/archives/apache-tomcat-10.0.27.tar.gz
upload: ./apache-tomcat-10.0.27.tar.gz to s3://public-bucket/archives/apache-tomcat-10.0.27.tar.gz

これらのオブジェクトに対して、http://[LocalStackのアドレス]:[LocalStackのポート]/[バケット名]/[キー]でアクセスできるようです。

$ curl localhost:4566/public-bucket/hello.txt
Hello Storage!!

特になにも設定は要らなかったので驚きました…。

Content-Typeなどはどうなっているんでしょうね?

$ curl -I localhost:4566/public-bucket/hello.txt
HTTP/1.1 200
content-type: text/plain
Content-Length: 16
Server: Werkzeug/2.1.2 Python/3.10.8
Date: Thu, 26 Jan 2023 06:22:40 GMT
content-md5: W+wurJXnAgnol21+VbZXOg==
ETag: "5bec2eac95e70209e8976d7e55b6573a"
last-modified: Thu, 26 Jan 2023 06:19:38 GMT
x-amzn-requestid: 2JkUKhj1v9ZqSoMks0lPZllR58tUG2u4SHUECYD4Y0xzGYCgVL3B
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
Access-Control-Allow-Headers: authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request
Access-Control-Expose-Headers: etag,x-amz-version-id
x-amz-request-id: 5F70896B07057A95
x-amz-id-2: MzRISOwyjmnup5F70896B07057A957/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp
date: Thu, 26 Jan 2023 06:22:40 GMT
server: hypercorn-h11


$ curl -I localhost:4566/public-bucket/images/tomcat.png
HTTP/1.1 200
content-type: image/png
Content-Length: 8410
Server: Werkzeug/2.1.2 Python/3.10.8
Date: Thu, 26 Jan 2023 06:23:06 GMT
content-md5: QVzVnSsAbnlbn5nFW4PX7A==
ETag: "415cd59d2b006e795b9f99c55b83d7ec"
last-modified: Thu, 26 Jan 2023 06:19:45 GMT
x-amzn-requestid: sgohQAVCccKR1Dz3ZEg3oSj5stKqYBoD7rpiMF8NGWbsnMIB0l7w
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
Access-Control-Allow-Headers: authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request
Access-Control-Expose-Headers: etag,x-amz-version-id
x-amz-request-id: B96A1E2F60DCC523
x-amz-id-2: MzRISOwyjmnupB96A1E2F60DCC5237/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp
date: Thu, 26 Jan 2023 06:23:06 GMT
server: hypercorn-h11


$ curl -I localhost:4566/public-bucket/archives/apache-tomcat-10.0.27.tar.gz
HTTP/1.1 200
content-type: application/x-tar
Content-Length: 11984522
Server: Werkzeug/2.1.2 Python/3.10.8
Date: Thu, 26 Jan 2023 06:23:22 GMT
ETag: "b1b0a5be661bb2e3d5e29dbf9daa4db7-2"
last-modified: Thu, 26 Jan 2023 06:19:49 GMT
x-amzn-requestid: H8pQahqIcVroyKEg9v1YdlB86oYWwzBNg30Xv3jiSTlx1qCBY77w
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
Access-Control-Allow-Headers: authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request
Access-Control-Expose-Headers: etag,x-amz-version-id
x-amz-request-id: 7B8DD387A5E98F06
x-amz-id-2: MzRISOwyjmnup7B8DD387A5E98F067/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp
date: Thu, 26 Jan 2023 06:23:22 GMT
server: hypercorn-h11

ちゃんと設定されていそうですが、これはContent-Typeが未指定の場合にAWS CLIというかPythonが推測したもののようです。

https://github.com/aws/aws-cli/blob/2.9.18/awscli/customizations/s3/utils.py#L330-L336

mimetypes --- ファイル名を MIME 型へマップする — Python 3.9.12 ドキュメント

なるほど。

$ python3
Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import mimetypes
>>> mimetypes.guess_type('hoge.png')
('image/png', None)
>>> mimetypes.guess_type('hoge.tar.gz')
('application/x-tar', 'gzip')
>>> mimetypes.guess_type('hoge.txt')
('text/plain', None)

まあ、AWS CLIなどでアップロードする際に、Content-Type(必要に応じてContent-Encodingも)を設定するのが良いような気はします。

$ aws --endpoint-url http://localhost:4566 s3 cp ./hello.txt s3://public-bucket/hello.txt --content-type 'text/plain'
$ aws --endpoint-url http://localhost:4566 s3 cp ./tomcat.png s3://public-bucket/images/tomcat.png --content-type 'image/png'
$ aws --endpoint-url http://localhost:4566 s3 cp ./apache-tomcat-10.0.27.tar.gz s3://public-bucket/archives/apache-tomcat-10.0.27.tar.gz --content-encoding 'gzip' --content-type 'application/x-gzip'

これで、Content-TypeContent-Encodingに反映されます。

$ curl -I localhost:4566/public-bucket/archives/apache-tomcat-10.0.27.tar.gz
HTTP/1.1 200
content-type: application/x-gzip
Content-Length: 11984522
Server: Werkzeug/2.1.2 Python/3.10.8
Date: Thu, 26 Jan 2023 06:24:08 GMT
content-encoding: gzip
ETag: "b1b0a5be661bb2e3d5e29dbf9daa4db7-2"
last-modified: Thu, 26 Jan 2023 06:24:03 GMT
x-amzn-requestid: xNqrIvUIPXDQMuvH2IeF6NMvPrT6kzjBVpB3qQCgtjE4OWsYOnUl
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
Access-Control-Allow-Headers: authorization,cache-control,content-length,content-md5,content-type,etag,location,x-amz-acl,x-amz-content-sha256,x-amz-date,x-amz-request-id,x-amz-security-token,x-amz-tagging,x-amz-target,x-amz-user-agent,x-amz-version-id,x-amzn-requestid,x-localstack-target,amz-sdk-invocation-id,amz-sdk-request
Access-Control-Expose-Headers: etag,x-amz-version-id
x-amz-request-id: 3A824132B0C81C38
x-amz-id-2: MzRISOwyjmnup3A824132B0C81C387/JypPGXLh0OVFGcJaaO3KW/hRAqKOpIEEp
date: Thu, 26 Jan 2023 06:24:08 GMT
server: hypercorn-h11
MinIO+AWS CLIで試す

次は、MinIO+AWS CLIで試してみましょう。

MinIOをローカルで起動しているので、エンドポイントはhttp://localhost:9000です。

バケット作成。

$ aws --endpoint-url http://localhost:9000 s3 mb s3://public-bucket
make_bucket: public-bucket

ファイルをアップロード。

$ aws --endpoint-url http://localhost:9000 s3 cp ./hello.txt s3://public-bucket/hello.txt --content-type 'text/plain'
$ aws --endpoint-url http://localhost:9000 s3 cp ./tomcat.png s3://public-bucket/images/tomcat.png --content-type 'image/png'
$ aws --endpoint-url http://localhost:9000 s3 cp ./apache-tomcat-10.0.27.tar.gz s3://public-bucket/archives/apache-tomcat-10.0.27.tar.gz --content-encoding 'gzip' --content-type 'application/x-gzip'

LocalStackと同じようにhttp://[MinIOのアドレス]:[MinIOのポート]/[バケット名]/[キー]でアクセスできると思いきや、アクセス拒否されます。

$ curl localhost:9000/public-bucket/hello.txt
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied.</Message><Key>hello.txt</Key><BucketName>public-bucket</BucketName><Resource>/public-bucket/hello.txt</Resource><RequestId>173DC7EFA397313E</RequestId><HostId>0d54e0c2-1fda-463c-a355-f9b30c0a2c9e</HostId></Error>

ここで、以下のように作成したバケットに対して誰もアクセスできるようなポリシーを作成します。

bucket-policy.json

{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Effect":"Allow",
      "Action": [
        "s3:GetBucketLocation",
        "s3:ListBucket"
      ],
      "Resource":"arn:aws:s3:::public-bucket",
      "Principal": {
        "AWS": "*"
      }
    },
    {
      "Effect":"Allow",
      "Action": "s3:GetObject",
      "Resource":"arn:aws:s3:::public-bucket/*",
      "Principal": {
        "AWS": "*"
      }
    }
  ]
}

適用。

$ aws --endpoint-url http://localhost:9000 s3api put-bucket-policy --bucket public-bucket --policy file://bucket-policy.json

これで、アクセス可能になります。

$ curl localhost:9000/public-bucket/hello.txt
Hello Storage!!
MinIO+MinIO CLIで試す

MinIOの場合は、CLIがあるのでそちらでも確認しておきましょう。

といっても、権限設定の話を中心にしておきます。

anonymous setで、認証されていないユーザーに対する振る舞いを指定できます。

mc anonymous set — MinIO Object Storage for Linux

nonedownloaduploadpublicで簡易的にポリシーを設定でき、downloadでは誰でもダウンロードが可能になるポリシーです。
今回はdownloadを使います。

バケット作成。

$ mcli mb myminio/public-bucket
Bucket created successfully `myminio/public-bucket`.

誰からもダウンロードできるように設定。

$ mcli anonymous set download myminio/public-bucket

これで、アップロードしたファイルを誰でも参照できるようになります。
※ファイルのアップロードは省略しています

$ curl localhost:9000/public-bucket/hello.txt
Hello Storage!!

ちなみに、この時のバケットポリシーがどうなっているかというと、このようになっています。

$ aws --endpoint-url http://localhost:9000 s3api get-bucket-policy --bucket public-bucket
{
    "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::public-bucket\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::public-bucket/*\"]}]}"
}

文字列からJSONにして、フォーマットするとこうですね。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "*"
        ]
      },
      "Action": [
        "s3:GetBucketLocation",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::public-bucket"
      ]
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "*"
        ]
      },
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::public-bucket/*"
      ]
    }
  ]
}

つまりMinIO+AWS CLIの例で適用していたバケットポリシーは、これと同じものだった、ということになります。

バケットにアップロードしたファイルを、認証情報付きでアクセスする

LocalStackはいきなり認証情報なしでアクセスできたので、MinIO限定の話になりますが。

認証情報を付与してHTTPアクセスできないものでしょうか?

バケットを作成して、ファイルをアップロード。

$ aws --endpoint-url http://localhost:9000 s3 mb s3://private-bucket
$ aws --endpoint-url http://localhost:9000 s3 cp ./hello.txt s3://private-bucket/hello.txt

この状態になるのを、バケットポリシーを変更せずになんとかしようという話です。

$ curl localhost:9000/private-bucket/hello.txt
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied.</Message><Key>hello.txt</Key><BucketName>private-bucket</BucketName><Resource>/private-bucket/hello.txt</Resource><RequestId>173DC99B32B3BBD0</RequestId><HostId>11bf943b-4421-48df-ad00-a604a07470d6</HostId></Error>

どうやら、AWSのバージョン4の署名を付ければ良さそうではあります。

AWS API リクエスト署名の要素 - AWS 全般のリファレンス

また、curl 7.75で--aws-sigv4というオプションが追加されており、こちらを使えば良さそうです。

CURLOPT_AWS_SIGV4

--aws-sigv4にはaws:amz:[リージョン]:[サービス名]を指定します。aws:amzは固定です。
また、--userにはアクセスキーIDとシークレットアクセスキーをユーザー名とパスワードとして指定します。

$ curl --aws-sigv4 'aws:amz:ap-northeast-1:s3' --user 'minioadmin:minioadmin' localhost:9000/private-bucket/hello.txt
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message><Key>hello.txt</Key><BucketName>private-bucket</BucketName><Resource>/private-bucket/hello.txt</Resource><RequestId>173DCABFE9F561A4</RequestId><HostId>11bf943b-4421-48df-ad00-a604a07470d6</HostId></Error>

なのですが、うまくいきません…。

どうやら80または443以外のポートがエンドポイントになっていると、動かないようです。

--aws-sigv4 does not sign requests with remote port (not 80 or 443) correctly. · Issue #9550 · curl/curl · GitHub

現時点でのワークアラウンドとしては、Hostヘッダーからポートを外すと動作します。

$ curl --aws-sigv4 'aws:amz:ap-northeast-1:s3' --user 'minioadmin:minioadmin' -H 'Host: localhost' localhost:9000/private-bucket/hello.txt
Hello Storage!!

curl 7.86で修正されているようなのですが、

aws_sigv4: fix headers computation for some unhandled cases. by outscale-mgo · Pull Request #7966 · curl/curl · GitHub

手元のcurlは7.81だったので…。

$ curl --version
curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 OpenSSL/3.0.2 zlib/1.2.11 brotli/1.0.9 zstd/1.4.8 libidn2/2.3.2 libpsl/0.21.0 (+libidn2/2.3.2) libssh/0.9.6/openssl/zlib nghttp2/1.43.0 librtmp/2.3 OpenLDAP/2.5.13
Release-Date: 2022-01-05
Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets zstd

Docker Hubのcurlのイメージから、新しいcurlで試すとうまくいきました。

$ docker container run -it --rm --name curl curlimages/curl:7.87.0 curl --aws-sigv4 'aws:amz:ap-northeast-1:s3' --user 'minioadmin:minioadmin' [ホストのIPアドレス]:9000/private-bucket/hello.txt
Hello Storage!!

バージョン。

$ docker container run -it --rm --name curl curlimages/curl:7.87.0 curl --version
curl 7.87.0-DEV (x86_64-pc-linux-musl) libcurl/7.87.0-DEV OpenSSL/1.1.1s zlib/1.2.12 brotli/1.0.9 libssh2/1.10.0 nghttp2/1.47.0
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps mqtt pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IPv6 Largefile libz NTLM NTLM_WB SSL threadsafe TLS-SRP UnixSockets

こんなところでしょうか。

まとめ

LocalStackとMinIOで、バケットにアップロードしたファイルをHTTPで参照してみました。

LocalStackはなにも考えずにアクセスできましたが、MinIOはポリシーの設定が必要にはなるものの、期待した使い方はできるということが
わかって良かったかなと思います。

それから、curlAWSのバージョン4の署名に関するオプションは知りませんでした…。

Docker Composeを使った時に、コンテナ間で名前解決したい

これは、なにをしたくて書いたもの?

ふだんDocker Composeをそんなに使っていないのですが、たまに使うとコンテナ間で名前解決するのにどうすればいいのかよくわからなく
なるので、ちゃんと見ておくことにしました。

参照ページ

主に、以下のページを見ています。

Networking in Compose | Docker Documentation

Compose specification | Docker Documentation

環境

今回の環境は、こちら。

Docker Engine。

$ docker version
Client: Docker Engine - Community
 Version:           20.10.23
 API version:       1.41
 Go version:        go1.18.10
 Git commit:        7155243
 Built:             Thu Jan 19 17:45:08 2023
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.23
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.18.10
  Git commit:       6051f14
  Built:            Thu Jan 19 17:42:57 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.15
  GitCommit:        5b842e528e99d4d4c1686467debf2bd4b88ecd86
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Docker Compose。

$ docker compose version
Docker Compose version v2.15.1

確認する構成

今回確認を行うための構成は、以下から始めることにしました。

compose.yaml

services:
  web:
    image: nginx:1.23.3

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null

  cache:
    image: redis:7.0.8

nginxとRedisがあり、間にあるUbuntu LinuxからnginxやRedisにアクセスする、というシナリオで考えてみたいと思います。

以降は、docker compose upで各コンテナを起動した後に

$ docker compose up

Ubuntu Linuxにログインして、動作確認に必要なパッケージをインストールしたものとして書いていきます。

$ docker compose exec -it task bash
# apt update && apt install -y curl redis-tools bind9-dnsutils

もっともシンプルなパターン

実は、なにもせずとも各コンテナにアクセスできます。

Each container can now look up the hostname web or db and get back the appropriate container’s IP address.

Networking in Compose | Docker Documentation

ネットワークを作成して、servicesに定義した名前でアクセスできるようになるみたいです。

When you run docker compose up, the following happens:

  1. A network called myapp_default is created.
  2. A container is created using web’s configuration. It joins the network myapp_default under the name web.
  3. A container is created using db’s configuration. It joins the network myapp_default under the name db.

起動。

$ docker compose up

確認。

# curl web
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>


# redis-cli -h cache
cache:6379>

確かにアクセスできます。

# dig +short web
172.18.0.3


# dig +short cache
172.18.0.4

なお、/etc/hostsに書かれているというわけではなさそうです。

# cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2      619f085a21ac

サービスあたりのコンテナ数を増やしてみる

サービスあたりのコンテナ数を増やしてみると、どうなるのでしょう?

今度は、nginxおよびRedisを3つにしてみます。

$ docker compose up --scale web=3 --scale cache=3

確認してみます。

# curl web
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>


# redis-cli -h cache
cache:6379>

依然としてアクセスできますね、どうなっているんでしょう?

# dig +short web
172.18.0.5
172.18.0.7
172.18.0.2


# dig +short cache
172.18.0.6
172.18.0.8
172.18.0.4

コンテナ数だけIPアドレスが返ってくるようになっていますね。

networks+aliasesでエイリアスを定義する

networksaliasesを使うことで、エイリアスも定義できます。

Compose specification / Services top-level element / networks

たとえば、nginxredisというエイリアスを定義してみます。ネットワークは、デフォルトネットワークとしました。

compose.yaml

services:
  web:
    image: nginx:1.23.3
    networks:
      default:
        aliases:
          - nginx

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null

  cache:
    image: redis:7.0.8
    networks:
      default:
        aliases:
          - redis

コンテナ数は増やしたまま起動しましょう。

$ docker compose up --scale web=3 --scale cache=3

サービス名でも、エイリアスでも名前解決できますね。

# dig +short web
172.18.0.5
172.18.0.3
172.18.0.7


# dig +short nginx
172.18.0.5
172.18.0.3
172.18.0.7


# dig +short cache
172.18.0.8
172.18.0.6
172.18.0.4


# dig +short redis
172.18.0.4
172.18.0.6
172.18.0.8

linksも使うことができます。

Compose specification / Services top-level element / links

以下のように、links[サービス名]:[エイリアス]または[サービス名]で定義します。サービス名のみを指定した場合は、サービス名が
そのままエイリアスになるようです。

compose.yaml

services:
  web:
    image: nginx:1.23.3

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null
    links:
      - "web:nginx"
      - "cache"

  cache:
    image: redis:7.0.8

コンテナ数は増やして起動。

$ docker compose up --scale web=3 --scale cache=3

確認。

# dig +short web
172.18.0.7
172.18.0.5
172.18.0.3


# dig +short nginx
172.18.0.7


# dig +short cache
172.18.0.6

動きが変わりましたね。エイリアスでアクセスすると、複数のコンテナがある場合はひとつのIPアドレスに収束するようです。

そもそも、linksの説明にサービス間の通信をしたいのであれば必要ない、と書かれていますからね。

Links are not required to enable services to communicate - when no specific network configuration is set, any service MUST be able to reach any other service at that service’s name on the default network.

Compose specification / Services top-level element / links

名前解決の目的で、linksは使わない方が良さそうです。

また、linksにはコンテナ間に依存関係を持ち込む効果もあるようですが、これはdepends_onでよいでしょう。

Links also express implicit dependency between services in the same way as depends_on, so they determine the order of service startup.

複数のネットワークを定義して、通信を区分けする

最後は、このパターンを試してみたいと思います。

Networking in Compose / Specify custom networks

ここまではデフォルトネットワークを使用していましたが、独自にネットワークを構成して、サービスがどのネットワークに属するかで
通信を区切ることもできるようです。

たとえば、2つのネットワークを定義してみます。

compose.yaml

services:
  web:
    image: nginx:1.23.3
    networks:
      - frontend

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null
    networks:
      - frontend
      - backend

  cache:
    image: redis:7.0.8
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

Ubuntu Linuxは、どちらのネットワークにも属しています。

起動。

$ docker compose up --scale web=3 --scale cache=3

確認。

# dig +short web
172.20.0.3
172.20.0.5
172.20.0.4


# dig +short cache
172.19.0.4
172.19.0.5
172.19.0.2

どちらのサービスも参照できていますね。

では、次はUbuntu LinuxはRedisが属するネットワークには含めないようにしてみます。

compose.yaml

services:
  web:
    image: nginx:1.23.3
    networks:
      - frontend

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null
    networks:
      - frontend

  cache:
    image: redis:7.0.8
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

この部分ですね。

  task:
    image: ubuntu:22.04
    entrypoint:
      - tail
      - -f
      - /dev/null
    networks:
      - frontend

すると、Redisへの名前解決ができなくなります。

# dig +short web
172.20.0.3
172.20.0.4
172.20.0.5


# dig +short store

こんなところでしょうか。

まとめ

Docker Composeを使った時に、コンテナ間で名前解決するバリエーションについて見てみました。

このあたりはほとんどわかっていなかったのでで、どういうバリエーションがあるか、その挙動について確認できたので良かったかなと
思います。

デフォルトだと、なにもせずにサービス名で名前解決できるんですね。