Corredor

ウェブ、プログラミングの勉強メモ。

Kubernetes の nginx Ingress でパスを書き換えて転送したい

Kubernetes の Ingress が受け取ったパスの一部を加工して、後ろにいる Service (Pod) へ転送したくなった。どういうことかというと、

  • http://example.com/my-app-1/index.html

といった URL で Ingress がリクエストを受け取った時、後ろに用意した my-app-1 Service に対しては

  • http://my-app-1:3000/index.html
    • (my-app-1 部分はリクエストが到達した Pod にとっては localhost となる)

といったように、間の /my-app-1/ 部分を除去してやりたいのだ。

しかし、通常 Ingress の path 指定をそのまま書いてしまうと、Pod に対しても

  • http://my-app-1:3000/my-app-1/index.html

というように、/my-app-1/ 部分が付いたままリクエストが転送されてしまい、正しいパスにならずに 404 エラーになってしまったりする。

そこで今回は、rewrite-target 機能と use-regex 機能を使って、このパス置換 (パス・リライト) をやってやろうと思う。

いきなりコード

いきなりだが、こんな感じでコードを書けば良い。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  labels:
    name: my-ingress
  annotations:
    # 正規表現を使う
    nginx.ingress.kubernetes.io/use-regex: "true"
    # 「/」直後から、正規表現のキャプチャしたグループでパスを指定する
    nginx.ingress.kubernetes.io/rewrite-target: /$1$2
spec:
  tls:
    - hosts:
      - example.com
  rules:
    - host: example.com
      http:
        paths:
          # 前述のような `/my-app-1/index.html` などのリクエストを処理する
          - path: /my-app-1/(.*)
            backend:
              serviceName: my-app-1-service
              servicePort: 3000
          
          # 別の Service 向けの転送処理 : コチラはパスを書き換えず `http://my-app-2:8000/my-app-2/index.html` とそのまま転送する
          - path: /(my-app-2)(\/.*)
            backend:
              serviceName: my-app-2-service
              servicePort: 8000
          
          # 最後にフォールバック的にルートパスへのリダイレクト
          - path: /
            backend:
              serviceName: root-app
              servicePort: 8080
          - path: /(.*)
            backend:
              serviceName: root-app
              servicePort: 8080

重要なのは annotations に書いた2つの機能と、paths.path の中の正規表現キャプチャだ。

アノテーションで use-regexrewrite-target を指定していて、rewrite-target 部分で、/ 以降の文字列をどのように変換するかというルールを書いている。正規表現のキャプチャした文字列を $1$2 と指定して、それを渡すようにしている。

my-app-1path 指定を見ると、/my-app-1/(.*) と書いている。この場合、キャプチャしているのは $1 のみで、$2 に相当する文字列はないので空文字になる。つまり my-app-1-service に転送されるパスは /$1 の文字列になり、間に書かれていた /my-app-1/ が除去できるワケだ。

一方、my-app-2path 指定は、こうしたパスの除去を行わない例。path: /(my-app-2)(\/.*) と書いていて、$1my-app-2$2/.* となっているので、それをそのままくっつけて my-app-2-service に転送されるというワケ。

今回は $1$2 を指定したが、ココはお好みで。以下のように $2$3 で処理するような例もある。

metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/rewrite-target: /$2$3
spec:
  rules:
    - path: /()(path1)(.*)
      backend:
        serviceName: webapp1
    - path: /(path2)(.*)
      backend:
        serviceName: webapp2

正規表現を扱わないといけなくなってトリッキーだが、とりあえずこんな感じで対応できそう。

moreutils の中の便利そうなコマンド : sponge と vidir

moreutils という便利コマンド集があって、Yum とか Homebrew とかで簡単に入れられるので試してみた。今回はその中でも特に有用そうな2つのコマンド、spongevidir を紹介する。

moreutils のインストール方法

moreutils は OS に合わせて以下のようなコマンドでインストールすればよい。今回は MacOS (Homebrew) でインストールしたモノで試した。

# MacOS (Homebrew)
$ brew install moreutils

# CentOS (Yum)
$ yum install -y moreutils

# Ubuntu (apt)
$ apt install -y moreutils

# Windows GitSDK・Archi Linux (Pacman)
$ pacman -S moreutils

sponge

まず紹介するのは sponge コマンド。コレを使うと、入力と出力に同じファイルを利用できる。

