docker debug

docker debug

docker debug는 어떤 컨테이너나 이미지에든 디버그 셸을 얻는 명령이에요. 슬림(slim) 이미지처럼 디버깅 도구가 다 제거된 환경에서도 쓸 수 있는, docker exec 대안이에요.

출처: 문서

본문

Docker Debug는 이미지를 작고 안전하게 유지하는 모범 사례를 따르도록 도와주는 CLI 명령이에요. 애플리케이션을 실행하는 데 필요한 최소한만 담긴 이미지를 디버깅할 수 있게 해 주죠. 모든 도구가 제거돼 있어 디버깅이 어려운 슬림 이미지나 컨테이너를 만들고 작업할 수 있게 해요.

예를 들어 docker exec -it my-app bash 같은 일반적인 디버깅 방법이 슬림 컨테이너에서는 동작하지 않을 수 있지만, docker debug는 동작해요. docker debug로는 셸이 들어 있지 않은 컨테이너나 이미지에도 디버그 셸을 얻을 수 있어요.

Docker Debug를 쓰기 위해 이미지를 수정할 필요도 없고, Docker Debug를 써도 이미지가 수정되지 않아요. Docker Debug는 쉽게 커스터마이즈할 수 있는 자체 toolbox를 가져오는데, vim·nano·htop·curl 같은 표준 Linux 도구가 미리 설치돼 있어요. https://search.nixos.org/packages에 있는 추가 도구는 내장 install 명령으로 더할 수 있어요.

Docker Debug는 bash, fish, zsh를 지원해요. 기본적으로 셸을 자동 감지하려고 해요.

내장 사용자 도구(builtin tools):

  • install [tool1] [tool2]: https://search.nixos.org/packages에서 Nix 패키지를 추가해요 (예제 참고)
  • uninstall [tool1] [tool2]: Nix 패키지를 제거해요
  • entrypoint: entrypoint를 출력·린트·실행해요 (예제 참고)
  • builtins: 내장 사용자 도구를 보여 줘요

참고: 이미지와 정지된 컨테이너의 경우 셸을 떠나면 모든 변경이 폐기돼요. 어떤 시점에도 변경이 실제 이미지나 컨테이너에 영향을 주지 않아요. 실행 중이거나 일시 중지된 컨테이너에 접근할 때 파일시스템 변경은 컨테이너에 직접 보여요. /nix 디렉터리는 실제 이미지나 컨테이너에 절대 보이지 않아요.

사용법 (Usage)

debug [OPTIONS] {CONTAINER|IMAGE}

옵션 (Options)

옵션 기본값 설명
--shell auto 사용할 셸을 선택해요. 지원: bash, fish, zsh, auto
-c, --command 대화형 세션 대신 지정한 명령을 평가해요 (예제 참고)
--host 연결할 데몬 docker 소켓이에요. 예: ssh://[email protected], unix:///some/path/docker.sock (예제 참고)

예제 (Examples)

셸이 없는 컨테이너 디버깅하기 (슬림 컨테이너)

hello-world 이미지는 매우 단순하고 /hello 바이너리만 담고 있어요. 다른 도구도 셸도 없는 슬림 이미지의 좋은 예죠.

hello-world 이미지에서 컨테이너를 실행해요.

$ docker run --name my-app hello-world

컨테이너는 즉시 종료돼요. 안에 디버그 셸을 얻으려면 이렇게 실행해요.

$ docker debug my-app

디버그 셸로 파일시스템을 살펴볼 수 있어요.

docker > ls
dev  etc  hello  nix  proc  sys

/hello 파일은 컨테이너를 실행할 때 실행된 바이너리예요. 직접 실행해 확인할 수 있어요.

docker > /hello

바이너리를 실행하면 같은 출력이 나와요.

슬림 이미지 디버깅하기

이미지를 직접 실행해 디버깅할 수 있어요.

$ docker debug hello-world
...
docker > ls
dev  etc  hello  nix  proc  sys

docker debugdocker run 명령처럼 이미지를 자동으로 가져오므로 이미지를 pull할 필요조차 없어요.

실행 중인 컨테이너의 파일 수정하기

Docker debug는 어떤 실행 중인 컨테이너의 파일도 수정할 수 있게 해 줘요. toolbox에는 vim과 nano가 미리 설치돼 있어요.

nginx 컨테이너를 실행하고 기본 index.html을 바꿔 볼게요.

$ docker run -d --name web-app -p 8080:80 nginx
d3d6074d0ea901c96cac8e49e6dad21359616bef3dc0623b3c2dfa536c31dfdb

nginx가 실행 중인지 확인하려면 브라우저를 열고 http://localhost:8080으로 이동해요. 기본 nginx 페이지가 보일 거예요. 이제 vim으로 바꿔요.

vim /usr/share/nginx/html/index.html

제목을 "Welcome to my app!"으로 바꾸고 파일을 저장해요. 이제 브라우저에서 페이지를 새로고침하면 갱신된 페이지가 보여요.

install 명령으로 toolbox 관리하기

내장 install 명령으로 https://search.nixos.org/packages의 어떤 도구든 toolbox에 추가할 수 있어요. 도구 추가는 실제 이미지나 컨테이너를 절대 수정하지 않고 toolbox에만 더해진다는 점을 기억해요.

docker debug를 실행한 뒤 nmap을 설치해요.

