CLOVER🍀

That was when it all began.

Dockerコンテナ上で、ログファイルにしかログを書かないソフトウェアに標準出力・標準エラー出力を使わせるようにする

Dockerのログまわりのドキュメントを読んでいて、ちょっと気になったところがありまして。

View logs for a container or service | Docker Documentation

The official nginx image creates a symbolic link from /var/log/nginx/access.log to /dev/stdout, and creates another symbolic link from /var/log/nginx/error.log to /dev/stderr, overwriting the log files and causing logs to be sent to the relevant special device instead. See the Dockerfile.

「docker container logs」で表示可能にできるようなコンテナ上で動作するプロセスが出力する内容は、標準出力、または
標準エラー出力にその内容を書き出す必要があります。

このため、たとえばApacheのオフィシャルイメージの場合、Apacheのプロセスをフォアグラウンドで実行するようにして
回避しています。

DockerHub / httpd

https://github.com/docker-library/httpd/blob/a426e299988c48f4937dfb4a9a67ac479ca011e1/2.4/Dockerfile#L148

CMD ["httpd-foreground"]

https://github.com/docker-library/httpd/blob/a426e299988c48f4937dfb4a9a67ac479ca011e1/2.4/httpd-foreground#L7

exec httpd -DFOREGROUND

Nginxの場合はこのような回避策がないため、アクセスログおよびエラーログのファイルを、それぞれ標準出力(/dev/stdout)、
標準エラー出力(/dev/stderr)のシンボリックリンクとして作成することで、回避しているようです。

https://github.com/nginxinc/docker-nginx/blob/8921999083def7ba43a06fabd5f80e4406651353/mainline/jessie/Dockerfile#L21-L23

# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log

なるほど、覚えておきましょう。

というわけで、ちょっと試してみます。

Apache Tomcatを題材にして、こんなDockerfileを用意。
Dockerfile

FROM openjdk:8

RUN apt-get update -y && \
    apt-get install -y wget sudo && \
    apt-get clean && \
    cd /opt && \
    wget -q https://www-us.apache.org/dist/tomcat/tomcat-9/v9.0.14/bin/apache-tomcat-9.0.14.tar.gz && \
    tar xf apache-tomcat-9.0.14.tar.gz && \
    rm apache-tomcat-9.0.14.tar.gz && \
    mv apache-tomcat-9.0.14 apache-tomcat && \
    ln -sf /dev/stdout /opt/apache-tomcat/logs/catalina.out

ENTRYPOINT /opt/apache-tomcat/bin/startup.sh && sleep 120

catalina.outを、/dev/stdoutのリンクにしています。

    ln -sf /dev/stdout /opt/apache-tomcat/logs/catalina.out

起動はstartup.shで、ですが、そのままだと終了してしまうのでsleepを…。

ENTRYPOINT /opt/apache-tomcat/bin/startup.sh && sleep 120

ちなみに、Apache Tomcatのオフィシャルイメージでは、こんなことをせずにcatalina.shを実行しています。

https://github.com/docker-library/tomcat/blob/f58a6b4236cfe10672c9505aab5024100c9e084d/9.0/jre8/Dockerfile#L167

CMD ["catalina.sh", "run"]

これで、ログの内容が標準出力に書き出されます。

…話を戻して、Dockerイメージをビルド。

$ docker build -t kazuhira/tomcat:9 .

起動。

$ docker container run --rm --name tomcat kazuhira/tomcat:9

すると、catalina.outに出力される内容がずらずらと標準出力に現れます。

Tomcat started.
02-Jan-2019 08:27:06.576 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name:   Apache Tomcat/9.0.14
02-Jan-2019 08:27:06.577 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built:          Dec 6 2018 21:13:53 UTC
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 9.0.14.0
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name:               Linux
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version:            4.15.0-43-generic
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture:          amd64
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home:             /usr/lib/jvm/java-8-openjdk-amd64/jre
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version:           1.8.0_181-8u181-b13-2~deb9u1-b13
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor:            Oracle Corporation
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE:         /opt/apache-tomcat
02-Jan-2019 08:27:06.579 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME:         /opt/apache-tomcat
02-Jan-2019 08:27:06.579 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/opt/apache-tomcat/conf/logging.properties
02-Jan-2019 08:27:06.579 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
02-Jan-2019 08:27:06.579 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djdk.tls.ephemeralDHKeySize=2048
02-Jan-2019 08:27:06.579 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.protocol.handler.pkgs=org.apache.catalina.webresources
02-Jan-2019 08:27:06.579 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dorg.apache.catalina.security.SecurityListener.UMASK=0027
02-Jan-2019 08:27:06.579 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dignore.endorsed.dirs=
02-Jan-2019 08:27:06.580 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.base=/opt/apache-tomcat
02-Jan-2019 08:27:06.580 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Dcatalina.home=/opt/apache-tomcat
02-Jan-2019 08:27:06.580 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/opt/apache-tomcat/temp
02-Jan-2019 08:27:06.580 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/usr/java/packages/lib/amd64:/usr/lib/x86_64-linux-gnu/jni:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/usr/lib/jni:/lib:/usr/lib]
02-Jan-2019 08:27:06.659 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["http-nio-8080"]
02-Jan-2019 08:27:06.715 INFO [main] org.apache.coyote.AbstractProtocol.init Initializing ProtocolHandler ["ajp-nio-8009"]
02-Jan-2019 08:27:06.717 INFO [main] org.apache.catalina.startup.Catalina.load Server initialization in [438] milliseconds
02-Jan-2019 08:27:06.742 INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]