例えば、example.txt の内容を読み込み、変換処理を加えて、結果を同じ example.txt に出力したくて、以下のようなコマンドを書いたとする。

$ sed 's/aaa/AAA/g' < example.txt > example.txt

このコマンドを実行すると、example.txt の中身は空になってしまう。まぁ sed に関しては -i オプションで上書き保存はできるのだが、あくまで一例として。

で、リダイレクト > の代わりに、パイプ |sponge コマンドを使うと、上のようなことが思ったとおりに実現できるようになる。

$ sed 's/aaa/AAA/g' < example.txt  | sponge example.txt 

こんな感じ。

vidir

vidir は、Vim っぽい画面でカレントディレクトリ配下のファイルやディレクトリ名をリネーム・削除できるツール。

$ vidir

と叩くと Vim 風な画面が現れ、カレントディレクトリ配下のファイルとディレクトリが見える。ココまでは $ vi ./ と叩いた時と似たように見える。

この状態で編集モードに入ると、ファイル名を書き換えたりでき、それを保存して終了すると実際にファイルがリネームされている。

Vim の矩形選択を覚えれば、複数ファイルの一括リネームが可能になるので、Vim における矩形選択の手順を紹介しよう。

  • 文字を一括で追加する場合
    1. 矩形選択で文字を追加したい箇所にカーソルを移動させておく
    2. Ctrl + v を押下し、「ビジュアル矩形 (Visual Block)」モードに入る
    3. 矩形選択したい行を jk (↑↓) で選択する
    4. Shift + iShift + a で編集モードに入る
    5. この状態で文字を打つと、選択した1行目にのみ変更が反映されていくが、一旦気にせず書く
    6. 追記が終わったら Esc を押す。すると追記した内容が選択していた行全てに反映される
  • 文字を一括で削除する場合
    1. 矩形選択で文字を削除したい箇所にカーソルを移動させておく
    2. Ctrl + v を押下し、「ビジュアル矩形 (Visual Block)」モードに入る
    3. 矩形選択で削除したい範囲 (文字数) を hl (←→) で選択する
    4. 矩形選択で削除する行を jk (↑↓) で選択する
    5. d を押下すると矩形選択した文字が削除される
  • 参考:vimを使った矩形選択 - Qiita

保存時は wq で終了すれば良いし、取り消したければ q! でキャンセルして終了すれば良い。

ディレクトリやファイルの削除にも対応していて、行ごと削除すればファイル削除される。ただし、ディレクトリは中が空でないと削除できない。

以上

シェル芸の幅が広がりそうなので、moreutils 覚えておこう。

Vue CLI で作ったアプリのバンドルサイズを分析する Vue CLI Plugin Webpack Bundle Analyzer

Vue は Webpack でバンドル処理を行うので、Webpack Bundle Analyzer を組み込める。

Vue CLI で作ったプロジェクトの場合、コレを簡単に仕込める Vue CLI Plugin Webpack Bundle Analyzer というプラグインが存在する。

Vue CLI 製のプロジェクトにて次のコマンドを実行するだけで良い。

$ vue add webpack-bundle-analyzer

ビルド後に結果ファイルが開かないよう、次のオプションを入れておく。

  • vue.config.js
module.exports = {
  pluginOptions: {
    // ↓ コレを追加
    webpackBundleAnalyzer: {
      openAnalyzer: false
    }
  }

コレで

$ npm run build

を実行すると、ビルド時に一緒に ./dist/report.html が出力される。コレをブラウザで開けば、よく見かける Webpack Bundle Analyzer の画面が確認できる。

Docker コンテナに注入する環境変数、どれが優先される?適用のされ方を実際に調べてみた

最終的に Kubernetes 上で動かすつもりのアプリを作っていると、環境変数を注入してアプリに使用させることが多々ある。

Node.js 製のアプリでは、dotenv という npm パッケージで .env ファイルを読み込んだりできるので、開発環境ではコレを使って環境変数を擬似的に設定したりもする。

そんなアプリを Docker イメージ化して、開発環境で動作確認したいな、という時に、どうやって環境変数を注入するか、手段がいくつか存在する。

今回はそれらを確認し、どのように Docker コンテナに環境変数を注入できるか複数の手法を併用したらどの環境変数設定が優先されるのか、といったことをまとめていく。

サンプルコード

今回は dotenv を使用した Node.js 製のアプリを想定し、簡単なサンプルコードを用意した。

  • package.json
    • dotenv をインストールしてある
{
  "name": "practice-env",
  "private": true,
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "dotenv": "8.2.0"
  }
}
  • index.js
    • ENV_1ENV_2ENV_3 という環境変数を3秒ごとに出力している
    • 自分で試す場合は、ココで用意する環境変数をいくつか増やしたりしてやると、バリエーション確認がしやすいだろう
