Docker ユーティリティ ツール gosu および su-exec の実践に関するクラウド ネイティブの詳細な分析

1. ボリューム権限の問題

  • Docker では、ホスト ディレクトリをボリュームとして使用するコンテナにマウントする必要がある場合、ファイルのアクセス許可の問題がよく発生します。よくある現象は、コンテナーにパスへの書き込み権限がないため、そこにあるサービスであらゆる種類の奇妙な問題が発生することです。
  • この種の問題の原因は、コンテナーの内側と外側の UID が異なることです。たとえば、ホスト上で現在 docker を使用しているユーザーの UID は 1000 (これはデフォルトの最初のユーザーの UID です) コンテナー内の UID が 2000 の場合、ホストによって作成されたディレクトリはそのホストの所有者ではありません。コンテナであり、デフォルトでは書き込むことができません。
  • さらに、マウントされたディレクトリがマウント前にホスト上に存在しないという別の状況もあります。 Docker はまず root 権限でディレクトリを作成し、それからマウントします。その結果、ホストとコンテナの UID が両方とも 1000 であっても、書き込み権限はありません。この現象は初期化中にのみ発生しますが、初心者や退屈なベテランを混乱させるには十分です。
  • Dockerfile でボリュームのアクセス許可を適切に構成できないのはなぜですか? Dockerfile はイメージの記述であり、ボリュームはコンテナのコンテンツであるためです。 Dockerfile で行われたアクセス許可の構成は、ボリューム以外には有効ですが、ボリュームには有効ではありません。基本的に、ホストによってボリュームにマウントされたディレクトリはホストに属します。 Dockerfileはdockerのビルド時に実行され、dockerの実行時にボリュームが生成されます。
  • 実際、Docker がボリューム パスを自動的に作成するときは、それをコンテナ内のフォアグラウンド プロセスの user:group に自動的に変更する必要があります。しかし、Docker には現在のところそのような仕組みがないため、私たちのようなユーザーは別の方法を見つけるしかありません。
  • 一般的な一時的な解決策は、権限を手動で変更することです。 chown を使用して所有者をコンテナ内のユーザーの UID に変更するか、chmod 777 を使用してすべてのユーザーに共通にするかのいずれかです。これらは確かに長期的なソリューションとしては優れたものではなく、デプロイを容易にするという Docker の本来の目的に反しています。
  • 現時点では、Dockerfile の ENTRYPOINT をカスタマイズするのが最善の解決策と思われます。

2. エントリーポイント

  • ENTRYPOINT には次の重要なポイントがあります。
    • ENTRYPOINT は、イメージのデフォルトのエントリ コマンドを指定します。このエントリ コマンドは、コンテナの起動時にルート コマンドとして実行され、他のすべての受信値はコマンドのパラメータとして使用されます。
    • ENTRYPOINT の値は、docker run --entrypoint によって上書きできます。
    • Dockerfile 内の最後の ENTRYPOINT ディレクティブのみが機能します。
  • ENTRYPOINT を指定すると、CMD の意味が変わり、コマンドを直接実行するのではなく、CMD の内容がパラメータとして ENTRYPOINT 命令に渡されます。つまり、実際に実行すると「」となります。
  • そのため、dockerfileのENTRYPOINTにエントリースクリプトentrypoint.shまたはdocker-entrypoint.shを記述します。 ENTRYPOINT を使用して、コンテナーの実行中に、ボリュームが正しくマウントされているディレクトリの権限を変更したり、通常のプログラム プロセスを実行するために通常のユーザーに切り替えたりするなど、一部の操作を実行します。

三、gosu 和 su-exec

  • gosu の github ウェアハウスのアドレス:
https://github.com/tianon/gosu
  • 使用法:
$ gosu
Usage: ./gosu user-spec command [args]
   eg: ./gosu tianon bash
       ./gosu nobody:root bash -c 'whoami && id'
       ./gosu 1000:1 id

./gosu version: 1.1 (go1.3.1 on linux/amd64; gc)
  • 簡単な例:
$ docker run -it --rm ubuntu:trusty su -c 'exec ps aux'
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  46636  2688 ?        Ss+  02:22   0:00 su -c exec ps a
root         6  0.0  0.0  15576  2220 ?        Rs   02:22   0:00 ps aux
$ docker run -it --rm ubuntu:trusty sudo ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  3.0  0.0  46020  3144 ?        Ss+  02:22   0:00 sudo ps aux
root         7  0.0  0.0  15576  2172 ?        R+   02:22   0:00 ps aux
$ docker run -it --rm -v $PWD/gosu-amd64:/usr/local/bin/gosu:ro ubuntu:trusty gosu root ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   7140   768 ?        Rs+  02:22   0:00 ps aux
  • suでもsudoでも、ps auxコマンド実行時のPID番号は1ではありません。コンテナ内でも可能ですが、これは良い解決策ではなく、コンテナ内の PID=1 のプロセスはアプリケーションそのものです。したがって、gosu コマンドを使用して、コマンドを実行するユーザーを切り替えることができます。
  • debianの場合のインストール方法は以下の通りです。
    • Debian 9 (「Debian ストレッチ」) 以降:
