Dive Deep a bug for nerdctl attach
- nerdctl containerd
Description と背景
下記の issue に関して、SDE も色々調査してくれていたが、自分なり調査してまとめる。
事の発端は issue に詳しく書いてあります。
Summary
-d
を指定して起動したコンテナに対して、nerdctl attach
を実行した際のエラーは下記の箇所で発生している。
if err := syscall.Mkfifo(fn, uint32(perm&os.ModePerm)); err != nil && !os.IsExist(err) {
return nil, fmt.Errorf("error creating fifo %v: %w", fn, err)
}
エラーになる場合、fn
に binary:///Users/haytok/workspace/nerdctl/_output/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db5
が入っており、これに対してパイプ (fifo
) を作成することができず、標準出力用のファイルが作成されないため、エラーが発生する。
この fn
は nerdctl run -d
や nerdctl run -dt
を実行してコンテナを作成する際に作成される Task
の ioCreator
に前述の binary
が指定されている。
そのため、-d
および -dt
を指定してコンテナが作成される処理における ioCreator
を作成する際に fifo
を指定するようにすると標準出力用のパイプが作成され、この変更を加えると、-d
および -dt
で起動したコンテナに nerdctl attach
を実行することができる。
いまだにわからんこと
fn
にbinary:///Users/haytok/workspace/nerdctl/_output/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db5
が設定される条件-d
および-dt
を指定してnerdctl run
を実行する際にioCreator
にfifo
が指定するように設定するので問題ないのか
開発環境 (読み飛ばしても可)
開発環境は基本的に自前の M3 MacBook Air を使用している。 なので、CPU 周りの動作の影響を受けたくないので、ホストのファイルをマウントする形で Lima で VM を構築し、VM からホストのファイルを操作して開発するようにする。
なお、nerdctl ディレクトリにおける go.mod
は下記のように修正した。
haytok ~/workspace/nerdctl [main]
> git diff go.mod
diff --git a/go.mod b/go.mod
index 5f985ca7..a832c46a 100644
--- a/go.mod
+++ b/go.mod
@@ -142,3 +142,6 @@ require (
google.golang.org/protobuf v1.33.0 // indirect
lukechampine.com/blake3 v1.1.7 // indirect
)
+
+replace github.com/containerd/containerd => ../containerd
+replace github.com/containerd/fifo => ../fifo
こうすると、nerdctl 内で外部パッケージ (containerd や fifo) のソースコードを変更した場合、その変更が nerdctl のビルドしたバイナリに梱包される。
また、containerd と fifo のブランチは nerdctl 内のパッケージのバージョンと合わせる。
haytok ~/workspace/fifo [(HEAD detached at v1.1.0)]
> git branch
* (HEAD detached at v1.1.0)
main
haytok ~/workspace/containerd [(HEAD detached at v1.7.14)]
> git branch
* (HEAD detached at v1.7.14)
main
ディレクトリ構成は下記である。
haytok ~/workspace
> pwd
/Users/haytok/workspace
haytok ~/workspace
> tree -L 1
.
├── bigmo
├── common-tests
├── containerd
├── fifo
├── finch
├── haytok
├── haytok.github.io
├── jobs
├── nerdctl
└── verification
11 directories, 0 files
調査すること
- nerdctl attach コマンドを実行した際に発生するエラーを突き止め、そのエラーが発生する条件を調査する。
sudo nerdctl run --name test -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
を実行した際動作の確認と workaround を調査する。- nerdctl 内で使用されているパッケージ fifo の動作を確認する。
調査 1 (nerdctl attach コマンドを実行した際に発生するエラーを突き止め、そのエラーが発生する条件を調査する。)
実際に発生したエラーは下記になります。
[haytok@lima-finch workspace]$ sudo nerdctl run --name test -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
54f605cda9b8184a5d16a543ba482dfaa1a90d3e71d9903c19b667bd202f5b18
[haytok@lima-finch workspace]$ sudo nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
54f605cda9b8 docker.io/library/alpine:latest "sh -c while true; d…" 3 seconds ago Up test
[haytok@lima-finch workspace]$ sudo nerdctl attach test
FATA[0000] failed to attach to the container: failed to open stdout fifo: error creating fifo binary:///usr/local/bin/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db59: no such file or directory
ここで発生したエラーメッセージ failed to attach to the container: failed to open stdout fifo: error creating fifo binary:///usr/local/bin/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db59: no such file or directory
を元に、エラーが発生する過程を追います。
Check error message failed to attach to the container
[haytok@lima-default nerdctl]$ grep -rn "failed to attach to the container" . -B2 -A1
grep: ./_output/nerdctl: binary file matches
--
./pkg/cmd/container/attach.go-96- task, err = container.Task(ctx, cio.NewAttach(opt))
./pkg/cmd/container/attach.go-97- if err != nil {
./pkg/cmd/container/attach.go:98: return fmt.Errorf("failed to attach to the container: %w", err)
./pkg/cmd/container/attach.go-99- }
container.Task()
の実装を追うために、containerd のリポジトリを確認する。
[haytok@lima-default containerd]$ grep -rn ") Task(" . -A2
./container.go:190:func (c *container) Task(ctx context.Context, attach cio.Attach) (Task, error) {
./container.go-191- return c.loadTask(ctx, attach)
./container.go-192-}
Check loadTask()
[haytok@lima-default containerd]$ grep -rn ") loadTask(" . -A29
./container.go:390:func (c *container) loadTask(ctx context.Context, ioAttach cio.Attach) (Task, error) {
./container.go-391- fmt.Printf("0: In loadTask\n")
./container.go-392- response, err := c.client.TaskService().Get(ctx, &tasks.GetRequest{
./container.go-393- ContainerID: c.id,
./container.go-394- })
./container.go-395- if err != nil {
./container.go-396- err = errdefs.FromGRPC(err)
./container.go-397- if errdefs.IsNotFound(err) {
./container.go-398- return nil, fmt.Errorf("no running task found: %w", err)
./container.go-399- }
./container.go-400- return nil, err
./container.go-401- }
./container.go-402- var i cio.IO
./container.go-403- if ioAttach != nil && response.Process.Status != tasktypes.Status_UNKNOWN {
./container.go-404- // Do not attach IO for task in unknown state, because there
./container.go-405- // are no fifo paths anyway.
./container.go-406- if i, err = attachExistingIO(response, ioAttach); err != nil {
./container.go-407- fmt.Printf("1: In loadTask\n")
./container.go-408- return nil, err
./container.go-409- }
./container.go-410- }
./container.go-411- t := &task{
./container.go-412- client: c.client,
./container.go-413- io: i,
./container.go-414- id: response.Process.ID,
./container.go-415- pid: response.Process.Pid,
./container.go-416- c: c,
./container.go-417- }
./container.go-418- return t, nil
./container.go-419-}
Check attachExistingIO()
[haytok@lima-default containerd]$ grep -rn "func attachExistingIO" . -A3
./container.go:426:func attachExistingIO(response *tasks.GetResponse, ioAttach cio.Attach) (cio.IO, error) {
./container.go-427- fifoSet := loadFifos(response)
./container.go-428- return ioAttach(fifoSet)
./container.go-429-}
attachExistingIO()
関数の返り値のエラーによって failed to attach to the container: ...
のエラーが発生しているはずだが、エラーを返しうるのは ioAttach()
しかない。
ioAttach()
は container.Task(ctx, cio.NewAttach(opt))
を呼び出す際の第二引数が体なので、その実装を追っていく。
./pkg/cmd/container/attach.go-96- task, err = container.Task(ctx, cio.NewAttach(opt))
引き続き、どの処理が error を返しうるかの観点から実装を追っていく。
Check NewAttach()
[haytok@lima-default containerd]$ grep -rn "func NewAttach(" . -A20
./cio/io.go:160:func NewAttach(opts ...Opt) Attach {
./cio/io.go-161- streams := &Streams{}
./cio/io.go-162- for _, opt := range opts {
./cio/io.go-163- opt(streams)
./cio/io.go-164- }
./cio/io.go-165- return func(fifos *FIFOSet) (IO, error) {
./cio/io.go-166- if fifos == nil {
./cio/io.go-167- return nil, fmt.Errorf("cannot attach, missing fifos")
./cio/io.go-168- }
./cio/io.go-169- if streams.Stdin == nil {
./cio/io.go-170- fifos.Stdin = ""
./cio/io.go-171- }
./cio/io.go-172- if streams.Stdout == nil {
./cio/io.go-173- fifos.Stdout = ""
./cio/io.go-174- }
./cio/io.go-175- if streams.Stderr == nil {
./cio/io.go-176- fifos.Stderr = ""
./cio/io.go-177- }
./cio/io.go-178- return copyIO(fifos, streams)
./cio/io.go-179- }
./cio/io.go-180-}
Check copyIO
[haytok@lima-default containerd]$ grep -rn "func copyIO(" . -A6
./cio/io_windows.go:44:func copyIO(fifos *FIFOSet, ioset *Streams) (_ *cio, retErr error) {
./cio/io_windows.go-45- cios := &cio{config: fifos.Config}
./cio/io_windows.go-46-
./cio/io_windows.go-47- defer func() {
./cio/io_windows.go-48- if retErr != nil {
./cio/io_windows.go-49- _ = cios.Close()
./cio/io_windows.go-50- }
--
./cio/io_unix.go:56:func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
./cio/io_unix.go-57- var ctx, cancel = context.WithCancel(context.Background())
./cio/io_unix.go-58- pipes, err := openFifos(ctx, fifos)
./cio/io_unix.go-59- if err != nil {
./cio/io_unix.go-60- cancel()
./cio/io_unix.go-61- return nil, err
./cio/io_unix.go-62- }
windows の実装に関心はないので、unix の方の実装を追う。
Check openFifos()
[haytok@lima-default containerd]$ grep -rn "func openFifos(" . -A23
./cio/io_unix.go:113:func openFifos(ctx context.Context, fifos *FIFOSet) (f pipes, retErr error) {
./cio/io_unix.go-114- defer func() {
./cio/io_unix.go-115- if retErr != nil {
./cio/io_unix.go-116- fifos.Close()
./cio/io_unix.go-117- }
./cio/io_unix.go-118- }()
./cio/io_unix.go-119-
./cio/io_unix.go-120- if fifos.Stdin != "" {
./cio/io_unix.go-121- if f.Stdin, retErr = fifo.OpenFifo(ctx, fifos.Stdin, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {
./cio/io_unix.go-122- return f, fmt.Errorf("failed to open stdin fifo: %w", retErr)
./cio/io_unix.go-123- }
./cio/io_unix.go-124- defer func() {
./cio/io_unix.go-125- if retErr != nil && f.Stdin != nil {
./cio/io_unix.go-126- f.Stdin.Close()
./cio/io_unix.go-127- }
./cio/io_unix.go-128- }()
./cio/io_unix.go-129- }
./cio/io_unix.go-130- if fifos.Stdout != "" {
./cio/io_unix.go-131- if f.Stdout, retErr = fifo.OpenFifo(ctx, fifos.Stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {
./cio/io_unix.go-132- fmt.Printf("In openFifos, fifos.Stdout -> %s\n", fifos.Stdout)
./cio/io_unix.go-133- fmt.Printf("In openFifos, fifos.Stdin -> %s\n", fifos.Stdin)
./cio/io_unix.go-134- fmt.Printf("In openFifos, fifos.Stderr -> %s \n", fifos.Stderr)
./cio/io_unix.go-135- return f, fmt.Errorf("failed to open stdout fifo: %w", retErr)
./cio/io_unix.go-136- }
Check OpenFifo()
in fifo repository
[haytok@lima-default fifo]$ grep -rn "func OpenFifo(" *.go -A9
fifo.go:72:func OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
fifo.go-73- fmt.Printf("0: In OpenFifo\n")
fifo.go-74- fifo, err := openFifo(ctx, fn, flag, perm)
fifo.go-75- if fifo == nil {
fifo.go-76- // Do not return a non-nil ReadWriteCloser((*fifo)(nil)) value
fifo.go-77- // as that can confuse callers.
fifo.go-78- return nil, err
fifo.go-79- }
fifo.go-80- return fifo, err
fifo.go-81-}
Check openFifo()
in fifo source code not nerdctl source code
[haytok@lima-default fifo]$ grep -rn "func openFifo(" *.go -A10
fifo.go:83:func openFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (*fifo, error) {
fifo.go-84- fmt.Printf("0: In openFifo, fn: %s\n", fn)
fifo.go-85- if _, err := os.Stat(fn); err != nil {
fifo.go-86- fmt.Printf("1: In openFifo\n")
fifo.go-87- if os.IsNotExist(err) && flag&syscall.O_CREAT != 0 {
fifo.go-88- fmt.Printf("2: In openFifo\n")
fifo.go-89- if err := syscall.Mkfifo(fn, uint32(perm&os.ModePerm)); err != nil && !os.IsExist(err) {
fifo.go-90- return nil, fmt.Errorf("error creating fifo %v: %w", fn, err)
fifo.go-91- }
fifo.go-92- } else {
fifo.go-93- return nil, err
これでエラーの末端までたどり着いた。
おそらく、syscall.Mkfifo(fn, uint32(perm&os.ModePerm))
の処理にこけてエラーが発生した。
fn
には何が入っているかと、syscall.Mkfifo()
を確認する。
上述の fmt.Printf()
の Debug code を挿入した nerdctl を build して動作を確認する。
[haytok@lima-default nerdctl]$ alias | grep jake
alias jake='make -j $(nproc)'
[haytok@lima-default nerdctl]$ jake
GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w -X github.com/containerd/nerdctl/v2/pkg/version.Version=78b66fdc.m -X github.com/containerd/nerdctl/v2/pkg/version.Revision=78b66fdcde0eeafb95fdf9915dc4ccbaef51021a.m" -o /Users/haytok/workspace/nerdctl/_output/nerdctl github.com/containerd/nerdctl/v2/cmd/nerdctl
[haytok@lima-default nerdctl]$ sudo ./_output/nerdctl run --name test -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
3209cc5c61e2adebd56cfa3a020e312a364d4faa934daa1c69252cdaed3d2ec6
[haytok@lima-default nerdctl]$ sudo ./_output/nerdctl attach test
0: In loadTask
0: In OpenFifo
0: In openFifo, fn: binary:///Users/haytok/workspace/nerdctl/_output/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db59
1: In openFifo
2: In openFifo
In openFifos, fifos.Stdout -> binary:///Users/haytok/workspace/nerdctl/_output/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db59
In openFifos, fifos.Stdin ->
In openFifos, fifos.Stderr -> binary:///Users/haytok/workspace/nerdctl/_output/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db59
1: In loadTask
FATA[0000] failed to attach to the container: failed to open stdout fifo: error creating fifo binary:///Users/haytok/workspace/nerdctl/_output/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db59: no such file or directory
Debug メッセージ等を加味すると、fn (fifos.Stdout
) に binary:///Users/haytok/workspace/nerdctl/_output/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db59
が設定されていることによって、エラーになっている。
なお、-d コマンドでバックグラウンドでコンテナを起動し、その際に標準出力に吐き出すように実行したコマンド sh -c while true; do echo /'Hello, world/'; sleep 1; done
の結果は nerdctl logs <コンテナ名>
から確認できる log ファイルには吐き出されいていることが明らかになった。
エラーに関しては、nerdctl run -d ...
でコンテナを起動しているので、fifos.Stdout
に標準出力のファイルが指定されていないのではないかと考えられる。
一旦、比較するために nerdctl run --name hoge --rm -it alpine sh
を実行した際に、fifo.Stdout
に設定されるファイルを確認してみる。
[haytok@lima-default nerdctl]$ sudo _output/nerdctl run --name hoge --rm -it alpine sh
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/345658729/f26c33cf34999edf45ab3306aa074b7496671cb8bfcd9a6d870623cbeec11be4-stdin
1: In openFifo
2: In openFifo
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/345658729/f26c33cf34999edf45ab3306aa074b7496671cb8bfcd9a6d870623cbeec11be4-stdout
1: In openFifo
2: In openFifo
/ #
-d
を指定せず -it
オプションを指定してフォアグラウンドでコンテナを起動すると標準出力と標準入力にはそれ用のファイルが作成された。
[haytok@lima-default containerd]$ sudo ../nerdctl/_output/nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3209cc5c61e2 docker.io/library/alpine:latest "sh -c while true; d…" 22 hours ago Up test
f26c33cf3499 docker.io/library/alpine:latest "sh" 2 minutes ago Up hoge
[haytok@lima-default containerd]$ sudo ../nerdctl/_output/nerdctl ps --no-trunc
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3209cc5c61e2adebd56cfa3a020e312a364d4faa934daa1c69252cdaed3d2ec6 docker.io/library/alpine:latest "sh -c while true; do echo /'Hello, world/'; sleep 1; done" 22 hours ago Up test
f26c33cf34999edf45ab3306aa074b7496671cb8bfcd9a6d870623cbeec11be4 docker.io/library/alpine:latest "sh" 2 minutes ago Up hoge
345658729
の値は何かわからんけど、その配下にコンテナ hoge に関連するデータが配置されている。
[haytok@lima-default containerd]$ sudo ls -la /run/containerd/fifo/345658729/
total 0
drwx------. 2 root root 80 Mar 27 22:03 .
drwx------. 11 root root 220 Mar 27 22:03 ..
prwx------. 1 root root 0 Mar 27 22:09 f26c33cf34999edf45ab3306aa074b7496671cb8bfcd9a6d870623cbeec11be4-stdin
prwx------. 1 root root 0 Mar 27 22:09 f26c33cf34999edf45ab3306aa074b7496671cb8bfcd9a6d870623cbeec11be4-stdout
stdout と stdin のためのパイプが作成されている。
一方、-d
でコンテナを起動する前後での /run/containerd/fifo
配下のディレクトリ構成の違いを確認したところ、差はなかった。
[haytok@lima-default containerd]$ sudo ls /run/containerd/fifo
118231144 2164213081 2376111114 2554841498 313929042 345658729 3541429126 890640757 938856586
# run sudo ./_output/nerdctl run --name testtest -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
[haytok@lima-default containerd]$ sudo ls /run/containerd/fifo
118231144 2164213081 2376111114 2554841498 313929042 345658729 3541429126 890640757 938856586
なので、-d
の時は fifo のパイプのファイルが作成されない処理を追ってみる。
つまり、nerdctl run
の処理を追う。
ディレクトリ構成的に nerdctl/cmd/nerdctl/container_run.go
内の処理が nerdctl run
時に呼び出されるはず。
haytok ~/workspace/nerdctl [main]
> git diff cmd/nerdctl/container_run.go
diff --git a/cmd/nerdctl/container_run.go b/cmd/nerdctl/container_run.go
index 077332d4..a059b504 100644
--- a/cmd/nerdctl/container_run.go
+++ b/cmd/nerdctl/container_run.go
@@ -373,6 +373,7 @@ func runAction(cmd *cobra.Command, args []string) error {
}
logURI := lab[labels.LogURI]
detachC := make(chan struct{})
+ fmt.Printf("0: In runAction\n")
task, err := taskutil.NewTask(ctx, client, c, false, createOpt.Interactive, createOpt.TTY, createOpt.Detach,
con, logURI, createOpt.DetachKeys, createOpt.GOptions.Namespace, detachC)
if err != nil {
build して確認したところ、予想通りやった。
[haytok@lima-default nerdctl]$ sudo ./_output/nerdctl run --name testtest -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
04445d3aef8f12697ea975d78cc7ff831a27afa3140107a59ca5006530c23525
[haytok@lima-default nerdctl]$ jake
GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w -X github.com/containerd/nerdctl/v2/pkg/version.Version=78b66fdc.m -X github.com/containerd/nerdctl/v2/pkg/version.Revision=78b66fdcde0eeafb95fdf9915dc4ccbaef51021a.m" -o /Users/haytok/workspace/nerdctl/_output/nerdctl github.com/containerd/nerdctl/v2/cmd/nerdctl
[haytok@lima-default nerdctl]$ sudo _output/nerdctl run --name hoge --rm -it alpine sh
0: In runAction
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/1034591092/80976a1afcadac0ab011154709e64522ee60f068cd46ff0aa3322bc10a72e54f-stdin
1: In openFifo
2: In openFifo
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/1034591092/80976a1afcadac0ab011154709e64522ee60f068cd46ff0aa3322bc10a72e54f-stdout
1: In openFifo
2: In openFifo
/ #
なので、以降は runAction()
内の処理を追っていく。
特に、下記の処理が重要そうなので、NewTask()
の処理を確認する。
task, err := taskutil.NewTask(ctx, client, c, false, createOpt.Interactive, createOpt.TTY, createOpt.Detach,
con, logURI, createOpt.DetachKeys, createOpt.GOptions.Namespace, detachC)
if err != nil {
return err
}
if err := task.Start(ctx); err != nil {
return err
}
Check NewTask()
[haytok@lima-default nerdctl]$ grep -rn " NewTask(" -A120
pkg/taskutil/taskutil.go:42:func NewTask(ctx context.Context, client *containerd.Client, container containerd.Container,
pkg/taskutil/taskutil.go-43- flagA, flagI, flagT, flagD bool, con console.Console, logURI, detachKeys, namespace string, detachC chan<- struct{}) (containerd.Task, error) {
pkg/taskutil/taskutil.go-44- var t containerd.Task
pkg/taskutil/taskutil.go-45- fmt.Printf("0: In, NewTask\n")
pkg/taskutil/taskutil.go-46- closer := func() {
pkg/taskutil/taskutil.go-47- if detachC != nil {
pkg/taskutil/taskutil.go-48- detachC <- struct{}{}
pkg/taskutil/taskutil.go-49- }
pkg/taskutil/taskutil.go-50- // t will be set by container.NewTask at the end of this function.
pkg/taskutil/taskutil.go-51- //
pkg/taskutil/taskutil.go-52- // We cannot use container.Task(ctx, cio.Load) to get the IO here
pkg/taskutil/taskutil.go-53- // because the `cancel` field of the returned `*cio` is nil. [1]
pkg/taskutil/taskutil.go-54- //
pkg/taskutil/taskutil.go-55- // [1] https://github.com/containerd/containerd/blob/8f756bc8c26465bd93e78d9cd42082b66f276e10/cio/io.go#L358-L359
pkg/taskutil/taskutil.go-56- io := t.IO()
pkg/taskutil/taskutil.go-57- if io == nil {
pkg/taskutil/taskutil.go-58- log.G(ctx).Errorf("got a nil io")
pkg/taskutil/taskutil.go-59- return
pkg/taskutil/taskutil.go-60- }
pkg/taskutil/taskutil.go-61- io.Cancel()
pkg/taskutil/taskutil.go-62- }
pkg/taskutil/taskutil.go-63- var ioCreator cio.Creator
pkg/taskutil/taskutil.go-64- if flagA {
pkg/taskutil/taskutil.go-65- fmt.Printf("1: In, NewTask\n")
pkg/taskutil/taskutil.go-66- log.G(ctx).Debug("attaching output instead of using the log-uri")
pkg/taskutil/taskutil.go-67- if flagT {
pkg/taskutil/taskutil.go-68- in, err := consoleutil.NewDetachableStdin(con, detachKeys, closer)
pkg/taskutil/taskutil.go-69- if err != nil {
pkg/taskutil/taskutil.go-70- return nil, err
pkg/taskutil/taskutil.go-71- }
pkg/taskutil/taskutil.go-72- ioCreator = cio.NewCreator(cio.WithStreams(in, con, nil), cio.WithTerminal)
pkg/taskutil/taskutil.go-73- } else {
pkg/taskutil/taskutil.go-74- ioCreator = cio.NewCreator(cio.WithStdio)
pkg/taskutil/taskutil.go-75- }
pkg/taskutil/taskutil.go-76-
pkg/taskutil/taskutil.go-77- } else if flagT && flagD {
pkg/taskutil/taskutil.go-78- fmt.Printf("2: In, NewTask\n")
pkg/taskutil/taskutil.go-79- u, err := url.Parse(logURI)
pkg/taskutil/taskutil.go-80- if err != nil {
pkg/taskutil/taskutil.go-81- return nil, err
pkg/taskutil/taskutil.go-82- }
pkg/taskutil/taskutil.go-83-
pkg/taskutil/taskutil.go-84- var args []string
pkg/taskutil/taskutil.go-85- for k, vs := range u.Query() {
pkg/taskutil/taskutil.go-86- args = append(args, k)
pkg/taskutil/taskutil.go-87- if len(vs) > 0 {
pkg/taskutil/taskutil.go-88- args = append(args, vs[0])
pkg/taskutil/taskutil.go-89- }
pkg/taskutil/taskutil.go-90- }
pkg/taskutil/taskutil.go-91-
pkg/taskutil/taskutil.go-92- // args[0]: _NERDCTL_INTERNAL_LOGGING
pkg/taskutil/taskutil.go-93- // args[1]: /var/lib/nerdctl/1935db59
pkg/taskutil/taskutil.go-94- fmt.Printf("0: In NewTask, args: %#v", args)
pkg/taskutil/taskutil.go-95- if len(args) != 2 {
pkg/taskutil/taskutil.go-96- return nil, errors.New("parse logging path error")
pkg/taskutil/taskutil.go-97- }
pkg/taskutil/taskutil.go-98- ioCreator = cio.TerminalBinaryIO(u.Path, map[string]string{
pkg/taskutil/taskutil.go-99- args[0]: args[1],
pkg/taskutil/taskutil.go-100- })
pkg/taskutil/taskutil.go-101- } else if flagT && !flagD {
pkg/taskutil/taskutil.go-102- fmt.Printf("3: In, NewTask\n")
pkg/taskutil/taskutil.go-103-
pkg/taskutil/taskutil.go-104- if con == nil {
pkg/taskutil/taskutil.go-105- return nil, errors.New("got nil con with flagT=true")
pkg/taskutil/taskutil.go-106- }
pkg/taskutil/taskutil.go-107- var in io.Reader
pkg/taskutil/taskutil.go-108- if flagI {
pkg/taskutil/taskutil.go-109- // FIXME: check IsTerminal on Windows too
pkg/taskutil/taskutil.go-110- if runtime.GOOS != "windows" && !term.IsTerminal(0) {
pkg/taskutil/taskutil.go-111- return nil, errors.New("the input device is not a TTY")
pkg/taskutil/taskutil.go-112- }
pkg/taskutil/taskutil.go-113- var err error
pkg/taskutil/taskutil.go-114- in, err = consoleutil.NewDetachableStdin(con, detachKeys, closer)
pkg/taskutil/taskutil.go-115- if err != nil {
pkg/taskutil/taskutil.go-116- return nil, err
pkg/taskutil/taskutil.go-117- }
pkg/taskutil/taskutil.go-118- }
pkg/taskutil/taskutil.go-119- ioCreator = cioutil.NewContainerIO(namespace, logURI, true, in, os.Stdout, os.Stderr)
pkg/taskutil/taskutil.go-120- } else if flagD {
pkg/taskutil/taskutil.go-121- fmt.Printf("4: In, NewTask\n")
pkg/taskutil/taskutil.go-122- ioCreator = cio.NewCreator(cio.WithStdio)
pkg/taskutil/taskutil.go-123- } else if flagD && logURI != "" {
pkg/taskutil/taskutil.go-124- fmt.Printf("44: In, NewTask, logURI: %s, flagD: %b\n", logURI, flagD)
pkg/taskutil/taskutil.go-125- // sudo ./_output/nerdctl run --name test2 -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
pkg/taskutil/taskutil.go-126- // を実行した時は。この if 文に処理が実施される。
pkg/taskutil/taskutil.go-127-
pkg/taskutil/taskutil.go-128- u, err := url.Parse(logURI)
pkg/taskutil/taskutil.go-129- if err != nil {
pkg/taskutil/taskutil.go-130- return nil, err
pkg/taskutil/taskutil.go-131- }
pkg/taskutil/taskutil.go-132- fmt.Printf("444: In, NewTask, u: %#v\n", u)
pkg/taskutil/taskutil.go-133- ioCreator = cio.LogURI(u)
pkg/taskutil/taskutil.go-134- } else {
pkg/taskutil/taskutil.go-135- fmt.Printf("5: In, NewTask, flagT && !flagD\n")
pkg/taskutil/taskutil.go-136- var in io.Reader
pkg/taskutil/taskutil.go-137- if flagI {
pkg/taskutil/taskutil.go-138- if sv, err := infoutil.ServerSemVer(ctx, client); err != nil {
pkg/taskutil/taskutil.go-139- log.G(ctx).Warn(err)
pkg/taskutil/taskutil.go-140- } else if sv.LessThan(semver.MustParse("1.6.0-0")) {
pkg/taskutil/taskutil.go-141- log.G(ctx).Warnf("`nerdctl (run|exec) -i` without `-t` expects containerd 1.6 or later, got containerd %v", sv)
pkg/taskutil/taskutil.go-142- }
pkg/taskutil/taskutil.go-143- var stdinC io.ReadCloser = &StdinCloser{
pkg/taskutil/taskutil.go-144- Stdin: os.Stdin,
pkg/taskutil/taskutil.go-145- Closer: func() {
pkg/taskutil/taskutil.go-146- if t, err := container.Task(ctx, nil); err != nil {
pkg/taskutil/taskutil.go-147- log.G(ctx).WithError(err).Debugf("failed to get task for StdinCloser")
pkg/taskutil/taskutil.go-148- } else {
pkg/taskutil/taskutil.go-149- t.CloseIO(ctx, containerd.WithStdinCloser)
pkg/taskutil/taskutil.go-150- }
pkg/taskutil/taskutil.go-151- },
pkg/taskutil/taskutil.go-152- }
pkg/taskutil/taskutil.go-153- in = stdinC
pkg/taskutil/taskutil.go-154- }
pkg/taskutil/taskutil.go-155- ioCreator = cioutil.NewContainerIO(namespace, logURI, false, in, os.Stdout, os.Stderr)
pkg/taskutil/taskutil.go-156- }
pkg/taskutil/taskutil.go-157- t, err := container.NewTask(ctx, ioCreator)
pkg/taskutil/taskutil.go-158- if err != nil {
pkg/taskutil/taskutil.go-159- return nil, err
pkg/taskutil/taskutil.go-160- }
pkg/taskutil/taskutil.go-161- return t, nil
pkg/taskutil/taskutil.go-162-}
ここでやっと Shubhranshu153 が言及していた処理が出てきた!!!
Shubhranshu153 の指摘にあるように、下記のコードを足して動作確認してみる。
確認する際は nerdctl を rebuild して、再度コンテナを起動させる。
haytok ~/workspace/nerdctl [main]
> git diff pkg/
diff --git a/pkg/taskutil/taskutil.go b/pkg/taskutil/taskutil.go
index 101452f8..3ca063d1 100644
--- a/pkg/taskutil/taskutil.go
+++ b/pkg/taskutil/taskutil.go
...
@@ -111,13 +117,22 @@ func NewTask(ctx context.Context, client *containerd.Client, container container
}
}
ioCreator = cioutil.NewContainerIO(namespace, logURI, true, in, os.Stdout, os.Stderr)
+ } else if flagD {
+ fmt.Printf("4: In, NewTask\n")
+ ioCreator = cio.NewCreator(cio.WithStdio)
} else if flagD && logURI != "" {
...
そうすると、確かに -d で起動したコンテナに nerdctl attach
することができた。
[haytok@lima-default nerdctl]$ sudo ./_output/nerdctl run --name test -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
0: In runAction
0: In, NewTask
4: In, NewTask
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/3201500946/40589e0394da21a578fc5767b92b3aac8333dea55fdcd0b531e5d772c7f8793b-stdin
1: In openFifo
2: In openFifo
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/3201500946/40589e0394da21a578fc5767b92b3aac8333dea55fdcd0b531e5d772c7f8793b-stdout
1: In openFifo
2: In openFifo
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/3201500946/40589e0394da21a578fc5767b92b3aac8333dea55fdcd0b531e5d772c7f8793b-stderr
1: In openFifo
2: In openFifo
/Hello, world/
40589e0394da21a578fc5767b92b3aac8333dea55fdcd0b531e5d772c7f8793b
[haytok@lima-default nerdctl]$ sudo ./_output/nerdctl attach test
0: In loadTask
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/3201500946/40589e0394da21a578fc5767b92b3aac8333dea55fdcd0b531e5d772c7f8793b-stdin
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/3201500946/40589e0394da21a578fc5767b92b3aac8333dea55fdcd0b531e5d772c7f8793b-stdout
0: In OpenFifo
0: In openFifo, fn: /run/containerd/fifo/3201500946/40589e0394da21a578fc5767b92b3aac8333dea55fdcd0b531e5d772c7f8793b-stderr
/Hello, world/
/Hello, world/
/Hello, world/
[haytok@lima-default nerdctl]$ sudo ./_output/nerdctl ps --no-trunc
0: In loadTask
0: In loadTask
0: In loadTask
0: In loadTask
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
04445d3aef8f12697ea975d78cc7ff831a27afa3140107a59ca5006530c23525 docker.io/library/alpine:latest "sh -c while true; do echo /'Hello, world/'; sleep 1; done" 20 minutes ago Up testtest
a074368ed3103fa9ce8e1b4b2e366bfc85b90a200b8d33b5db9bb8befaebfa0f docker.io/library/alpine:latest "sh -c while true; do echo /'Hello, world/'; sleep 1; done" About a minute ago Up test
[haytok@lima-default containerd]$ sudo tree -L 2 /run/containerd/fifo
/run/containerd/fifo
├── 1034591092
│ ├── 80976a1afcadac0ab011154709e64522ee60f068cd46ff0aa3322bc10a72e54f-stdin
│ └── 80976a1afcadac0ab011154709e64522ee60f068cd46ff0aa3322bc10a72e54f-stdout
├── 118231144
│ ├── 1ae9c69f9ee18e2263455c41cb9162c4608934cb76b3eb65f26ea7cb0a5064ea-stdin
│ └── 1ae9c69f9ee18e2263455c41cb9162c4608934cb76b3eb65f26ea7cb0a5064ea-stdout
├── 2164213081
├── 2376111114
│ ├── 95e8262aedb39d0900968195ec65db649c917f369fcbb68475b163fbf820515d-stdin
│ └── 95e8262aedb39d0900968195ec65db649c917f369fcbb68475b163fbf820515d-stdout
├── 2439977144
│ ├── a074368ed3103fa9ce8e1b4b2e366bfc85b90a200b8d33b5db9bb8befaebfa0f-stderr
│ ├── a074368ed3103fa9ce8e1b4b2e366bfc85b90a200b8d33b5db9bb8befaebfa0f-stdin
│ └── a074368ed3103fa9ce8e1b4b2e366bfc85b90a200b8d33b5db9bb8befaebfa0f-stdout
├── 2554841498
│ ├── b5a6199c3cc4f2409e09e2410d8038d3108ca234f64090108c788f35c9b1f0b5-stdin
│ └── b5a6199c3cc4f2409e09e2410d8038d3108ca234f64090108c788f35c9b1f0b5-stdout
├── 313929042
├── 345658729
│ ├── f26c33cf34999edf45ab3306aa074b7496671cb8bfcd9a6d870623cbeec11be4-stdin
│ └── f26c33cf34999edf45ab3306aa074b7496671cb8bfcd9a6d870623cbeec11be4-stdout
├── 3541429126
│ ├── 838f295c4635acb3a06e4d5a81ed49d84f1dfb0fa532666089477eb6e70ad3d0-stdin
│ └── 838f295c4635acb3a06e4d5a81ed49d84f1dfb0fa532666089477eb6e70ad3d0-stdout
├── 890640757
└── 938856586
├── affc6c7417229c0d15f43eb2d8f0322cdd7737c5953fe5d079b08cd0f1862593-stdin
└── affc6c7417229c0d15f43eb2d8f0322cdd7737c5953fe5d079b08cd0f1862593-stdout
12 directories, 17 files
[haytok@lima-default containerd]$ sudo cat /run/containerd/fifo/2439977144/a074368ed3103fa9ce8e1b4b2e366bfc85b90a200b8d33b5db9bb8befaebfa0f-stdout
/Hello, world/
/Hello, world/
/Hello, world/
...
以上から、nerdctl run
を実行し、-d
オプションのみを指定した時でも stdin / stdout / stderr 用の fifo を作成するようにロジックを変更すると、とりあえず -d
で起動したコンテナに nerdctl attach
を実行することはできる。
調査 2 (sudo nerdctl run --name test -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
を実行した際動作の確認と workaround を調査する。)
コンテナを起動する際のオプションの組み合わせの動作を確認してみる。
logURI に文字列が挿入される状況が不明
// NewTask is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/tasks/tasks_unix.go#L70-L108
func NewTask(ctx context.Context, client *containerd.Client, container containerd.Container,
flagA, flagI, flagT, flagD bool, con console.Console, logURI, detachKeys, namespace string, detachC chan<- struct{}) (containerd.Task, error) {
var t containerd.Task
fmt.Printf("0: In, NewTask\n")
closer := func() {
if detachC != nil {
detachC <- struct{}{}
}
// t will be set by container.NewTask at the end of this function.
//
// We cannot use container.Task(ctx, cio.Load) to get the IO here
// because the `cancel` field of the returned `*cio` is nil. [1]
//
// [1] https://github.com/containerd/containerd/blob/8f756bc8c26465bd93e78d9cd42082b66f276e10/cio/io.go#L358-L359
io := t.IO()
if io == nil {
log.G(ctx).Errorf("got a nil io")
return
}
io.Cancel()
}
var ioCreator cio.Creator
if flagA {
fmt.Printf("1: In, NewTask\n")
log.G(ctx).Debug("attaching output instead of using the log-uri")
if flagT {
in, err := consoleutil.NewDetachableStdin(con, detachKeys, closer)
if err != nil {
return nil, err
}
ioCreator = cio.NewCreator(cio.WithStreams(in, con, nil), cio.WithTerminal)
} else {
ioCreator = cio.NewCreator(cio.WithStdio)
}
} else if flagT && flagD {
fmt.Printf("2: In, NewTask\n")
u, err := url.Parse(logURI)
if err != nil {
return nil, err
}
var args []string
for k, vs := range u.Query() {
args = append(args, k)
if len(vs) > 0 {
args = append(args, vs[0])
}
}
// args[0]: _NERDCTL_INTERNAL_LOGGING
// args[1]: /var/lib/nerdctl/1935db59
fmt.Printf("0: In NewTask, args: %#v\n", args)
if len(args) != 2 {
return nil, errors.New("parse logging path error")
}
ioCreator = cio.TerminalBinaryIO(u.Path, map[string]string{
args[0]: args[1],
})
} else if flagT && !flagD {
fmt.Printf("3: In, NewTask\n")
if con == nil {
return nil, errors.New("got nil con with flagT=true")
}
var in io.Reader
if flagI {
// FIXME: check IsTerminal on Windows too
if runtime.GOOS != "windows" && !term.IsTerminal(0) {
return nil, errors.New("the input device is not a TTY")
}
var err error
in, err = consoleutil.NewDetachableStdin(con, detachKeys, closer)
if err != nil {
return nil, err
}
}
ioCreator = cioutil.NewContainerIO(namespace, logURI, true, in, os.Stdout, os.Stderr)
} else if flagD {
fmt.Printf("4: In, NewTask\n")
ioCreator = cio.NewCreator(cio.WithStdio)
} else if flagD && logURI != "" {
fmt.Printf("44: In, NewTask, logURI: %s, flagD: %b\n", logURI, flagD)
// sudo ./_output/nerdctl run --name test2 -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
// を実行した時は。この if 文に処理が実施される。
u, err := url.Parse(logURI)
if err != nil {
return nil, err
}
fmt.Printf("444: In, NewTask, u: %#v\n", u)
ioCreator = cio.LogURI(u)
} else {
fmt.Printf("5: In, NewTask, flagT && !flagD\n")
var in io.Reader
if flagI {
if sv, err := infoutil.ServerSemVer(ctx, client); err != nil {
log.G(ctx).Warn(err)
} else if sv.LessThan(semver.MustParse("1.6.0-0")) {
log.G(ctx).Warnf("`nerdctl (run|exec) -i` without `-t` expects containerd 1.6 or later, got containerd %v", sv)
}
var stdinC io.ReadCloser = &StdinCloser{
Stdin: os.Stdin,
Closer: func() {
if t, err := container.Task(ctx, nil); err != nil {
log.G(ctx).WithError(err).Debugf("failed to get task for StdinCloser")
} else {
t.CloseIO(ctx, containerd.WithStdinCloser)
}
},
}
in = stdinC
}
ioCreator = cioutil.NewContainerIO(namespace, logURI, false, in, os.Stdout, os.Stderr)
}
t, err := container.NewTask(ctx, ioCreator)
if err != nil {
return nil, err
}
return t, nil
}
binary: のファイルの指定がよくわからんが、log-driver と関連がありそうなので、明日はそれを調査してみる。
調査 3 (nerdctl 内で使用されているパッケージ fifo の動作を確認する。)
…
結論
…
まとめ
SDE のトラシュー力に憧れて自分もコンテナに関連する OSS の開発により積極的に従事したくなった。いや、従事します。
Misc 1
binary:///Users/haytok/workspace/nerdctl/_output/nerdctl?_NERDCTL_INTERNAL_LOGGING=%2Fvar%2Flib%2Fnerdctl%2F1935db59
における 1935db59
の値は一意に定まるハッシュ値の一部っぽい。
[haytok@lima-default nerdctl]$ echo $(echo -n "/run/containerd/containerd.sock" | sha256sum | cut -c1-8)
1935db59
詳細は下記の doc に書いてあった。
[haytok@lima-default nerdctl]$ sudo tree /var/lib/nerdctl/1935db59
/var/lib/nerdctl/1935db59
├── containers
│ └── default
│ ├── 31b73a05a787aae0314bf6e5302e64127e659f3b2913f47a4a181c12686b901e
│ │ ├── 31b73a05a787aae0314bf6e5302e64127e659f3b2913f47a4a181c12686b901e-json.log
│ │ ├── hostname
│ │ ├── log-config.json
│ │ └── resolv.conf
│ ├── 3209cc5c61e2adebd56cfa3a020e312a364d4faa934daa1c69252cdaed3d2ec6
│ │ ├── 3209cc5c61e2adebd56cfa3a020e312a364d4faa934daa1c69252cdaed3d2ec6-json.log
│ │ ├── hostname
│ │ ├── log-config.json
│ │ ├── oci-hook.startContainer.log
│ │ └── resolv.conf
│ ├── 371236edf6cfbd5290f43f5772527fd6d287daa8d04f1fac58e6fe49d0cdea1d
│ │ ├── 371236edf6cfbd5290f43f5772527fd6d287daa8d04f1fac58e6fe49d0cdea1d-json.log
│ │ ├── hostname
│ │ ├── log-config.json
│ │ └── resolv.conf
│ ├── 8f2969effbf6fb0c8cd0358c03494c25d5bafd87c0aa1218861fe59e3d5e3550
│ │ ├── 8f2969effbf6fb0c8cd0358c03494c25d5bafd87c0aa1218861fe59e3d5e3550-json.log
│ │ ├── hostname
│ │ ├── log-config.json
│ │ └── resolv.conf
│ ├── a4e8f622ce1004f2b7bb224d9b2ead3235c053c6f2443ee371cde09ec43c3150
│ │ ├── a4e8f622ce1004f2b7bb224d9b2ead3235c053c6f2443ee371cde09ec43c3150-json.log
│ │ ├── hostname
│ │ ├── log-config.json
│ │ └── resolv.conf
│ ├── a6b1677b854c18a8cbc77bd52ce5f50a07bded07de4b5684d520653c676dd02e
│ │ ├── a6b1677b854c18a8cbc77bd52ce5f50a07bded07de4b5684d520653c676dd02e-json.log
│ │ ├── hostname
│ │ ├── log-config.json
│ │ └── resolv.conf
│ ├── b261e00db083b63902acf7201a15183ba40e31f829d114287814eb679f70aeec
│ │ ├── b261e00db083b63902acf7201a15183ba40e31f829d114287814eb679f70aeec-json.log
│ │ ├── hostname
│ │ ├── log-config.json
│ │ └── resolv.conf
│ ├── bfdb321d5dcf1ec7c0e900713597ba73d55efd7ae32d13e50bf776af66319c1a
│ │ ├── bfdb321d5dcf1ec7c0e900713597ba73d55efd7ae32d13e50bf776af66319c1a-json.log
│ │ ├── hostname
│ │ ├── log-config.json
│ │ └── resolv.conf
│ └── e56eee84f6a9ff827396e2d83c75657f51c1f31899bf3e0f53bc620bede508db
│ ├── e56eee84f6a9ff827396e2d83c75657f51c1f31899bf3e0f53bc620bede508db-json.log
│ ├── hostname
│ ├── log-config.json
│ └── resolv.conf
├── etchosts
│ └── default
│ ├── 31b73a05a787aae0314bf6e5302e64127e659f3b2913f47a4a181c12686b901e
│ │ └── hosts
│ ├── 3209cc5c61e2adebd56cfa3a020e312a364d4faa934daa1c69252cdaed3d2ec6
│ │ ├── hosts
│ │ └── meta.json
│ ├── 371236edf6cfbd5290f43f5772527fd6d287daa8d04f1fac58e6fe49d0cdea1d
│ │ └── hosts
│ ├── 8f2969effbf6fb0c8cd0358c03494c25d5bafd87c0aa1218861fe59e3d5e3550
│ │ └── hosts
│ ├── a4e8f622ce1004f2b7bb224d9b2ead3235c053c6f2443ee371cde09ec43c3150
│ │ └── hosts
│ ├── a6b1677b854c18a8cbc77bd52ce5f50a07bded07de4b5684d520653c676dd02e
│ │ └── hosts
│ ├── b261e00db083b63902acf7201a15183ba40e31f829d114287814eb679f70aeec
│ │ └── hosts
│ ├── bfdb321d5dcf1ec7c0e900713597ba73d55efd7ae32d13e50bf776af66319c1a
│ │ └── hosts
│ └── e56eee84f6a9ff827396e2d83c75657f51c1f31899bf3e0f53bc620bede508db
│ └── hosts
├── names
│ └── default
│ └── test
└── volumes
└── default
[haytok@lima-default nerdctl]$ sudo _output/nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3209cc5c61e2 docker.io/library/alpine:latest "sh -c while true; d…" 7 minutes ago Up test
現時点で動かしているコンテナ ID と照らし合わせた感じ、/var/lib/nerdctl/1935db59
の配下にコンテナに関連するデータが突っ込まれていると考えられる。
もう少し、/var/lib/nerdctl/1935db59/containers/default/
に関して調査してみる。
今、バックグラウンドで起動しているコンテナ ID のフルは下記より確認できる。
[haytok@lima-default nerdctl]$ sudo _output/nerdctl ps --no-trunc
0: In loadTask
0: In loadTask
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3209cc5c61e2adebd56cfa3a020e312a364d4faa934daa1c69252cdaed3d2ec6 docker.io/library/alpine:latest "sh -c while true; do echo /'Hello, world/'; sleep 1; done" About an hour ago Up test
このコンテナ ID がついた json log を確認すると、コンテナを起動する際に sh -c
で指定したコマンドによる標準出力の結果が書き込まれていた。
[haytok@lima-default nerdctl]$ sudo head -5 /var/lib/nerdctl/1935db59/containers/default/3209cc5c61e2adebd56cfa3a020e312a364d4faa934daa1c69252cdaed3d2ec6/3209cc5c61e2adebd56cfa3a020e312a364d4faa934daa1c69252cdaed3d2ec6-json.log
{"log":"/Hello, world/\n","stream":"stdout","time":"2024-03-26T14:45:56.643460042Z"}
{"log":"/Hello, world/\n","stream":"stdout","time":"2024-03-26T14:45:57.647806795Z"}
{"log":"/Hello, world/\n","stream":"stdout","time":"2024-03-26T14:45:58.650546172Z"}
{"log":"/Hello, world/\n","stream":"stdout","time":"2024-03-26T14:45:59.65744747Z"}
{"log":"/Hello, world/\n","stream":"stdout","time":"2024-03-26T14:46:00.664010059Z"}
これ、このコンテナに exec して echo haytok
を実行すると、この log の内容が変わるかを確認してみる。
結果、log には echo haytok
による記録は確認することができなかった。
一方、下記のようにコンテナを起動し、標準出力に文字列を書き込むコマンドを実行したとする。
[haytok@lima-default nerdctl]$ sudo _output/nerdctl run --rm -it alpine sh
/ # echo hoge
hoge
/ # ls
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
/ # echo risa
risa
/ #
[haytok@lima-default containerd]$ sudo ../nerdctl/_output/nerdctl ps --no-trunc
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3209cc5c61e2adebd56cfa3a020e312a364d4faa934daa1c69252cdaed3d2ec6 docker.io/library/alpine:latest "sh -c while true; do echo /'Hello, world/'; sleep 1; done" 2 hours ago Up test
b5a6199c3cc4f2409e09e2410d8038d3108ca234f64090108c788f35c9b1f0b5 docker.io/library/alpine:latest "sh" 4 minutes ago Up alpine-b5a61
[haytok@lima-default containerd]$ sudo cat /var/lib/nerdctl/1935db59/containers/default/b5a6199c3cc4f2409e09e2410d8038d3108ca234f64090108c788f35c9b1f0b5/b5a6199c3cc4f2409e09e2410d8038d3108ca234f64090108c788f35c9b1f0b5-json.log
{"log":"/ # \u001b[6n\r/ # \u001b[Jecho hoge\n","stream":"stdout","time":"2024-03-26T16:25:47.252472554Z"}
{"log":"hoge\n","stream":"stdout","time":"2024-03-26T16:25:47.25396863Z"}
{"log":"/ # \u001b[6nls\n","stream":"stdout","time":"2024-03-26T16:26:01.943442994Z"}
{"log":"\u001b[1;34mbin\u001b[m \u001b[1;34mdev\u001b[m \u001b[1;34metc\u001b[m \u001b[1;34mhome\u001b[m \u001b[1;34mlib\u001b[m \u001b[1;34mmedia\u001b[m \u001b[1;34mmnt\u001b[m \u001b[1;34mopt\u001b[m \u001b[1;34mproc\u001b[m \u001b[1;34mroot\u001b[m \u001b[1;34mrun\u001b[m \u001b[1;34msbin\u001b[m \u001b[1;34msrv\u001b[m \u001b[1;34msys\u001b[m \u001b[1;34mtmp\u001b[m \u001b[1;34musr\u001b[m \u001b[1;34mvar\u001b[m\n","stream":"stdout","time":"2024-03-26T16:26:01.948172886Z"}
{"log":"/ # \u001b[6n\r/ # ls\u001b[J\r/ # echo hoge\u001b[J\u0008\u001b[J\u0008\u001b[J\u0008\u001b[J\u0008\u001b[Jrisa\n","stream":"stdout","time":"2024-03-26T16:26:24.657328604Z"}
{"log":"risa\n","stream":"stdout","time":"2024-03-26T16:26:24.657492812Z"}
-d
で動作させる時とそうでない時で動作が変わってそう。
Docker でも動作確認をしたけど、-d でコンテナを起動した時に、そのコンテナで echo hoge をしても log には書き込まれへんのは期待された動作っぽい???
Misc 2
syscall.Mkfifo() について調査してみる。
そもそも、パイプを作成するためのコマンド / システムコールがある。
MKFIFO(1) General Commands Manual MKFIFO(1)
NAME
mkfifo – make fifos
SYNOPSIS
mkfifo [-m mode] fifo_name ...
DESCRIPTION
The mkfifo utility creates the fifos requested, in the order specified.
The options are as follows:
-m Set the file permission bits of the created fifos to the specified mode, ignoring the umask(2) of the calling process. The mode argument takes any format that can be
specified to the chmod(1) command. If a symbolic mode is specified, the op symbols ‘+’ (plus) and ‘-’ (hyphen) are interpreted relative to an assumed initial mode of “a=rw”
(read and write permissions for all).
If the -m option is not specified, fifos are created with mode 0666 modified by the umask(2) of the calling process. The mkfifo utility requires write permission in the parent
directory.
haytok ~/workspace/verification/fifo-verification
> mkfifo fifo
haytok ~/workspace/verification/fifo-verification
> ls -la
total 24
drwxr-xr-x@ 6 haytok staff 192 3 27 12:15 .
drwxr-xr-x@ 3 haytok staff 96 3 23 20:15 ..
prw-r--r--@ 1 haytok staff 0 3 27 12:15 fifo
-rw-r--r--@ 1 haytok staff 133 3 23 20:16 go.mod
-rw-r--r--@ 1 haytok staff 322 3 23 20:16 go.sum
-rw-r--r--@ 1 haytok staff 1418 3 25 13:22 main.go
haytok ~/workspace/verification/fifo-verification
> echo haytok >> fifo
haytok ~/workspace/verification/fifo-verification
> cat fifo
haytok
実はこれがmkfifoコマンドで作った不思議なファイル、「名前付きパイプ」の挙動です。
- 名前付きパイプから読み出そうとすると、誰かがその名前付きパイプに書き込むまで待たされる。
- 名前付きパイプへ書き込もうとすると、誰かがその名前付きパイプから読み出すまで待たされる。
Misc 3
issue のコメントを 1 つ 1 つ確認していく。
コメント 1
Is this change compatible with nerdctl logs?
My understanding is that when a container is launched with -d flag, the stdout and stderr streams will be forwarded to the logging driver, so when you attach to this container there are no output fifos to open. Would probably need to have the output streams directed to both fifos and logging driver to make it work. There is a discussion on #1946 to support this dual logging feature, but looks like there hasn’t been much progress.
訳
この変更は nerdctl ログと互換性がありますか?
私の理解では、コンテナが -d フラグで起動されると、stdout と stderr ストリームはロギング・ドライバに転送されます。そのため、このコンテナにアタッチすると、開くべき出力fifosがありません。おそらく、これを動作させるには、出力ストリームをfifosとロギングドライバの両方に向ける必要があるでしょう。このデュアルロギング機能をサポートするための議論が#1946で行われていますが、あまり進展がないようです。
パッチの適用前の状態で、-d を指定して作成したコンテナでは fifo は作成されない。
けど、nerdctl logs
を実行すると標準出力に対するる書き込みは確認できる。
[haytok@lima-default nerdctl]$ sudo ./_output/nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
240d6720d568 docker.io/library/alpine:latest "sh -c while true; d…" 2 hours ago Up test-dt
588376287ab6 docker.io/library/alpine:latest "sh -c while true; d…" 30 minutes ago Up test
[haytok@lima-default nerdctl]$
[haytok@lima-default nerdctl]$
[haytok@lima-default nerdctl]$ sudo ls /run/containerd/fifo
1034591092 118231144 2164213081 2376111114 2554841498 267100849 313929042 345658729 3541429126 4161914190 4230603106 890640757 938856586
[haytok@lima-default nerdctl]$ sudo ls /run/containerd/fifo/588376287ab6
ls: cannot access '/run/containerd/fifo/588376287ab6': No such file or directory
[haytok@lima-default nerdctl]$ sudo ls /var/lib/nerdctl/1935db59/containers/default/588376287ab6b23a48a45cd04e224c443ad3615ff3365bf43055f558874dee1b
588376287ab6b23a48a45cd04e224c443ad3615ff3365bf43055f558874dee1b-json.log hostname log-config.json oci-hook.startContainer.log resolv.conf
デフォルトの log driver は json-file
やけど、<container ID>-json.log
が作成されている。
nerdctl logs
箱のファイルを読み出していると考えられる。
パッチ適用して動作を確認してみる。
[haytok@lima-default nerdctl]$ sudo ./_output/nerdctl logs test-d
[haytok@lima-default nerdctl]$
互換性はございません … mharwani の言う通りです …
なので、mharwani の言うには、現状の動作として -d で起動したコンテナの標準出力と標準入力は logging driver に転送される。 この動作に対する互換性を
loggin driver の機能は残しつつ、-d で起動したコンテナから標準出力に書き込まれた内容を読み出す必要があります。 そのための方法として、
Other
重要なコマンド
sudo ./_output/nerdctl run --name test -d alpine sh -c "while true; do echo /'Hello, world/'; sleep 1; done"
sudo ./_output/nerdctl attach test
attach に失敗する。
sudo ./_output/nerdctl run –name test -d alpine sh -c “while true; do echo /‘Hello, world/’; sleep 1; done” sudo ./_output/nerdctl run –name test-dt -dt alpine sh -c “while true; do echo /‘Hello, world/’; sleep 1; done”
フォアグラウンドで動作するので、attach できる。
sudo ./_output/nerdctl run –name test-t -t alpine sh -c “while true; do echo /‘Hello, world/’; sleep 1; done”