const envFilePath = `${__dirname}/env-dir/.env`;
require('dotenv').config({ path: envFilePath });
setInterval(() => {
  console.log(`Env : [${envFilePath}]
    ENV_1 : [${process.env.ENV_1}]
    ENV_2 : [${process.env.ENV_2}]
    ENV_3 : [${process.env.ENV_3}]`);
}, 3000);
  • ./env-dir/.env
    • dotenv で読み込む環境変数ファイル
    • 今回は調査のため、プロジェクト直下ではなく、専用の階層を設けてファイルを用意している
    • 中身はデタラメに用意しておけば良い。全ての環境変数に値を与えず、一部だけ定義してやっても良いだろう
ENV_1=This is from .env file 1
ENV_2=This is from .env file 2
ENV_3=This is from .env file 3
  • Dockerfile
    • 今回は検証のため、Dockerfile の中で ENV_1 のみ値を設定してやっている
FROM node:14-alpine3.12

WORKDIR /app/

# 環境変数を直接指定する
ENV ENV_1='From Dockerfile'
# dotenv で参照する環境変数ファイルをコピーする
COPY ./env-dir/.env             /app/env-dir/

COPY ./package.json ./index.js  /app/
RUN npm install

CMD ["npm", "start"]

ココまで用意できたら、

$ docker build -t practice-env:0 ./

というコマンドで Docker イメージをビルドしてやる。ココまでで環境変数に対して値を注入しているのは2ヶ所である。

  1. .env ファイルを dotenv で読み込む
  2. ENV で環境変数を設定する

準備はココまで。

環境変数の注入方法を知る

こうして作成した Docker イメージを元に、色々な方法で環境変数を注入していく。

その際、同じ名前の環境変数に対して、異なる値を注入しようとした時に、どのやり方で渡した値が優先されるのか、検証する。

優先順位は不等号記号で表現する。例えば A < B と書いたら、「A の方法で注入した値より、B の方法で注入した値の方が優先された」という表現になる。

1. そのまま実行 → .envENV

まずはそのまま実行してみる。

$ docker run --rm practice-env:0

出力を確認すると、COPY した .env ファイルを dotenv が読み込んでいることは確認できる。

しかし、環境変数 ENV_1 については、Dockerfile で ENV 命令にて設定した値の方が優先されていた。

dotenv は、既に値が設定されている場合は .env ファイルの内容を適用しないことが分かる。コレは実際のコードを見ても明らかである。

ということで、.env ファイルとシステム環境変数とでは、常にシステム環境変数の方が優先され、.env ファイルに書いても上書きはできないことを押さえておこう。

2. .env ファイルをボリュームマウントで差替 → イメージ内のファイル < ボリュームマウントしたファイル

次は docker run コマンド時に --volume (-v) オプションを使ってみる。

$ docker run --rm --volume="$(pwd)/docker-volume:/app/env-dir" practice-env:0

このようにして、Docker イメージに取り込み済みの .env ファイルを、別の内容の .env ファイルに差し替えてみる。コレをやるために、環境変数ファイル用のディレクトリ env-dir/ を設けていたワケ。

--volume オプションは、Docker イメージ内に既に同名のファイルがあっても、ディレクトリごと差し替える形で中身を入れ替えてしまう。というワケで、Docker イメージ内に COPY しておいた .env ファイルの内容は全て破棄され、ボリュームマウントした .env ファイルを dotenv が解釈する形となる。

3. --env-file オプション → dotenvENV 命令 < --env-file

次は、dotenv とよく似てはいるが、Docker 自身が指定のファイルをシステム環境変数として注入してくれる、--env-file オプションを使ってみる。

$ docker run --rm --env-file='./docker-env-file/ENV-FILE' practice-env:0

--env-file オプションは dotenv などとは関係なく、システム環境変数として注入されるので、シェルの世界で $ env コマンドを実行しても確認できるモノとなる。

