アプリケーションに環境変数を強制的に読み取らせずにDockerコンテナーをバインドする方法

Docker、まだ誰も聞いていない場合-コンテナ仮想化を管理するためのオープンソースフレームワーク。 速く、快適で、思慮深く、ファッショナブルです。 実際、彼は、サーバー構成の管理、アプリケーションの構築、サーバーコードの実行、依存関係の管理などの高貴な作業でゲームのルールを変更します。

Dockerが推奨するアーキテクチャは、それぞれが単一のコマンドを実行する分離されたコンテナーです。 これらのコンテナは、互いを見つける方法のみを知っている必要があります。つまり、コンテナのfqdnとポート、またはipとポート、つまり外部サービスに関する情報のみを知る必要があります。

このような座標をDockerで実行されているプロセスの内部に伝える推奨される方法は、環境変数です。 NODE_ENVは適用されないこのアプローチの典型的な例は、Railsフレームワークで採用されているDATABASE_URLまたはNODE_ENVフレームワークで採用されているNODE_ENVです。

そして、コンテナ内のアプリケーションがデータベースを便利かつ制約なく見つけることを可能にする環境変数があります。 ただし、このためには、アプリケーションを作成する人がそれについて知っている必要があります。 環境変数を使用したアプリケーション構成は適切で正しいものですが、アプリケーションの記述が不十分な場合があり、何らかの方法で実行する必要があります。

Docker、環境変数およびリンク


2つのコンテナーをリンクし、Dockerリンクメカニズムを提供する場合、Dockerが役立ちます。 それらについての詳細はDockerのサイトのマニュアルで読むことができますが、要するに次のようになります:

  1. 起動時にコンテナに名前を付けdocker run -d --name db training/postgresdocker run -d --name db training/postgres 。 これで、 dbという名前のこのコンテナを参照できます。
  2. 2番目のコンテナーを開始し、最初のコンテナーに関連付けdocker run -d -P --name web --link db:db training/webapp python app.py この行で最も興味深いのは、-- --link name:aliasです。 nameはコンテナの名前、 aliasはこのコンテナがランナーに知られる名前です。
  3. これにより、2つの結果がもたらされます:最初に、 dbコンテナーを指すwebコンテナーに一連の環境変数が表示され、次に、データベースコンテナーを開始したipを指すwebコンテナーの/etc/hostsコンテナーにエイリアスdbが表示されます。 webコンテナで使用できる環境変数のセットは次のとおりです。


 DB_NAME=/web/db DB_PORT=tcp://172.17.0.5:5432 DB_PORT_5432_TCP=tcp://172.17.0.5:5432 DB_PORT_5432_TCP_PROTO=tcp DB_PORT_5432_TCP_PORT=5432 DB_PORT_5432_TCP_ADDR=172.17.0.5 


そして、アプリケーションがそのような変数を読む準備ができていない場合、 socat consoleユーティリティが助けになります。

socat


socatは、Unixポート転送ユーティリティです。 アイデアは、その助けを借りて、コンテナ内のアプリケーションの印象を作成することです。たとえば、開発者のコ​​ンピュータで発生するように、データベースが同じホストとその標準ポートの同じコンテナで実行されているという印象です。 socat 、すべての低レベルUnixと同様、非常に軽量であり、コンテナのメインプロセスに負担をかけません。

リンク機構がコンテナにスローする環境変数を詳しく見てみましょう。 特に興味深いのは、 DB_PORT_5432_TCP=tcp://172.17.0.5:5432です。 この変数には、必要なすべてのデータが含まれます:localhost( DB_5432_TCP 5432)でリッスンするポートと、データベース自体の座標(172.17.0.5:5432)。

このような変数は、データベース、 Redisおよび補助サービスの各送信リンクのコンテナーにスローされます。

次のようにコマンドをラップするスクリプトを作成します。目的の環境変数のリストをスキャンし、それぞれに対してsocatを実行し、転送されたコマンドを実行して制御を与えます。 スクリプトが終了すると、すべてのsocatプロセスが完了するはずです。

スクリプト


標準ヘッダー。 set -eは、最初のエラーでスクリプトを完了するようシェルに指示します。つまり、通常のプログラマーの動作が必要です。

 #!/bin/bash set -e 

追加のsocatプロセスを生成するため、それらを監視して、後で完了し、完了するまで待機できるようにする必要があります。

 store_pid() { pids=("${pids[@]}" "$1") } 

