containerd はコンテナランタイムのひとつ。CRI に準拠しており、Kubernetes のコンテナランタイムとして利用できる。 仕事で containerd の調査ができそうな気配があるので(あるかな?)、containerd を動作確認しつつ調査できるようにデバッグ環境を整える。
Go のインストール
containerd は Go 言語で記述されている。
containerd/containerdgithub.com
ということで、まずは Go 言語をインストールする。
# yum install golang
...
# go version
go version go1.14.12 linux/amd64
Delve のインストール
Go で記述されたプログラムのデバッグには Delve がおすすめらしい。
Note that Delve is a better alternative to GDB when debugging Go programs built with the standard toolchain. It understands the Go runtime, data structures, and expressions better than GDB. Delve currently supports Linux, OSX, and Windows on amd64. For the most up-to-date list of supported platforms, please see the Delve documentation.
引用元: https://golang.org/doc/gdb
ということで、README を参考に Delve をインストールする。
# git clone https://github.com/go-delve/delve
# cd delve
# go install github.com/go-delve/delve/cmd/dlv
# dlv version
Delve Debugger
Version: 1.4.0
Build: $Id: 67422e6f7148fa1efa0eac1423ab5594b223d93b $
containerd のインストール
getting-started を参考に、GitHub のアーカイブからファイルを展開した。ちなみに selinux は黙殺した。
# wget https://github.com/containerd/containerd/releases/download/v1.5.0/cri-containerd-cni-1.5.0-linux-amd64.tar.gz
# tar -xf cri-containerd-cni-1.5.0-linux-amd64.tar.gz
# cp etc/systemd/system/containerd.service /usr/lib/systemd/system/
# cp usr/local/bin/* /usr/local/bin/
# cp usr/local/sbin/* /usr/local/sbin/
# setenforce 0
# systemctl daemon-reload
# systemctl restart containerd
しかしこのバイナリは debuginfo が削られてるため、Delve からは扱えないっぽい。
# ps -ef | grep containerd
root 11606 1 0 20:56 ? 00:00:00 /usr/local/bin/containerd
# dlv attach 11606
could not attach to pid 11606: could not open debug info
たしかにバイナリに debug セクションっぽいものはない(そもそも Go のデバッグ情報どういうセクション名になるのか知らんけど、Debugging Go Code with GDB によると、.debug_* sections.
みたいな名前になるらしい)。
# readelf -S /usr/local/bin/containerd | grep debug
ということで、この方法はダメそう。
containerd のビルド
Delve のために go build -gcflags=all="-N -l"
を設定して containerd を自前でビルドする。
以下の PR によると、GODEBUG=1
環境変数を設定すると上記ビルドオプションが設定されるらしい。
Add build option “GODEBUG=1”
BUILDING.md を参考にビルドする。
# wget -c https://github.com/google/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip
# unzip protoc-3.11.4-linux-x86_64.zip -d /usr/local
# go get github.com/containerd/containerd
# cd $GOPATH/src/github.com/containerd/containerd/
# git checkout -b work refs/tags/v1.5.0
# GODEBUG=1 BUILDTAGS=no_btrfs make
# make install
readelf で確認しても debug 情報はちゃんと付いてる様子。
# readelf -S bin/containerd | grep debug
[33] .zdebug_aranges PROGBITS 0000000000000000 038affa0
[34] .zdebug_pubnames PROGBITS 0000000000000000 038b017a
[35] .zdebug_info PROGBITS 0000000000000000 038bf450
[36] .zdebug_abbrev PROGBITS 0000000000000000 03b168a6
[37] .zdebug_line PROGBITS 0000000000000000 03b173b2
[38] .zdebug_frame PROGBITS 0000000000000000 03c4aaa3
[39] .zdebug_str PROGBITS 0000000000000000 03cc3ebd
[40] .zdebug_loc PROGBITS 0000000000000000 03cc5971
[41] .zdebug_pubtypes PROGBITS 0000000000000000 03cda3b3
[42] .zdebug_ranges PROGBITS 0000000000000000 03d12fbc
[43] .debug_gdb_script PROGBITS 0000000000000000 03d50530
runc のビルド
runc は go.mod で記載されたバージョンでビルドする。
# go get github.com/opencontainers/runc
# cd $GOPATH/src/github.com/opencontainers/runc
# cat ../../containerd/containerd/go.mod | grep opencontainers/runc
github.com/opencontainers/runc v1.0.0-rc93
# git checkout -b work refs/tags/v1.0.0-rc93
# make BUILDTAGS='seccomp selinux'
go build -trimpath "-mod=vendor" "-buildmode=pie" -tags "seccomp selinux" -ldflags "-X main.gitCommit="12644e614e25b05da6fd08a38ffa0cfe1903fdec" -X main.version=1.0.0-rc93 " -o runc .
# mv runc /usr/local/sbin/runc
Delve でデバッグする
dlv attach
で起動済みのプロセスにアタッチする。
# systemctl restart containerd
# ps -ef | grep containerd
root 10937 1 0 22:30 ? 00:00:00 /usr/local/bin/containerd
# dlv attach 10937
うまく動いてるっぽい。
(dlv) funcs Pull
github.com/containerd/containerd.(*Client).Pull
github.com/containerd/containerd.(*Client).Pull.func1
github.com/containerd/containerd.(*Client).Pull.func2
github.com/containerd/containerd.WithPullLabel
github.com/containerd/containerd.WithPullLabel.func1
github.com/containerd/containerd.WithPullSnapshotter
github.com/containerd/containerd.WithPullSnapshotter.func1
github.com/containerd/containerd.WithPullUnpack
github.com/containerd/containerd/pkg/cri/server.(*criService).PullImage
github.com/containerd/containerd/pkg/cri/server.(*criService).PullImage.func1
github.com/containerd/containerd/pkg/cri/server.(*criService).encryptedImagesPullOpts
github.com/containerd/containerd/pkg/cri/server.(*instrumentedService).PullImage
github.com/containerd/containerd/pkg/cri/server.(*instrumentedService).PullImage.func1
github.com/containerd/containerd/remotes/docker.ContextWithAppendPullRepositoryScope
...
(dlv) break github.com/containerd/containerd/pkg/cri/server.(*criService).PullImage
(dlv) continue // ← 別ターミナルで crictl pull ubuntu を実行する
> github.com/containerd/containerd/pkg/cri/server.(*criService).PullImage() /root/go/src/github.com/containerd/containerd/pkg/cri/server/image_pull.go:92 (hits goroutine(83):1 total:1) (PC: 0x557fc2f618fd)
87: // 4) Is the content important if we cached necessary information in-memory
88: // after we pull the image? How to manage the disk usage of contents? If some
89: // contents are missing but snapshots are ready, is the image still "READY"?
90:
91: // PullImage pulls an image with authentication config.
=> 92: func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest) (*runtime.PullImageResponse, error) {
93: imageRef := r.GetImage().GetImage()
94: namedRef, err := distribution.ParseDockerRef(imageRef)
95: if err != nil {
96: return nil, errors.Wrapf(err, "failed to parse image reference %q", imageRef)
97: }
さいごに
これでソースコードあんまり読まずに実行パスやら呼び出し関係を確認できるねやったー。