このオプションを使うと、dotenv で注入した値は勿論、DockerfileENV 命令で定義した環境変数であっても、上書き適用できる。

4. --env オプション → dotenvENV 命令 < --env

次は .env ファイルを渡すのではなく、コマンドライン上で環境変数の Key・Value を渡してやる、--env (-e) オプションを使ってみる。

$ docker run --rm --env 'ENV_1=from env option`

コチラも --env-file と同様、dotenv で読み込む値、ENV 命令で指定した値よりも優先された。

全体的にいえるのは、

  • dotenv は一番優先度が低い (定義済のシステム環境変数を優先する・dotenv の仕様どおり)
  • Docker イメージ作成時に指定した値より、Docker コンテナ生成・実行時に注入する値の方が優先される

ということであろう。

オプションを併用したらどうなるか?

ココまでで大体分かってきた気はするが、もう少し検証。次は docker run 時に複数のオプションを併用した場合にどうなるか、見てみる。

1. ボリュームマウント + ファイルから注入 → ボリュームマウント < ファイルから注入

$ docker run --rm --volume="$(pwd)/docker-volume:/app/env-dir" --env-file='./docker-env-file/ENV-FILE' practice-env:0`

--volume--env-file。コレは dotenv 仕様のとおり、ボリュームマウントされた .env も読み込まれはするが、--env-file の値の方が優先される。

2. ボリュームマウント + オプション引数から注入 → ボリュームマウント < オプション引数から注入

$ docker run --rm --volume="$(pwd)/docker-volume:/app/env-dir" --env 'ENV_1=from env option' practice-env:0

--volume--env の比較。結果は --env-file の時と同じく、--env で指定した値の方が優先される。

3. ファイルから注入 + オプション引数から注入 → ファイルから注入 < オプション引数から注入

$ docker run --rm --env-file='./docker-env-file/ENV-FILE' --env 'ENV_1=from env option' practice-env:0

--env-file--env を併用した場合は、--env で指定した値の方が上書き優先される。

オプション引数の指定順は関係なく、--env-file よりも --env の方が優先された。コレは Docker の仕様どおりである。

これらの3つのフラグに関係なく、--env-file が始めに処理され、その後 -e--env フラグが処理されます。この方法は、必要な時に -e--env で変数を上書きするために使えます。

大部分は --env-file で指定したファイルから読み込ませたいが、一部だけ変えたい、という時に、ファイルを書き換えるのではなく実行時のコマンドで上書きできるようにしてあるワケだ。

4. ボリュームマウント + ファイルから注入 + オプション引数から注入 → ボリュームマウント < ファイルから注入 < オプション引数から注入

全てを併用した時も一応確認。

$ docker run --rm --volume="$(pwd)/docker-volume:/app/env-dir" --env-file='./docker-env-file/ENV-FILE' --env 'ENV_1=from env option' practice-env:0

それぞれの手法で、別々の名前の環境変数を設定しているのであれば、いずれも正しく適用される。

同じ名前の環境変数に対する設定が衝突する場合は、--volume より --env-file--env-file より --env の値の方が優先される。

まとめ

というワケで、Docker + dotenv における環境変数の適用順は、次の順番で順に定義され、上書きされていくモノと思うと良いだろう。

  1. dotenv が読み込む、Docker イメージ内の .env ファイル
  2. dotenv が読み込む、ボリュームマウントして差し替えた .env ファイル
    • 「1.」の .env ファイルと完全差し替え
    • 実際には「dotenv が指定した値が上書きされる」のではなく、「システム環境変数が存在したら dotenv が上書きしないように処理している」ワケだが…
  3. Dockerfile 中に書いた ENV 命令
  4. --env-file オプション
  5. --env オプション

dotenv による注入を抜きにすれば、ENV 命令 < --env-file--env の順で上書きされることを覚えておけば良い。

全体的には、「イメージ内に含まれるモノ」<「コマンド実行時の指定」な順だが、その中でも --env-file--env は、より「その場限り感が強い方」の値を優先されるワケだ。

コレで覚えましたし。

15Stepで習得 Dockerから入るKubernetes

15Stepで習得 Dockerから入るKubernetes

  • 作者:高良真穂
  • 発売日: 2019/12/06
  • メディア: Kindle版