RUN set -eux; \
 apt-get update; \
 apt-get install -y gosu; \
 rm -rf /var/lib/apt/lists/*; \
# verify that the binary works
 gosu nobody true
    • 古い Debian バージョン (または新しい gosu バージョン):
ENV GOSU_VERSION 1.16
RUN set -eux; \
# save list of currently installed packages for later so we can clean up
 savedAptMark="$(apt-mark showmanual)"; \
 apt-get update; \
 apt-get install -y --no-install-recommends ca-certificates wget; \
 if ! command -v gpg; then \
  apt-get install -y --no-install-recommends gnupg2 dirmngr; \
 elif gpg --version | grep -q '^gpg (GnuPG) 1\.'; then \
# "This package provides support for HKPS keyservers." (GnuPG 1.x only)
  apt-get install -y --no-install-recommends gnupg-curl; \
 fi; \
 rm -rf /var/lib/apt/lists/*; \
 \
 dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
 wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
 wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
 \
# verify the signature
 export GNUPGHOME="$(mktemp -d)"; \
 gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
 gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
 command -v gpgconf && gpgconf --kill all || :; \
 rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
 \
# clean up fetch dependencies
 apt-mark auto '.*' > /dev/null; \
 [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \
 apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
 \
 chmod +x /usr/local/bin/gosu; \
# verify that the binary works
 gosu --version; \
 gosu nobody true
    • Alpine (3.7 以降) の場合: Alpine を使用する場合は、su-exec (apk add --no-cache su-exec) をチェックしてみるのも価値があるかもしれません。これは、バージョン 0.2 以降、わずかなファイル サイズで gosu と完全に互換性があります。 1つ:
ENV GOSU_VERSION 1.16
RUN set -eux; \
 \
 apk add --no-cache --virtual .gosu-deps \
  ca-certificates \
  dpkg \
  gnupg \
 ; \
 \
 dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
 wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
 wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
 \
# verify the signature
 export GNUPGHOME="$(mktemp -d)"; \
 gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
 gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
 command -v gpgconf && gpgconf --kill all || :; \
 rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
 \
# clean up fetch dependencies
 apk del --no-network .gosu-deps; \
 \
 chmod +x /usr/local/bin/gosu; \
# verify that the binary works
 gosu --version; \
 gosu nobody true

4. エントリポイントスクリプトファイル

  • 脚本例 1:
#!/bin/sh
set -e
ls ${
    
    LOG_PATH} > /dev/null 2>&1 || mkdir -p ${
    
    LOG_PATH}
chown -R www-data ${
    
    LOG_PATH}
if [ $# -gt 0 ];then
    #su ${
    
    USERNAME} -c "exec $@"
    exec su-exec www-data $@
else
    #su ${
    
    USERNAME} -c "exec uwsgi --ini uwsgi.ini --http=0.0.0.0:${DJANGO_PORT}"
    exec su-exec www-data uwsgi --ini uwsgi.ini --http=0.0.0.0:${
    
    DJANGO_PORT}
fi
  • スクリプトの説明:
    • set -e: コマンドの実行が失敗した場合は、失敗によるその後の影響を避けるために、スクリプトの実行を続行せずにスクリプトを終了する必要があります。これにより、操作が失敗しても実行が継続されるという問題を回避できます。
    • exec: システム コール exec は元のプロセスを新しいプロセスに置き換えますが、プロセスの PID は変更されないため、コンテナのメイン プログラム PID=1 が保証されます。
  • 脚本例 2:
#!/bin/sh
set -e
if [ "$1" = 'uwsgi' -a "$(id -u)" = '0' ]
then
    ls ${
    
    LOG_PATH} > /dev/null 2>&1 || mkdir -p ${
    
    LOG_PATH}
    chown -R www-data ${
    
    LOG_PATH}
    exec su-exec www-data "$0" "$@"
fi
exec "$@"
  • スクリプトの説明:
    • 現在のユーザーが root の場合は、LOG_PATH ディレクトリの権限を作成して変更し、www-data の ID に切り替え、残りのパラメータを取得して、docker-entrypoint.sh ファイルを再度実行します ("0" 表示dockえーentryp ot.s h 自体、"@" は残りのパラメータを表します)。 www-data ユーザーが実行する必要があるコードがこのスクリプトの他の場所にある場合は、それも実行できます。
    • 再度スクリプトを実行すると、root ユーザーではなくなっているため、exec "$@" が直接実行され、パラメータ、つまり CMD で定義されたスクリプトが直接実行されます。
  • Dockerfile に docker-entrypoint.sh スクリプトを追加します。x の実行権限に注意する必要があります。そうしないと、実行権限がありません。
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
  • この docker-entrypoint.sh スクリプトを使用すると、root ユーザーが作成したボリューム マウント ディレクトリを docker が初期化する場合でも、コンテナーの実行時にディレクトリのアクセス許可を強制的に必要なアクセス許可に変更できます。このようにして、コンテナ内で通常のユーザーとしてプログラムを実行し、通常の権限でディレクトリにファイルを書き込むことができます。

おすすめ

転載: blog.csdn.net/Forever_wj/article/details/135022541