まくろぐ
更新: / 作成:

爆速で使いやすいインクリメンタル・サーチエンジンの Meilisearch ですが、本番環境で運用するときは、アクセス制御用のマスターキーや API キーの使い方を理解する必要があります。

Meilisearch のデフォルト動作

Meilisearch サーバーを次のように特に何もオプションを指定せずに起動すると、開発用の development 環境として立ち上がります。 アクセス制御用のマスターキーも設定されません。

お試し用の Meilisearch サーバー起動
$ docker run -it --rm -p 7700:7700 \
    -v $(pwd)/meili_data:/meili_data \
    getmeili/meilisearch:v1.6

動作環境やマスターキーは、Meilisearch サーバーの起動時に、下記のような環境変数(あるいはコマンドライン引数)で指定することができます。

環境変数CLI オプションデフォルト値設定可能な値
MEILI_ENV--envdevelopmentdevelopment or production
MEILI_MASTER_KEY--master-keyなし16 バイト以上の UTF-8 文字列

デフォルトではアクセス制御を行うためのマスターキーが設定されず、Meilisearch サーバーはどのような API 呼び出しも受け付けてしまいます(インデックスデータの更新を含めて)。 マスターキーを設定して Meilisearch サーバーを起動すると、API への匿名アクセスができなくなります(ヘルスチェック用の API エンドポイント /health を除く)。

マスターキーと API キー

Meilisearch のキーには、マスターキー (master key)API キー (API keys) の 2 種類があります。 どちらのキーも、API 呼び出し時の Authorization ヘッダーに指定するものですが、次のような用途の違いがあります。

マスターキー (master key)

Meilisearch サーバーを起動するときに、MEILI_MASTER_KEY 環境変数、あるいは --master-key オプションで設定するキーです。 本番環境 (--env=production) で起動する場合、必ずマスターキーを指定する必要があります。

API 呼び出し時にマスターキーを使うと、どんな処理でも実行できてしまうので、マスターキーは厳重に管理する必要があります。 後述の「API キー」を管理するための /keys エンドポイントには、マスターキーでしかアクセスできません。 逆に、マスターキーはこの /keys エンドポイントへのアクセスのみに使用することが推奨されています。

API キー (API keys)

