Docker run の起動時に任意コードを実行後 bash や ash を終了しない

この記事は、 Docker Advent Calendar 2022 の 23日目の記事だ。
空いていたので埋めちゃうよ。

この記事では、 bash や ash で任意のコードの実行後、ターミナルを終了せずに入力待ちにする方法について紹介する。
特に、 docker run の実行後に、その環境を維持したまま入力待ちにすることを考える。

例えば、 Windows コマンドプロンプトや PowerShell であれば、 CMD /K *** オプションや、 -NoExit -Command *** オプションで実現できるような内容だ。

本来なら、 docker build にてその任意コードの実行後の内容をイメージにするべきだが、 わざわざ build するまでもないとか、 build できない事情などもあるかもしれない。

ということで、 bash の場合と、 alpine などで使われる BusyBox ash それぞれについて、 docker run 実行時に任意コード実行後、ターミナルの入力待ちにする方法を紹介する。

bash の場合

bash の場合、 --rcfile オプションにて、起動時に実行するコマンドを指定できる。

ただし、 --rcfile はファイルを指定する必要があるため、 替わりにプロセス置換で実行コードを与えてやる方法をとる。

user@hostmachine:~$ docker run --rm -it debian:bullseye bash -c "bash --rcfile <(echo 'ls && export '\''FOO=B A R'\'' && MY_TIME=\$(date)')"
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr
root@container:/# echo $FOO : $MY_TIME
B A R : Thu Dec 22 15:00:00 UTC 2022
root@container:/# 

プロセス置換はコンテナ内で実行される必要があるため、一旦 bash -c にてコンテナ内で bash 実行させ、その中で改めて --rcfile オプションを指定した bash を起動する流れとなる。

実際に実行したいコマンドは、 echo で文字列として書き出す。
上記例では、 ls && export 'FOO=B A R' && MY_TIME=$(date) と言う文字列を echo させている。

引用符が二重三重になっていて、エスケープが非常に難しくなっているので注意。

BusyBox ash

alpine 3.15 以降に含まれる BusyBox の ash であれば、意外にもプロセス置換が使える。

しかし、 --rcfile に相当するオプションは残念ながら無い。
替わりに、 ash には ENV という環境変数に記載されたファイルを ash 起動時に実行する機能がある。

これを使おう。

user@hostmachine:~$ docker run --rm -it alpine:3.15 ash -c "ash -c 'export ENV=\$1;ash' -s <(echo 'ls && export '\''FOO=B A R'\'' && MY_TIME=\$(date)')"
bin    etc    lib    mnt    proc   run    srv    tmp    var
dev    home   media  opt    root   sbin   sys    usr
/ # echo $FOO : $MY_TIME
B A R : Thu Dec 22 15:00:00 UTC 2022
/ # 

ash-c のコマンドに対して引数を与える -s オプションを使ってプロセス置換のファイルを与え、 それを $1 経由で ENV 環境変数にセット。 その状態で再度 ash を起動させれば、 bash と同様のことが行える。

ENV 環境変数に直接プロセス置換のファイルを指定せず、わざわざ一旦引数を経由させているのは、 入力を受け付ける ash プロセスが動いている間、 プロセス置換のファイルにアクセス可能にする必要があるためだ。
例えば、 export ENV=<(echo 'command');ash と実行しても、 ash 実行の段階ではプロセス置換のファイルが閉じられているので、コマンドは実行されない。

もうちょっと複雑で実用的な例

エスケープが何重にもなっていてややこしいが、書き方さえ気をつければ基本的にどんな内容でも実行可能だ。

apt パッケージマネージャーのリポジトリを書き換えた状態で、 bash を起動する方法 (Ubuntu):

docker run --rm -it ubuntu:22.04 bash -c "bash --rcfile <(echo 'sed -i -E '\''s%^(deb(-src|)\s+)https?://(archive|security)\.ubuntu\.com/ubuntu/%\1http://srv2.ftp.ne.jp/Linux/packages/ubuntu/archive/%'\'' /etc/apt/sources.list && apt update && FooBar=`date -uIs`')"

apk パッケージマネージャーのリポジトリを書き換えた状態で、 ash を起動する方法 (Alpine):

docker run --rm -it alpine:3.15 ash -c "ash -c 'export ENV=\$1;ash' -s <(echo 'sed -i -E '\''s%^https?://dl-cdn\.alpinelinux\.org/alpine/%https://ftp.udx.icscoe.jp/Linux/alpine/%'\'' /etc/apk/repositories && apk update && FooBar=`date -uIs`')"