$ docker debug nginx
...
docker > install nmap
Tip: You can install any package available at: https://search.nixos.org/packages.
installing 'nmap-7.93'
these 2 paths will be fetched (5.58 MiB download, 26.27 MiB unpacked):
/nix/store/brqjf4i23fagizaq2gn4d6z0f406d0kg-lua-5.3.6
/nix/store/xqd17rhgmn6pg85a3g18yqxpcya6d06r-nmap-7.93
copying path '/nix/store/brqjf4i23fagizaq2gn4d6z0f406d0kg-lua-5.3.6' from 'https://cache.nixos.org'...
copying path '/nix/store/xqd17rhgmn6pg85a3g18yqxpcya6d06r-nmap-7.93' from 'https://cache.nixos.org'...
building '/nix/store/k8xw5wwarh8dc1dvh5zx8rlwamxfsk3d-user-environment.drv'...

docker > nmap --version
Nmap version 7.93 ( https://nmap.org )
Platform: x86_64-unknown-linux-gnu
Compiled with: liblua-5.3.6 openssl-3.0.11 libssh2-1.11.0 nmap-libz-1.2.12 libpcre-8.45 libpcap-1.10.4 nmap-libdnet-1.12 ipv6
Compiled without:
Available nsock engines: epoll poll select

다른 이미지에 디버그 셸을 얻어 nmap이 이제 toolbox의 일부인지 확인할 수 있어요.

$ docker debug hello-world
...
docker > nmap --version

Nmap version 7.93 ( https://nmap.org )
Platform: x86_64-unknown-linux-gnu
Compiled with: liblua-5.3.6 openssl-3.0.11 libssh2-1.11.0 nmap-libz-1.2.12 libpcre-8.45 libpcap-1.10.4 nmap-libdnet-1.12 ipv6
Compiled without:
Available nsock engines: epoll poll select

docker > exit

nmap은 여전히 남아 있어요.

컨테이너의 기본 시작 명령 이해하기 (entry point)

Docker Debug에는 내장 도구 entrypoint가 있어요. hello-world 이미지에 들어가 entrypoint가 /hello인지 확인해요.

$ docker debug hello-world
...
docker > entrypoint --print
/hello

entrypoint 명령은 기본 이미지의 ENTRYPOINT와 CMD 문을 평가해서 결과 entrypoint를 출력·린트·실행할 수 있게 해 줘요. 다만 Understand how CMD and ENTRYPOINT interact에서 모든 모서리 케이스를 이해하는 건 어려울 수 있어요. 이런 상황에서 entrypoint가 도움이 돼요.

entrypoint로 Nginx 이미지에서 컨테이너를 실행할 때 실제로 무슨 일이 일어나는지 조사해요.

$ docker debug nginx
...
docker > entrypoint
Understand how ENTRYPOINT/CMD work and if they are set correctly.
From CMD in Dockerfile:
 ['nginx', '-g', 'daemon off;']

From ENTRYPOINT in Dockerfile:
 ['/docker-entrypoint.sh']

By default, any container from this image will be started with following   command:

/docker-entrypoint.sh nginx -g daemon off;

path: /docker-entrypoint.sh
args: nginx -g daemon off;
cwd:
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Lint results:
 PASS: '/docker-entrypoint.sh' found
 PASS: no mixing of shell and exec form
 PASS: no double use of shell form

Docs:
- https://docs.docker.com/reference/dockerfile/#cmd
- https://docs.docker.com/reference/dockerfile/#entrypoint
- https://docs.docker.com/reference/dockerfile/#understand-how-cmd-and-entrypoint-interact

출력은 nginx 이미지가 시작될 때 /docker-entrypoint.sh 스크립트가 인자 nginx -g daemon off;로 실행된다는 걸 알려 줘요. --run 옵션으로 entrypoint를 테스트할 수 있어요.

$ docker debug nginx
...
docker > entrypoint --run
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2024/01/19 17:34:39 [notice] 50#50: using the "epoll" event method
2024/01/19 17:34:39 [notice] 50#50: nginx/1.25.3
2024/01/19 17:34:39 [notice] 50#50: built by gcc 12.2.0 (Debian 12.2.0-14)
2024/01/19 17:34:39 [notice] 50#50: OS: Linux 5.15.133.1-microsoft-standard-WSL2
2024/01/19 17:34:39 [notice] 50#50: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2024/01/19 17:34:39 [notice] 50#50: start worker processes
2024/01/19 17:34:39 [notice] 50#50: start worker process 77
...

이렇게 하면 실제로 컨테이너를 실행하지 않고도 디버그 셸에서 nginx를 시작해요. Ctrl+C로 nginx를 종료할 수 있어요.

명령 직접 실행하기 (예: 스크립팅)

대화형 세션을 시작하는 대신 --command 옵션으로 명령을 직접 평가할 수 있어요. 예를 들어 bash -c "arg1 arg2 ..."와 비슷해요. 다음 예제는 대화형 세션을 시작하지 않고 nginx 이미지에서 cat 명령을 실행해요.

$ docker debug --command "cat /usr/share/nginx/html/index.html" nginx

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

--host 옵션으로 원격 디버깅하기

다음 예제들은 --host 옵션을 쓰는 방법을 보여 줘요. 첫 번째 예제는 SSH로 example.org의 원격 Docker 인스턴스에 root 사용자로 연결해 my-container 컨테이너에 셸을 얻어요.

$ docker debug --host ssh://[email protected] my-container

다음 예제는 다른 로컬 Docker Engine에 연결해 my-container 컨테이너에 셸을 얻어요.

$ docker debug --host=unix:///some/path/docker.sock my-container

더 알아보기 (Learn more)