Podmanでpodにnginx-proxy-acmeを入れるとエラーになる件

nginx-proxyでSSL/TLS化するのに、普通はdocker-composeするので特に問題は無いのだけれど、これをpodmanでやろうとするとなかなかに難しいという話。環境としてはRHEL9.3のpodman-4.6.1-7.el9_3.x86_64。事前にpodman.socketを有効化してあり、docker.sockはシンボリックリンクになっている。

# systemctl enable --now podman.socket
# ll /var/run/docker.sock 
lrwxrwxrwx. 1 root root 23  1月 25 10:19 /var/run/docker.sock -> /run/podman/podman.sock

どういうエラーか

”Error: can’t get my container ID !”がログに出力される。

# podman pod logs app-pod
6f8df1ce5fe1 Info: running acme-companion version v2.2.10-5-g013005a
6f8df1ce5fe1 2024/01/28 01:05:16, Error: can't get my container ID !
6f8df1ce5fe1 Warning: can't check if '/etc/nginx/certs' is a mounted volume without self container ID.
6f8df1ce5fe1 2024/01/28 01:05:16, Error: can't get my container ID !
6f8df1ce5fe1 Warning: can't check if '/etc/nginx/vhost.d' is a mounted volume without self container ID.
6f8df1ce5fe1 2024/01/28 01:05:16, Error: can't get my container ID !
fab5b0cf58eb Info: running nginx-proxy version 1.4.0-71-gd46881f
6f8df1ce5fe1 Warning: can't check if '/etc/acme.sh' is a mounted volume without self container ID.
6f8df1ce5fe1 2024/01/28 01:05:16, Error: can't get my container ID !

podに入れない場合のhostname

手順としては以下。hostnameはコンテナIDの上位12桁が設定される。

# podman run \
    --detach \
    --name nginx-proxy \
    --volume nginx-certs:/etc/nginx/certs \
    --volume nginx-vhost:/etc/nginx/vhost.d \
    --volume nginx-html:/usr/share/nginx/html \
    --volume /var/run/docker.sock:/tmp/docker.sock:ro \
    docker.io/nginxproxy/nginx-proxy
e2fb454034da541ce00b0dc8f4d3471359458dca9326914328091c0353fb5421

# podman run \
    --detach \
    --name nginx-proxy-acme \
    --volumes-from nginx-proxy \
    --volume nginx-certs:/etc/nginx/certs:rw \
    --volume acme:/etc/acme.sh \
    --volume /var/run/docker.sock:/var/run/docker.sock:ro \
    -e NGINX_PROXY_CONTAINER=nginx-proxy \
    -e DEFAULT_EMAIL=rio@rio.st \
    docker.io/nginxproxy/acme-companion
1cb364360e42af3dbd911ec5249f7b577692f4e4773e3e4cb50cf45e5327ab87

# podman exec -it 1cb hostname
1cb364360e42

podに入れる場合のhostname

手順は以下。pod createして、podman runに–podオプションを追加して、podにコンテナを入れる。hostnameはpodの名前が設定される。

# podman pod create -p 80:80 -p 443:443 --name app-pod
e32b3fd7c38052a7780d340d6bd622071d940ad14dd6c1c466da81ae65758217

# podman run \
    --detach \
    --pod app-pod \
    --name nginx-proxy \
    --volume nginx-certs:/etc/nginx/certs \
    --volume nginx-vhost:/etc/nginx/vhost.d \
    --volume nginx-html:/usr/share/nginx/html \
    --volume /var/run/docker.sock:/tmp/docker.sock:ro \
    docker.io/nginxproxy/nginx-proxy
74aeab47336c14387e224cbac083ad9447135cc5273382e4f493ddeecf7bce74

# podman run \
    --detach \
    --pod app-pod \
    --name nginx-proxy-acme \
    --volumes-from nginx-proxy \
    --volume nginx-certs:/etc/nginx/certs:rw \
    --volume acme:/etc/acme.sh \
    --volume /var/run/docker.sock:/var/run/docker.sock:ro \
    -e NGINX_PROXY_CONTAINER=nginx-proxy \
    -e DEFAULT_EMAIL=rio@rio.st \
    docker.io/nginxproxy/acme-companion
0ba98878fa3e6c1110a30cb0fe619083846e9cfdb5162ca0c8e94a17682ffe93

# podman exec -it 0ba hostname
app-pod

エラーになるフロー

podに入れた場合にエラーになるのは、nginxproxy/acme-companionの/app/functions.shの以下のコード。

/proc/1/cpusetと/proc/self/cgroupはcgroups v1でしか利用できず、Docker APIを叩いている。