各種クライアントからの API 呼び出し時に使用するキーです。 API キーは、マスターキーを使って /keys エンドポイントにアクセスすることで、生成・削除・リスト表示できます。 API キーの生成時に、その権限を次のようなプロパティで設定できるようになっています。

  • indexes プロパティ … どのインデックスにアクセス可能か (例: "indexes": ["*"])
  • actions プロパティ … どのような操作が可能か(例: "actions": ["search"]
  • expiresAt プロパティ … いつまで使用できるか(例: "expiresAt": null

マスターキーを指定して Meilisearch サーバーを起動すると、次のような 2 つの デフォルト API キーが自動生成されます。

Default Admin API Key
すべてのインデックスに対して、すべての API 呼び出しを行うことができる API キーです。 ただし、API キー自体を管理するための /keys エンドポイントにはアクセスできません(マスターキーが必要)。 主にインデックスを更新するときに使用します。 キーの使用期限はありません。
Default Search API Key
すべてのインデックスの読み取りアクセス (search) が可能な API キーです。 クライアントアプリケーションから検索処理を行うときに使用します。 キーの使用期限はありません。

自動生成されるデフォルト API キーをそのまま使ってもよいですし、アプリケーションごとに新しく API キーを生成するのもよいです。 デフォルト API キーは削除することが可能です。

マスターキーを生成する

マスターキーには、UTF-8 で 16 バイト以上の任意の文字列を設定できます。 英数字であれば通常は 16 文字以上に相当します。 次のような Linux コマンドを使用して、ランダムなマスターキー文字列を生成できます。

ランダムなマスターキーの生成方法
$ openssl rand -hex 16
8801d3d091409c4fedd56bd425dc1a0d

$ uuidgen
FA41233F-B85E-4749-8E95-A90118171FE8

あるいは、RandomKeygen のサイト で表示されたランダム文字列を使ってもよいですし、マスターキーを指定せずに Meilisearch サーバーを起動したときに表示される、次のようなメッセージを利用するのでも OK です。

We generated a new secure master key for you (you can safely use this token):

>> --master-key xaAEQv0e4wVMJ4QgaRlLpK2TZGUBply4Gliix8hk43E <<

本番環境で Meilisearch サーバーを起動する

Meilisearch サーバーを本番環境 (--env=production) として起動するときは、マスターキーも合わせて指定する必要があります。 例えば、次のように環境変数で設定しておくことができます。

環境変数によるマスターキーの設定
export MEILI_MASTER_KEY=8801d3d091409c4fedd56bd425dc1a0d

ここでは、Docker Compose ファイルを使って Meilisearch コンテナーを起動することにします。 ポイントは、ハイライトされた環境変数の部分 (environment:) です。

docker-compose.yml
version: "3.9"

services:
  meilisearch:
    image: "getmeili/meilisearch:v1.6"
    container_name: meilisearch
    ports:
      - "7700:7700"
    environment:
      - MEILI_ENV=production  # 本番環境として起動する
      - MEILI_NO_ANALYTICS=true  # テレメトリデーターは送らない
      - MEILI_MASTER_KEY  # マスターキーはシェルの環境変数をそのまま渡す
    volumes:
      - type: bind
        source: ./meili_data
        target: /meili_data
        bind:
          create_host_path: true

このファイルがあるディレクトリで、次のように Meilisearch コンテナーを起動・停止できます。

$ docker compose up -d  # Meilisearch コンテナーの起動
$ docker compose down   # Meilisearch コンテナーの停止

本番環境で Meilisearch の API を呼び出してみる

マスターキーを指定して Meilisearch を起動すると、各 API エンドポイントへのアクセス時に API キー(あるいはマスターキー)が必要になります。 ただし、ヘルスチェック用の /health エンドポイントだけはそのままアクセスできます。

$ curl 127.0.0.1:7700/health  # アクセスできる
{"status":"available"}

$ curl 127.0.0.1:7700/keys  # アクセスできない
{"message":"The Authorization header is missing. ...省略...

API キーの確認

HTTP の GET メソッドで /keys エンドポイント にアクセスすると、API キーの一覧を取得することができます。 Authorization ヘッダーで マスターキー を指定する必要があります。

$ curl -H "Authorization: Bearer 8801d3d091409c4fedd56bd425dc1a0d" \
    http://localhost:7700/keys | jq
{
  "results": [
    {
      "name": "Default Search API Key",
      "description": "Use it to search from the frontend",
      "key": "d044f32b03a0b7b2b38e96a5fe3ca44dbb3bca443ec8458d184a8f89d34eaf78",
      "uid": "e6d62591-4123-45e4-a68f-5f19f9961def",
      "actions": [
        "search"
      ],
      "indexes": [
        "*"
      ],
      "expiresAt": null,
      "createdAt": "2024-02-15T15:20:06.785410298Z",
      "updatedAt": "2024-02-15T15:20:06.785410298Z"
    },
    {
      "name": "Default Admin API Key",
      "description": "Use it for anything that is not a search operation. Caution! Do not expose it on a public frontend",
      "key": "29847d8297d17657ac98555cb7059b2a59288509f84fd28e87f0430d15f04ae9",
      "uid": "7b372564-d644-4aac-a783-64b72bbc69e5",
      "actions": [
        "*"
      ],
      "indexes": [
        "*"
      ],
      "expiresAt": null,
      "createdAt": "2024-02-15T15:20:06.783015465Z",
      "updatedAt": "2024-02-15T15:20:06.783015465Z"
    }
  ],
  "offset": 0,
  "limit": 20,
  "total": 2
}
☝️ jq コマンド

上記で使用している jq コマンドは、JSON 出力の整形ツールです。 インストール方法は、公式の download ページ に記述されています。 下記はインストール方法の一例です。

  • macOS の場合: brew install jq / port install jq
  • Windows の場合: winget install jqlang.jq / choco install jq
  • Debian / Ubuntu の場合: sudo apt install -y jq

/keys エンドポイントからの出力結果を抜粋して見ると、前述したように 2 つのデフォルト API キーが生成されていることを確認できます。

"name": "Default Search API Key",
"key": "d044f32b03a0b7b2b38e96a5fe3ca44dbb3bca443ec8458d184a8f89d34eaf78",
...
"name": "Default Admin API Key",
"key": "29847d8297d17657ac98555cb7059b2a59288509f84fd28e87f0430d15f04ae9",

主に、Default Search API Key の方はフロントエンドアプリからの検索に使用し、Default Admin API Key の方はインデックスの更新などに使用します。

新しい API キーを作成する

特定のインデックスへのアクセスだけを許可する API キーを作りたい場合は、/keys エンドポイントに POST メソッドでアクセスします。 次の例では、books インデックスの検索のみを許可する API キーを作成しています。

books インデックス検索用の API キーを作成
$ curl \
    -X POST 'http://localhost:7700/keys' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer 8801d3d091409c4fedd56bd425dc1a0d' \
    --data-binary '{
      "name": "Search key for books index",
      "actions": ["search"],
      "indexes": ["books"],
      "expiresAt": "2025-01-01T00:00:00Z"
    }'

"actions": ["search"] というアクション指定は、/indexes/{index_uid}/search エンドポイントへの GET および POST アクセスのみを許可することを示しています。 API キーの作成に成功すると、次のように出力されます。 key プロパティに表示されている長い文字列が、検索時に使用する API キーです。

生成された API キー
{
  "name": "Search key for books index",
  "description": null,
  "key": "e4dca0e0e82f6af276d3090a6e9294e61288bdbd9ee9097a91cd3ad393f57cc0",
  "uid": "800f8446-5813-429b-8508-7fcd97370b01",
  "actions": [
    "search"
  ],
  "indexes": [
    "books"
  ],
  "expiresAt": "2025-01-01T00:00:00Z",
  "createdAt": "2024-02-15T16:05:03.075883962Z",
  "updatedAt": "2024-02-15T16:05:03.075883962Z"
}

インデックスの更新(ドキュメントの追加)などを行うための API キーには、actions プロパティとして、documentsindexes などを指定する必要があります。 ワイルドカード (*) を指定すると、すべてのアクションが許可されます。 ワイルドカードの使用は推奨されないようですが、ちゃんとキー管理しておけば、まぁ大丈夫かと思います。

管理用の API キーを作成
$ curl \
    -X POST 'http://localhost:7700/keys' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer 8801d3d091409c4fedd56bd425dc1a0d' \
    --data-binary '{
      "name": "Admin key for books index",
      "actions": ["*"],
      "indexes": ["books"],
      "expiresAt": "2025-01-01T00:00:00Z"
    }'

これで、books インデックス専用の「検索用 API キー」と「管理用 API キー」を作成できました。

API キーの削除

不要な API キーを削除するには、DELETE メソッドで /keys/{uid} エンドポイントにアクセスします。

API キーの削除
$ curl \
  -X DELETE 'http://localhost:7700/keys/800f8446-5813-429b-8508-7fcd97370b01' \
  -H 'Authorization: Bearer 8801d3d091409c4fedd56bd425dc1a0d'

URL のパスの末尾には、削除したい API キーの uid を指定しますが、uid がわからない場合は、GET メソッドで API キーの一覧を確認しましょう。

API キーの uid を確認
$ curl -X GET 'http://localhost:7700/keys' \
    -H 'Authorization: Bearer 8801d3d091409c4fedd56bd425dc1a0d' \
    | jq '.results[] | {name, uid}'

{
  "name": "Admin key for books index",
  "uid": "fd5efe58-cd71-428a-b6d4-34a04f5f1ff4"
}
{
  "name": "Search key for books index",
  "uid": "800f8446-5813-429b-8508-7fcd97370b01"
}

インデックスの作成と検索

適切な API キーを準備できたら、あとはそれらを使ってインデックスを作成し、検索するだけです。 ここでは、下記のようなテスト用ドキュメントを投入することにします。

[
  {
    "id": "1",
    "title": "The Great Gatsby",
    "author": "F. Scott Fitzgerald",
    "genre": "Fiction",
    "year": 1925
  },
  {
    "id": "2",
    "title": "To Kill a Mockingbird",
    "author": "Harper Lee",
    "genre": "Fiction",
    "year": 1960
  },
  {
    "id": "3",
    "title": "1984",
    "author": "George Orwell",
    "genre": "Science Fiction",
    "year": 1949
  },
  {
    "id": "4",
    "title": "Pride and Prejudice",
    "author": "Jane Austen",
    "genre": "Romance",
    "year": 1813
  },
  {
    "id": "5",
    "title": "Moby-Dick",
    "author": "Herman Melville",
    "genre": "Adventure",
    "year": 1851
  }
]

まず、管理用の API キーを使って、books インデックスを作成します。

books インデックスの作成
$ curl -X POST 'http://localhost:7700/indexes/books/documents' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer 275e10e7234602...省略...' \
    --data-binary @books.json

次に、検索用の API キーを使って、books 内のドキュメントを検索します。 ペイロード部分 (--data-binary) でクエリ文字列を送るため、GET ではなく POST メソッドを使うことに注意してください(参考: Search API)。

books インデックスの検索
$ curl -X POST 'http://localhost:7700/indexes/books/search' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer ef43c9a2cac822...省略...' \
    --data-binary '{ "q": "fiction" }' | jq
{
  "hits": [
    {
      "id": "1",
      "title": "The Great Gatsby",
      "author": "F. Scott Fitzgerald",
      "genre": "Fiction",
      "year": 1925
    },
    {
      "id": "2",
      "title": "To Kill a Mockingbird",
      "author": "Harper Lee",
      "genre": "Fiction",
      "year": 1960
    },
    {
      "id": "3",
      "title": "1984",
      "author": "George Orwell",
      "genre": "Science Fiction",
      "year": 1949
    }
  ],
  "query": "fiction",
  "processingTimeMs": 0,
  "limit": 20,
  "offset": 0,
  "estimatedTotalHits": 3
}

その他の API も、上記のような感じで API キーを使って呼び出せば OK です。

関連記事

更新: / 作成:

nginx-proxy は Docker コンテナ用のリバースプロキシ

Docker コンテナイメージの nginx-proxy を使うと、複数の Docker コンテナ用のリバースプロキシを簡単に立ち上げることができます。

例えば、1 つの VPS サーバー上で複数の Docker コンテナー(例えば Web サーバー)を立ち上げるような場合は、次のような感じで入り口にリバースプロキシを配置し、そこから各 Web サーバーにアクセスを振り分ける構成にします。 ここでは、aaa.example.combbb.example.com というサブドメインにより、プロキシ先を振り分けるようにしていますが、example.com というベースドメインを使うこともできます。

/p/kos367z/img-001.drawio.svg
図: nginx-proxy によるリバースプロキシでコンテナーを繋ぐ

もともと Nginx でリバースプロキシを設定するのはそれほど難しくはないのですが、どうしてもその背後にあるコンテナーの設定がリバースプロキシ側の設定に染み出してしまいます。 また、リバースプロキシ用のコンテナーを最後に起動するという手順の複雑さもあります。

nginx-proxy によるリバースプロキシを起動しておくと、背後でコンテナーを起動するだけで、自動的にリバースプロキシのエントリに追加してくれます。 nginx-proxy には次のような利点があります。

  • 背後の Docker コンテナの存在を意識しなくてよい(同一の Docker ネットーワーク内のコンテナ起動を検出して、自動的にリバースプロキシ設定に反映してくれます)
  • 各サブドメイン用の SSL 設定(HTTPS 対応)を自動で行ってくれる(acme-companion を組み合わせて使用することで、Let’s Encrypt による SSL 証明書の取得を自動化できます)

リバースプロキシの設定更新が自動化されているので、Web アプリ用の Docker コンテナーを簡単に追加・削除できます。

事前準備

nginx-proxy の設定を行う前に、サブドメイン用の DNS 設定や、80/443 ポートの解放などを行っておく必要があります。 ネットワーク的に繋がらないとどうしようもないですからね。

サブドメインの DNS 設定

独自ドメインの DNS 設定で、サブドメイン用の A レコードを追加して IP アドレスに関連づけておきます。 下記は ConoHa VPS での DNS 設定例ですが、だいたい同じような感じで設定できるはずです。

/p/kos367z/img-002.png
図: A レコードの追加(IP アドレスは偽物です)

反映されるまで数分待ってから、DNS の A レコードを正しく引けるか確認しておきます。 例えば、dig コマンドの出力の status:NOERROR になっていれば正しく設定されています(もっと簡単に nslookupping で正引きできるかを確認するのでも OK です)。

## A レコードが正しく設定されている場合
$ dig A aaa.example.com | grep status
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55073

## A レコードが設定されていない場合
$ dig A aaa.example.com | grep status
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 55073

http/https 用のポート解放

対象のサーバー(VPS など)のファイアウォール設定で、外部からの 80 番ポート (http) および 443 番ポート (https) へのアクセスを許可しておきます。

まずは http ベースで設定してみる

下記は、nginx-proxy コンテナーを起動するための Docker Compose ファイルです。 volumes: プロパティで docker.sock ファイルをマウントしているのは、nginx-proxy が他のコンテナーの起動を監視して情報を取得するためです。

nginx-proxy/docker-compose.yml
version: "3.8"

# nginx-proxy コンテナの基本的な設定
services:
  nginx-proxy:
    image: nginxproxy/nginx-proxy:1.5
    container_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
    restart: always

# 接続先の各コンテナーはこれと同じ Docker ネットワークに参加する必要がある
networks:
  default:
    name: nginx-proxy

次のようにしてリバースプロキシを起動できます。

リバースプロキシの起動
$ cd nginx-proxy
$ docker compose up -d

あとは、リバースプロキシの中継先となる Docker コンテナーを起動していくだけです。 このとき、バーチャルホスト名をコンテナー側の VIRTUAL_HOST 環境変数で設定することで、nginx-proxy が自動的にその情報をリバースプロキシ設定に反映してくれます。

例えば、簡単な Web サーバーを起動する場合は次のようにします。 リバースプロキシと同じ nginx-proxy ネットワークに接続することに注意してください(これを忘れると、アクセス時に 502 Bad Gateway エラーになります)。 ここでは接続確認だけしたいので、Ctrl-C で停止できる形でコンテナを起動します(-d オプションをつけません)。

テスト用に Web サーバーを起動
$ docker container run --rm --net nginx-proxy \
    --env VIRTUAL_HOST=aaa.example.com nginx:alpine
version: "3.8"

services:
  webapp:
    image: "nginx:alpine"
    container_name: webapp
    environment:
      VIRTUAL_HOST: aaa.example.com
    networks:
      - nginx-proxy

networks:
  nginx-proxy:
    external: true

Web サーバーを起動したら、Web ブラウザから http://aaa.example.com という URL でアクセスして、Welcome to nginx! のページが表示されれば成功です。

次のステップ(SSL 証明書の自動設定)に進む前に、リバースプロキシを停止しておきましょう。

$ cd nginx-proxy
$ docker compose down

https 通信 (SSL) に対応する

nginx-proxy と合わせて acme-companion という Docker コンテナイメージを使うと、SSL 証明書の設定まで自動化できます(https によるアクセスが可能になります)。 Let’s Encrypt による SSL 証明書発行処理のために、nginx-proxy コンテナーの方にもいくつかボリューム設定を追加する必要があります(本家サイトの説明通りに設定しています)。

nginx-proxy/docker-compose.yml
version: "3.8"

services:
  # リバースプロキシ
  nginx-proxy:
    image: nginxproxy/nginx-proxy:1.5
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - certs:/etc/nginx/certs
      - html:/usr/share/nginx/html
      - vhost:/etc/nginx/vhost.d
    restart: always

  # SSL/TLS 証明書の自動更新
  nginx-proxy-acme:
    image: nginxproxy/acme-companion:2.2
    container_name: nginx-proxy-acme
    volumes_from:
      - nginx-proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - acme:/etc/acme.sh
    restart: always

# 接続先の各コンテナーはこれと同じ Docker ネットワークに参加する必要がある
networks:
  default:
    name: nginx-proxy

# ボリュームの定義
volumes:
  acme:
  certs:
  html:
  vhost:

ボリューム設定が若干ややこしいですが、接続先のコンテナーの情報がまったく出てこないのが素晴らしいです。 つまり、この docker-compose.yml ファイルは、みなさんの環境でもそのまま使い回すことができます。

nginx-proxy コンテナを起動
$ cd nginx-proxy
$ docker compose up -d

中継先のコンテナー(ここでは Web サーバー)を立ち上げるときは、Let’s Encrypt 用の設定値として LETSENCRYPT_HOSTLETSENCRYPT_EMAIL を指定します。 これらは、SSL 証明書の発行時に使用されます。

webapp/docker-compose.yml
version: "3.8"

services:
  webapp:
    image: nginx:alpine
    container_name: webapp
    environment:
      VIRTUAL_HOST: aaa.example.com
      LETSENCRYPT_HOST: aaa.example.com
      LETSENCRYPT_EMAIL: yourname@example.com
    networks:
      - nginx-proxy

networks:
  nginx-proxy:
    external: true  # 既存のネットワークに乗っかる
webapp コンテナを起動
$ cd webapp
$ docker compose up -d

このコンテナを起動すると、自動的に Let’s Encrypt から SSL 証明書が発行されてリバースプロキシサーバーに設定されます。 これで、Web ブラウザから https://aaa.example.com というアドレスでアクセスできるようになります。

長々と説明してきましたが、結局のところ nginx-proxy を一度導入してしまえば、あとは中継先のコンテナーを起動するときに、VIRTUAL_HOSTLETSENCRYPT_HOST を指定するだけでインターネットから https (SSL) でアクセスできるようになる ということですね。 これで、Docker コンテナーを使ったサービスを簡単に公開できるようになります。

٩(๑❛ᴗ❛๑)۶ わーぃ

関連記事

更新: / 作成:
/p/k8htq7s/img-001.jpg
図: ソロモンの鍵 - タイトル画面

Switch でできるようになってたので、ものすごい久しぶりにファミコンの「ソロモンの鍵」をプレイ。

子供のころクリアできたかどうか記憶にないです。 Switch では どこでもセーブ巻き戻し という反則技が使えるので、なんとか全 50 面をクリアできました。 最難関と言われているらしい ROOM 43 がめちゃくちゃ難しくて、反則巻き戻しで 100 回くらいやり直してやっとクリアできたくらいなので、きっと子供の頃はクリアできてないですね。 昔クリアできなかったゲームを大人になってからクリアするのはなかなか感慨深い。

/p/k8htq7s/img-002.jpg
図: ソロモンの鍵 - 最終 ROOM 50

そういえば、ゲームセンター CX で有野課長がクリアしてたけど、結構ゲームうまいんじゃないかと思った。

ゲーム芸人フジタのプレイ はうますぎる。 レトロゲームの大会とか増えるといいなぁ。

関連記事

メニュー

まくろぐ
サイトマップまくへのメッセージ