〜省略〜

「docker container logs」でも、確認することができます。

$ docker logs tomcat | head -n 10
Tomcat started.
02-Jan-2019 08:27:06.576 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name:   Apache Tomcat/9.0.14
02-Jan-2019 08:27:06.577 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built:          Dec 6 2018 21:13:53 UTC
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version number: 9.0.14.0
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name:               Linux
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version:            4.15.0-43-generic
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture:          amd64
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home:             /usr/lib/jvm/java-8-openjdk-amd64/jre
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version:           1.8.0_181-8u181-b13-2~deb9u1-b13
02-Jan-2019 08:27:06.578 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor:            Oracle Corporation

なお、先ほどのDockerfileのシンボリックリンクを作成している箇所をコメントアウトしてビルドして

    mv apache-tomcat-9.0.14 apache-tomcat
#    ln -sf /dev/stdout /opt/apache-tomcat/logs/catalina.out

起動すると、ログは以下の1行だけになります。

$ docker container run --rm --name tomcat kazuhira/tomcat:9
Tomcat started.

というわけで、catalina.outの内容を標準出力に書き出すことができるようになったことが、確認できました、と。

気になること

シンボリックリンクにしたファイルに書き出すのは、ログインユーザーと実行ユーザーが異なると、うまくいかないようですね?

シンボリックリンクの作成をDockerfile内で作成した別ユーザーで行い、かつ起動も別ユーザーですると、こんな感じに…。

touch: cannot touch '/opt/apache-tomcat/logs/catalina.out': Permission denied
Tomcat started.
/opt/apache-tomcat/bin/catalina.sh: 1: eval: cannot create /opt/apache-tomcat/logs/catalina.out: Permission denied

この時のDockerfile。tomcatというユーザーを作成し、sudoで一時的にユーザーを変更して実行しています。

FROM openjdk:8

RUN adduser --disabled-login --gecos '' tomcat

RUN apt-get update -y && \
    apt-get install -y wget sudo && \
    apt-get clean && \
    cd /opt && \
    wget -q https://www-us.apache.org/dist/tomcat/tomcat-9/v9.0.14/bin/apache-tomcat-9.0.14.tar.gz && \
    tar xf apache-tomcat-9.0.14.tar.gz && \
    rm apache-tomcat-9.0.14.tar.gz && \
    mv apache-tomcat-9.0.14 apache-tomcat && \
    chown -R tomcat.tomcat apache-tomcat && \
    sudo -u tomcat ln -sf /dev/stdout /opt/apache-tomcat/logs/catalina.out

ENTRYPOINT sudo -u tomcat /opt/apache-tomcat/bin/startup.sh && sleep 120

「sudo -u tomcat」で

sudo -u tomcat /opt/apache-tomcat/bin/startup.sh

ちなみに、こうするとうまくいきます。

FROM openjdk:8

RUN adduser --disabled-login --gecos '' tomcat

RUN apt-get update -y && \
    apt-get install -y wget sudo && \
    apt-get clean && \
    cd /opt && \
    wget -q https://www-us.apache.org/dist/tomcat/tomcat-9/v9.0.14/bin/apache-tomcat-9.0.14.tar.gz && \
    tar xf apache-tomcat-9.0.14.tar.gz && \
    rm apache-tomcat-9.0.14.tar.gz && \
    mv apache-tomcat-9.0.14 apache-tomcat && \
    chown -R tomcat.tomcat apache-tomcat && \
    sudo -u tomcat ln -sf /dev/stdout /opt/apache-tomcat/logs/catalina.out

USER tomcat

ENTRYPOINT /opt/apache-tomcat/bin/startup.sh && sleep 120

「USER tomcat」でユーザーを指定するようにしました。

この時の、/dev/stdoutの差。

rootで実行し、sudoで切り替えようとした場合。

# ls -l /dev/stdout
lrwxrwxrwx 1 root root 15 Jan  2 08:45 /dev/stdout -> /proc/self/fd/1

# ls -l /proc/self/fd/1
lrwx------ 1 root root 64 Jan  2 08:45 /proc/self/fd/1 -> /dev/pts/0

「USER tomcat」と指定した場合。

$ ls -l /dev/stdout
lrwxrwxrwx 1 root root 15 Jan  2 08:47 /dev/stdout -> /proc/self/fd/1

$ ls -l /proc/self/fd/1
lrwx------ 1 tomcat tomcat 64 Jan  2 08:47 /proc/self/fd/1 -> /dev/pts/0

1番目のFile Descriptorのオーナーが異なりますね。

参考)

/dev/stderr(/dev/std{in,out}も)は使うべきではない - Qiita