function get_self_cid {                                                                                                 
    local self_cid=""                                                                                                   
                                                                                                                        
    # Try the /proc files methods first then resort to the Docker API.                                                  
    if [[ -f /proc/1/cpuset ]]; then                                                                                    
        self_cid="$(grep -Eo '[[:alnum:]]{64}' /proc/1/cpuset)"                                                         
    fi                                                                                                                  
    if [[ ( ${#self_cid} != 64 ) && ( -f /proc/self/cgroup ) ]]; then                                                   
        self_cid="$(grep -Eo -m 1 '[[:alnum:]]{64}' /proc/self/cgroup)"                                                 
    fi                                                                                                                  
    if [[ ( ${#self_cid} != 64 ) ]]; then                                                                               
        self_cid="$(docker_api "/containers/$(hostname)/json" | jq -r '.Id')"                                           
    fi                                                                                                                  
                                                                                                                        
    # If it's not 64 characters long, then it's probably not a container ID.                                            
    if [[ ${#self_cid} == 64 ]]; then                                                                                   
        echo "$self_cid"                                                                                                
    else                                                                                                                
        echo "$(date "+%Y/%m/%d %T"), Error: can't get my container ID !" >&2                                           
        return 1                                                                                                        
    fi                                                                                                                  
}                                                                                                                       
                                                                                                                        
## Docker API                                                                                                           
function docker_api {                                                                                                   
    local scheme                                                                                                        
    local curl_opts=(-s)                                                                                                
    local method=${2:-GET}                                                                                              
    # data to POST                                                                                                      
    if [[ -n "${3:-}" ]]; then                                                                                          
        curl_opts+=(-d "$3")                                                                                            
    fi                                                                                                                  
    if [[ -z "$DOCKER_HOST" ]];then                                                                                     
        echo "Error DOCKER_HOST variable not set" >&2                                                                   
        return 1                                                                                                        
    fi                                                                                                                  
    if [[ $DOCKER_HOST == unix://* ]]; then                                                                             
        curl_opts+=(--unix-socket "${DOCKER_HOST#unix://}")                                                             
        scheme='http://localhost'                                                                                       
    else                                                                                                                
        scheme="http://${DOCKER_HOST#*://}"                                                                             
    fi                                                                                                                  
    [[ $method = "POST" ]] && curl_opts+=(-H 'Content-Type: application/json')                                          
    curl "${curl_opts[@]}" -X "${method}" "${scheme}$1"                                                                 
} 

podを作成しない場合、もし/proc/1/cpusetと/proc/self/cgroupからコンテナIDを取得できなくても、functions.shのget_self_cid()はdocker_api()は以下を実行することでコンテナIDを取得できるため、エラーにならない。

curl -s --unix-socket /tmp/docker.sock -X GET http://localhost/containers/1cb364360e42/json

podを作成する場合、実行されるcurlは以下のようになる。

curl -s --unix-socket /tmp/docker.sock -X GET http://localhost/containers/app-pod/json

すると、docker.sock、つまりpodman.sockは以下のエラーを返す。

# curl -s --unix-socket /tmp/docker.sock -X GET http://localhost/containers/app-pod/json
{"cause":"no such container","message":"\"app-pod\" is a pod, not a container: no such container","response":404}

「そんなコンテナは無ぇ」。それはそう…。

hostnameにコンテナID(の一部)が設定されていることを期待しているnginxproxy/acme-companionがよろしくないっぽい。というのは、OCIのコンテナランタイムの仕様ではhostnameはUTS namespaceとなっているから。

ではどうするか

2つの方法が考えられる。1つは/proc/self/mountinfoから取得すること。”cgroups v2″のコメントから3行がそれ。busyboxのgrepではPerl拡張が使えないのでとりあえずこう書いたが、他に良い書き方があれば知りたい…。

function get_self_cid {
    local self_cid=""

    # Try the /proc files methods first then resort to the Docker API.
    if [[ -f /proc/1/cpuset ]]; then
        self_cid="$(grep -Eo '[[:alnum:]]{64}' /proc/1/cpuset)"
    fi
    if [[ ( ${#self_cid} != 64 ) && ( -f /proc/self/cgroup ) ]]; then
        self_cid="$(grep -Eo -m 1 '[[:alnum:]]{64}' /proc/self/cgroup)"
    fi
    # cgroups v2
    if [[ ( ${#self_cid} != 64 ) && ( -f /proc/self/mountinfo ) ]]; then
        self_cid="$(grep '/userdata/hostname' /proc/self/mountinfo | grep -Eo '[[:alnum:]]{64}')"
    fi
    if [[ ( ${#self_cid} != 64 ) ]]; then
        self_cid="$(docker_api "/containers/$(hostname)/json" | jq -r '.Id')"
    fi

    # If it's not 64 characters long, then it's probably not a container ID.
    if [[ ${#self_cid} == 64 ]]; then
        echo "$self_cid"
    else
        echo "$(date "+%Y/%m/%d %T"), Error: can't get my container ID !" >&2
        return 1
    fi
}

もう1つは、podman run –cidfile /tmp/acme-companion.cidなどとして、runした後にマウントして内部で読み取ること。ただしrunが完了しないとコンテナIDは確定しないため、runに-vでマウントするように指示してもfile not foundになってしまう。上記get_self_cid()の実行をwaitさせるようにしないとならないため、いまいちである。

とりまPRはしておいたが、acme-companionにマージされるか否かは分からんw