参考: https://stackoverflow.com/questions/74094552/how-not-to-terminate-after-carried-out-commands-in-bash

プロキシ環境下で Docker Desktop から Rancher Desktop への移行する

この記事は、 Docker Advent Calendar 2022 の 22日目の記事だ。

Windows 上や mac OS 上で、手軽に Docker や Kubernetes の環境を構築するための、 Docker Desktop というツールが存在する。

この Docker Desktop は、 2021年8月31日以降、営利企業での利用は基本的に有料サブスクリプションが必須 となった。
更に、 2022年10月27日以降、 Docker Team プランにて 100人のユーザー数上限が設けられ 、それ以上の場合は Docker Business プランの契約が必要となっている。

まぁ、営利企業ならそれぐらい払えよって話なんだが、のっぴきならん理由でなかなかそうもいかない諸兄も少なくないだろう。

正直、 Business プランで Docker Desktop 以外の機能を全く使わない(ないし使えない)場合、 $21/user/month はだいぶ高い。

ということで、代替?になるかわからんが、主に Windows での実行をスコープに、 同一コンセプトの Rancher Desktop への移行を紹介してみよう。


Rancher Desktop について

Rancher Desktop は、 Docker Desktop と同様 PC の仮想環境上にコンテナデーモンを起動させ、 デスクトップから手軽に Docker CLI (with Moby/dockerd) や nerdctl (with containerd) を利用できるようにするツールだ。

かつての SUSE Linux の開発元、 現在の openSUSE プロジェクトの主要スポンサーとなっている SUSE 社 が開発している、 Rancher ソリューションの一環として、 オープンソースで開発されているプロジェクトである。

WSL2 上に dockerd や containerd デーモンを立ち上げ、 他の WSL2 ディストリビューションや ホストOS から Docker CLI や nerdctl が使えるようになっている。

Windows の Docker CLI からも、 WSL 内の Docker CLI からも、 Docker outside of Docker (DooD) が問題なくできるなど、基本的な機能はしっかりしている。

Docker Desktop と比較すると、以下のような利点がある。

  • 無料で利用できる
  • nerdctl にも対応している
  • Kubernetes のバージョンが選べる

一方で、使っているといくつかの問題が目につく。 (1.7.0 現在)

  • プロキシ環境下で イメージの pull させる公式手順がない
    • 回避手順は後述する
  • WSL 内からの CLI の実行にいくつか難がある
    • docker CLI の実行が、 2~3 テンポ遅い
    • docker run--interactive ( -i ) オプションを付けないと、標準出力がホスト側に表示されない

また、問題とまでは言わないまでも、いくつか注意点もある。

  • 付属の docker-compose が、 docker compose コマンドと同等の Compose V2 である
  • Hyper-V の仮想化に依存するため、 VPN 環境下と相性が悪い
    • 後述

Docker Desktop から Rancher Desktop への移行

Windows 版に於ける、 Docker Desktop から Rancher Desktop への移行手順をまとめておく。

1. Docker Desktop を削除

公式の FAQ では、「Docker Desktop を Rancher Desktop と一緒にインストールできる(同時に実行できない)」と書かれてはいる。
しかし、 クライアントの Docker CLI コマンドやその設定フォルダ (%USERPROFILE%\.docker\) の設定内容が衝突して問題が発生するので、 Docker Desktop を削除しておいたほうが良い。

Q: Can I have Docker Desktop installed alongside Rancher Desktop?

A: Yes, but they cannot be run at the same time as both Rancher Desktop and Docker Desktop use the same Docker socket (/var/run/docker.sock). Before starting one, be sure to stop the other first.

その手順としては、以下の通り。

  1. Docker Desktop のアンインストール
  2. %USERPROFILE%\.docker フォルダの削除(ないし、リネーム)
  3. PC の再起動

3. Rancher Desktop のインストール

Rancher Desktop 本体のインストール。