これで、記憶できる子プロセスを生成する関数を作成できます。

 start_command() { echo "Running $1" bash -c "$1" & pid="$!" store_pid "$pid" } start_commands() { while read cmd; do start_command "$cmd" done } 

アイデアは(_,_,_)環境変数のセットからタプル(_,_,_)_TCP 、それらをsocat起動コマンドのセットにsocatです。

 to_link_tuple() { sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/\1,\2,\3/' } to_socat_call() { sed 's/\(.*\),\(.*\),\(.*\)/socat -ls TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3/' } env | grep '_TCP=' | to_link_tuple | sort | uniq | to_socat_call | start_commands 

envは環境変数のリストを表示し、 grepは必要なもののみを残し、 to_link_tuple必要なトリプルを引き出しsort | uniq sort | uniq 1つのサービスに対して2つのsocatの起動sort | uniq防ぎますto_socat_callは必要なコマンドをすでに作成します。

また、メインプロセスが完了したときにsocat子プロセスを完了したいと考えました。 これを行うには、 SIGTERM送信します。

 onexit() { echo Exiting echo sending SIGTERM to all processes kill ${pids[*]} &>/dev/null } trap onexit EXIT 

execコマンドでメインプロセスを開始します。 その後、制御が彼に転送され、彼のSTDOUTが表示され、彼はSTDIN信号を受信しSTDIN

 exec "$*" 

スクリプト全体を1つのピースで表示できます。

それで何?


このスクリプトをコンテナー(たとえば、 /run/links.sh / /run/links.sh入れ、次のように/run/links.shを開始します。

 $ docker run -d -P --name web --link db:db training/webapp /run/links.sh python app.py 

出来上がり! ポート5432 127.0.0.1のコンテナでは、postgresが利用可能になります。

エントリーポイント


スクリプトについて覚えておく必要がないように、DockerfileのENTRYPOINTディレクティブを使用して 、イメージをエントリポイントに設定できます。 これにより、このようなイメージで起動されたコマンドは、最初にこのエントリポイントの形式のプレフィックスで補完されるという事実につながります。

Dockerfile追加します。

 ADD ./links.sh /run/links.sh ENTRYPOINT ["/run/links.sh"] 

繰り返しますが、コンテナはコマンドを渡すだけで起動でき、関連付けられたコンテナのサービスがローカルホストで実行されているかのようにアプリケーションに表示されることを確認します。

そして、画像へのアクセスがない場合はどうなりますか?


上記に関連して、興味深い問題があります。画像内にアクセスがない場合に、サービスの同じ便利なプロキシを作成する方法は? つまり、彼らはイメージを与えて、内部にsocatがあることを誓いますが、スクリプトはそこになく、添付することはできません。 しかし、起動チームをarbitrarily意的に複雑にすることができます。 ラッパーを内部にスローする方法は?

コンテナ内のホストファイルシステムの一部を転送する機能が役立ちます。 つまり、たとえば、ホストファイルシステムに/usr/local/docker_binフォルダーを/usr/local/docker_bin 、そこにlinks.shて、次のようにコンテナーを起動できます。

 $ docker run -d -P \ --name web \ --link db:db training/webapp \ -v /usr/local/docker_bin:/run:ro \ /run/links.sh python app.py 

その結果、 /usr/local/docker_bin配置したスクリプトは、コンテナー内で実行可能になります。

roフラグを使用したことに注意してください。これは、コンテナが/runフォルダーに書き込むことを許可しません。

別の方法は、イメージから継承し、そこにファイルを追加するだけです。

合計


socatと良い単語を使用すると、良い単語を単独で使用するよりもはるかに便利な方法でコンテナ間で通信できます。

あとがきの代わりに


気配りのある洗練された読者であれば、 アンバサダードライブラリが原則として同じことを行っていることに気付くでしょう。 そして、この読者は絶対に正しいでしょう。 システムを動作させるだけのユーザーは、おそらく既成の実績のあるソリューションを使用することを好むでしょうが、Habrはそのようなユーザーの間ではあまり人気がありません。 だからこそ、このオプスが生まれました。これは、良いジョークのように、明白なことを伝えるだけでなく、教えています。

ご清聴ありがとうございました。

Source: https://habr.com/ru/post/J260053/


All Articles