Docker Desktop を動かしていたなら問題ないと思うが、 WSL2 を動かせるだけの仮想化支援機能を持った CPU が必要だ。

  1. https://rancherdesktop.io/ から、 Windows 向けインストーラーをダウンロード
  2. インストーラーを起動し、ライセンス (Apache License, Version 2.0) に同意してインストール
    • システムにインストールするか、ユーザーごとにインストールするか選べるが、 基本的にはシステムにインストールさせる。
    • 予め WSL2 が有効にされた状態でないと、 WSL2 の有効化のために何度か OS の再起動が必要になる。
  3. 初回起動時に、 Kubernetes の要否と コンテナランタイムを選択する。
    dockerd に変更しておこう。
  4. Rancher Desktop の設定で、 WSL2 ディストリビューション内からの docker コマンドの実行を有効にしておく
  5. プロキシ環境下の場合、以下の設定を行う

    • Docker daemon 側でイメージを pull する際のプロキシーを有効にするため、 PowerShell で以下のコマンドを実行し、 Rancher Desktop を再起動させる。

      'echo ''# proxy configuration'' >> /etc/rc.conf; echo ''rc_env_allow="http_proxy http_proxy no_proxy"'' >> /etc/rc.conf; echo ''export http_proxy="http://proxy.example.com:8080/"'' >> /etc/rc.conf; echo ''export https_proxy="http://proxy.example.com:8080/"'' >> /etc/rc.conf;' | wsl --distribution rancher-desktop --exec /bin/ash --stdin
      • : not found というエラーが出るが、気にしない。
      • Rancher Desktop のバージョンアップで、 docker daemon イメージの更新が入った場合、この設定をやり直す必要がある。
    • Docker CLI で イメージのビルドやコンテナ実行時のプロキシーを有効にするため、 Docker クライアントの設定 の例に従い、 %USERPROFILE%\.docker/config.json と、 各 WSL2 ディストリビューション毎の ~/.docker/config.json に proxy の設定を追記する。
      例:

      {
        "proxies":
        {
          "default":
          {
            "httpProxy": "http://proxy.example.com:8080",
            "httpsProxy": "http://proxy.example.com:8080",
            "noProxy": "*.test.example.com,.example2.com,127.0.0.0/8"
          }
        }
      }

Rancher Desktop を起動してしばらく待てば、 Windows ホストや、 WSL2 内から docker コマンドが利用できるようになる。

トラブルシューティング

プロキシ環境下の pull 絡みの操作で connection reset by peer のエラー

pull が絡む操作で以下のようなエラーが出る場合

username@pcname:~$ docker pull alpine:3.16
Error response from daemon: Get "https://registry-1.docker.io/v2/": read tcp 172.25.205.147:57296->34.205.13.154:443: read: connection reset by peer

Rancher Desktop のバージョンアップで、 docker daemon 側のプロキシの設定がリセットされているかもしれない。

上述の 『Docker daemon 側でイメージを pull する際のプロキシーを有効』 の設定をやり直す。

pull 絡みの操作で credentials のエラー

pull が絡む操作で以下のようなエラーが出る場合

error getting credentials - err: exit status 127, out: ``

Windows 側の場合、 %USERPROFILE%.docker\config.json に "credsStore": "wincred" を付け加える

@@ -1,4 +1,5 @@
 {
+  "credsStore": "wincred",
   "proxies": {
     "default": {

WSL 側の場合、 ~/.docker/config.json に "credsStore": "pass" を付け加えてみる

@@ -1,4 +1,5 @@
 {
+  "credsStore": "pass",
   "proxies": {
     "default": {

WSL 上で docker.sock の権限エラー

/var/run/docker.sock がパーミッションエラーになってしまう場合

dial unix /var/run/docker.sock: connect: permission denied

グループの設定とパーミッションを振ってから、 WSL のシェルにログインし直す。
ref: Q: How do I fix permission denied errors when trying to use Docker on WSL?

$ sudo groupadd docker
$ sudo adduser $USER docker
$ sudo chown root:docker /var/run/docker.sock
$ sudo chmod u=rw,g=rw,o= /var/run/docker.sock

WSL 上で実行するとコンテナ内の標準出力が表示されない

WSL ではなぜか、 docker run に --interactive ( -i ) オプションを付けないと、標準出力がホスト側に表示されない。

$ docker run --rm alpine ls -la
$ docker run --rm -i alpine ls -la
total 64
drwxr-xr-x    1 root     root          4096 Dec 22 17:31 .
drwxr-xr-x    1 root     root          4096 Dec 22 17:31 ..
-rwxr-xr-x    1 root     root             0 Dec 22 17:31 .dockerenv
drwxr-xr-x    2 root     root          4096 Nov 22 13:06 bin
drwxr-xr-x    5 root     root           340 Dec 22 17:31 dev
drwxr-xr-x    1 root     root          4096 Dec 22 17:31 etc
drwxr-xr-x    2 root     root          4096 Nov 22 13:06 home
[...]

Windows 側からだと問題なく表示される。

VPN 下で通信できない

Hyper-V の仮想化に依存するため、一部の VPN 環境下と相性が悪い。
回避方法は以下の記事を参照。

docker コマンド実行時に docker daemon is not running エラー

Rancher Desktop を起動して居るのにもかかわらず、以下のようなエラーが出る場合。

error during connect: This error may indicate that the docker daemon is not running.:

とりあえずしばらく待って再試行してみよう。
Rancher Desktop UI 上で起動が完了したように見えてから、実際に docker コマンドが使えるようになるまで 十数秒~数十秒 かかることが多い。

置換ができない/複数ある場合に sed の終了コード0以外にする

本記事は、 シェルスクリプト Advent Calendar 2021 の 4日目 の記事だ。
そして、 且つ docker Advent Calendar 2021 4日目 の記事でもある。

どちらのカレンダーもまだまだスッカスカなので、禁じ手で埋めにかかってしまった。


Docker 公式イメージ などをベースにして、カスタムしてイメージをビルドして使おうとした際、 なるべくなら /etc/apt/apt.conf.d/ 等のように、設定用のファイルを追加して、ツール側がいい感じにマージして利用してくれるのが望ましい。
しかし、 場合によってはやむを得ず、既存のファイルを sed コマンドなどで編集せざるを得ないこともあるだろう。

カスタムイメージの Dockerfile をビルドする際に、当初は意図通り書き換えられていても、イメージが更新された結果、イメージのリビルド時にファイルの書き換えが意図しない結果となってしまう場合がある。 1

通常、 sed コマンドは、置換が発生してもしなくても、 終了コード 0 で終了する。
このため、書き換えの成否にかかわらず、 docker build 時にエラーにならないため、コンテナ実行時に初めて置換が意図しない結果だったことに気づくことがある。

そこで、sed コマンドの書き換えで適切なパターンが見つからなかった場合に 0以外の終了コードを返し、ビルド時にエラーとする方法を考える。

続きを読む

Samba が動く Docker Desktop のコンテナで、ボリューム内のファイルを見る

本記事は、 docker Advent Calendar 2021 2日目 の記事だ。
昨日は、 @subretu氏 の DockerfileにおけるCMDとRUNの挙動 だった。


docker のコンテナやボリュームの中身を見る際に、 ホスト PC から SMB ファイル共有で参照できると都合が良い。

と言うことで以前、以下のような記事を作成したが、 Docker Desktop では利用できない問題があった。

今回は、それを解消する手段について紹介したい。

先に断っておくが、私自身でいくつかの PC で試したところ、上手くいく PC とダメな PC がハッキリと分かれてしまい、現時点ではどのような条件だとダメなのかハッキリわかっていない。

続きを読む

VPN に繋ぐと WSL2 や Hyper-V VM でネットワークに繋がらなくなる問題を解消する

OpenVPN や Cisco AnyConnect, GlobalProtect 等といった VPN に接続した際、 Hyper-V 仮想マシン内からや、 WSL2 のディストリビューション内、 Windows Sandbox 内、 WSL2 ベースの Docker コンテナ内 等々、 Hyper-V 系の技術を使った仮想環境から、 PC 外のネットワークにアクセスしようとすると、 以下のようなエラーが発生して失敗する。

$ # curl 利用時の例
curl: (6) Could not resolve host: example.com
curl: (5) Could not resolve proxy: proxy.example.com

$ # apt で更新しようとした場合の例
W: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/focal/InRelease  Temporary failure resolving 'archive.ubuntu.com'
W: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/focal/InRelease  Temporary failure resolving 'proxy.example.com'

エラーの内容からわかるとおり、アクセス先やプロキシーのドメイン名を DNS で解決できなくなっている。

このような問題が発生することは以前から知っていたのだが、このご時世で VPN 使うことが増えてきて、いい加減鬱陶しくなってきたので、なんとかしようと思う。

解決方法

とりあえず、まずは解決方法から。

続きを読む

AWX をインストールした後の Server Error を解決したかった話

この記事は、 Ansible AWX をインストールしたときに、 Server Error に なったりならなかったりする 問題に対処したときのポエムだ。

はじめに断っておくが、最終的に AWX 8.0.0 で解消しているっぽいものの、 原因や正確な条件などは不明なままである。
また後述するが、 (タイトルに反して)おそらく Ansible AWX の問題ではなく、 postgres:9.6 の docker イメージの問題ではないかと思われる。

発生した問題の状況

続きを読む