Home [데브코스] 9주차 - Docker (chroot, pseudo path)
Post
Cancel

[데브코스] 9주차 - Docker (chroot, pseudo path)


chroot

change root directory의 약자로 root dir를 특정 디렉토리로 변경하는 기능이다. UNIX command, c언어의 형태로 존재한다. 초기 chroot의 경우 SVr4에서 등장했고, system 설치나 복구 등에 대해 사용되었다. 부수적으로 jail(감옥)의 기능을 가지고 있다.


특정 폴더를 생성하고, bin,etc,lib,usr 등 폴더를 생성하고 chroot / /mnt/chroot 를 하게 되면 루트 디렉토리가 /mnt/chroot로 변경된다. 이 후 /usr/local로 이동하면 실제로는 /mnt/chroot/usr/local로 이동이 되는 셈이다.


chroot는 부수적 기능으로서 보안적 측면의 격리 기능을 가지고 있다. 이를 sandbox의 개념이라고 하는데, 특서한 목적을 위해 격리된 형태의 공간을 의미한다. chroot를 사용하게 되면 특정 디렉토리 안에서 격리가 되고, 동일한 프로그램을 다른 환경으로 복제할 수 있도록 해준다. 구현체 중에 sandboxf라는 이름이 있는데, 이것과 다른 기능이므로 주의해야 한다.


  • 준비 작업

1.먼저 ftp 서비스를 제공하는 vsftpd를 설치한다.

1
$ sudo apt -y install vsftpd


2.설치가 되었는지 확인한다.

1
2
$ sudo systemctl is-active vsftpd
active


2-1.active 상태가 아니라면 다음과 같이 명령한다.

1
$ sudo systemctl start vsftpd


3.ftp클라이언트 프로그램인 filezilla를 설치 후 실행

1
2
$ sudo apt -y install filezilla
$ filezilla

설치 후 실행하면 창이 뜨는데, 좌측 상단에 제일 왼쪽 버튼을 누르면 사이트 관리자가 나온다. 여기서 새 사이트를 누르고 이름은 아무렇게나 넣으면 된다. ip주소는 127.0.0.1, 포로토콜은 FTP, 로그온은 일반으로 설정 후 시스템에 있는 일반 유저명과 암호를 넣는다. 이 때, 중요한 것은 한글이 깨지는 것을 방지하기 위해 문자셋을 설정해야 한다. 문자셋 탭에 가서 utf-8로 강제 설정을 선택한다.


접속이 완료되면 창이 2개 나온다. 왼쪽이 local, 오른쪽이 remote이다. 오른쪽의 홈 디렉토리를 확인한 후 연결을 끊는다.


4.vsftpd.conf 파일을 수정한다.

1
$ sudo vim /etc/vsftpd.conf

그 후 아래 2가지를 추가한다.

1
2
chroot_local_user=YES
allow_writeable_chroot=YES

설정을 저장한 뒤 vsftpd를 재시작한다.

1
2
$ sudo systemctl restart vsftpd
$ sudo systemctl status vsftpd


5.filezilla에 재접속 하여 home 디렉토리를 확인한다.

확인해보면 오른쪽 화면에 디렉토리가 /로 되어 있을 것이다.


chroot는 rescue모드 부팅에서 사용될 수 있다. 예를 들어 A와 B시스템이 있는데, A가 고장나서 부팅이 안되고, B는 정상일 때 B에 A의 디스크를 붙이고 부팅한 뒤 A를 B에 마운트한 후 chroot /mnt를 하면 B디스크에 A시스템이 붙어서 A에 있는 파일을 실행시킬 수 있다.


Isolation

  • 격리의 필요성 시스템 내에 존재하는 자원은 한정적이다. 한정적인 자원을 효율적으로 분배하면 시스템의 가용성을 올릴 수 있다. 현대적인 OS는 프로세스가 독립적인 공간을 가지게 해준다. 즉 고유한 공간이기 때문에 다른 사람들이 볼 수 없다. 그러나 외부 통신을 위해 IPC를 사용해야 해서 I/O 비용이 높아진다. 여러 프로세스가 협동해야 하는 프로그램에서는 단점이 더 커진다. 예를 들어 DBMS이나 server 네트워크를 다룰 때, 한 시스템에 2개의 DBMS를 구동하기 힘들다. 그 이유는 DBMS를 구성하는 각종 프로세스들이 특정 디렉토리를 독점적으로 사용하는 경우가 많기 때문이다. 이를 함께 사용하면 충돌이 발생한다.


Isolation의 활용

  1. 보안, 자원 관리적 측면 특정 파일 경로의 접근을 제한할 수 있다. 예를 들어 위에서 했듯이 root directory를 변경하여 특정 공간 안에서만 이동이 가능하도록 만들면 독립적인 공간을 가질 수 있다. 또는 호스팅 업체라면 고성능의 컴퓨터 1대로 여러 사업자에게 DB나 웹을 제공할 수 있다.

  2. 호환, 충돌 측면 동일한 디렉토리를 사용하는 프로세스는 독립된 실행하거나, 서로 다른 버전의 파일을 사용하는 프로세스를 사용하고자 한다면 격리된 공간을 사용해야 할 것이다.



Name space

chroot에는 chroot 기능하나만 있는 것이 아니라 name space라는 기능이 또 있다.

History

Plan 9이라는 기업에서 1992년도에 분산 컴퓨팅 시스템으로서 local system, remote system을 계층적 file system으로 표현한 기능을 만들었다. 예를 들어, /usr 폴더 안에 /a/aa/aaa 폴더가 있고, /b/bb/bbb 폴더가 있는 것이 이 계층적 file system이다. 이를 linux에도 구현이 되어 있다.isolated resouces(독립된 자원)를 NS의 계층적 file system 형태로 구현했다. 방식의 위의 폴더 방식과 동일하다. 이 name space를 줄여서 NS로 표기하기도 한다.


Namespace 종류

  • mount
  • UTS(UNIX Time-sharing : 유닉스 시분할 시스템)
    • UTS는 가상머신과 비슷한 것으로 호스트 네임을 분리하여 하나의 시스템이 여러 개의 이름을 가질 수 있다.
  • IPC
  • network
  • PID
  • user
  • cgroup
    • 2006년도에는 process container로 만들어져, 2007년에 cgroup으로 이름이 바뀌었다.
    • group별로 가상화된 공간을 만들고 자원을 제약할 수 있게 한다. 다른 그룹은 격리되어있으므로 물리적으로 다른 호스트처럼 인식한다.
    • docker, hadoop, systemd 등 수많은 프로젝트들이 cgroup을 사용한다.


namespace 관리하는 명령어

  • unshare
  • lsns
  • nsenter


Namespace 기본 작동

unshare과 lsns를 사용해보고자 한다. unshare의 경우 고유의 공간을 만들고 그 안에서 프로그램을 실행할 수 있게 해주는 기능이고, lsns를 통해 namespace를 살펴본다.


1
# unshare -pf --mount-proc /bin/bash

격리된 프로세스를 만드는데, 그 프로세스의 이름이 bash이다.

옵션

  • -p : –pid = pid를 격리시켜서 새로운 pid를 생성
  • -f : –fork = 자식 프로세스를 만들어서 실행
  • –mount-proc : proc파일 시스템을 고유의 공간으로 가져가기
    • 프로세스 컨트롤 블록을 고유하게 사용


1
2
3
4
5
6
# ps
   PID TTY          TIME CMD
     1 pts/0    00:00:00 bash
    57 pts/0    00:00:00 ps

# exit

pid는 낮은 숫자부터 실행된다.


  • network를 격리해보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ss -nltp

State    Recv-Q    Send-Q        Local Address:Port       Peer Address:Port    
LISTEN   0         128           127.0.0.53%lo:53              0.0.0.0:*       
LISTEN   0         128                 0.0.0.0:22              0.0.0.0:*       
LISTEN   0         5                 127.0.0.1:631             0.0.0.0:*       
LISTEN   0         32                        *:21                    *:*       
LISTEN   0         128                    [::]:22                 [::]:*       
LISTEN   0         5                     [::1]:631                [::]:*   



host에서 5000번 포트를 listen
# nc -l 5000 >nc_host_outout.txt  & 
[1] 32400

# ss -nltp

State    Recv-Q    Send-Q        Local Address:Port       Peer Address:Port                                                                                   
LISTEN   0         128           127.0.0.53%lo:53              0.0.0.0:*                                                                                      
LISTEN   0         128                 0.0.0.0:22              0.0.0.0:*                                                                                      
LISTEN   0         5                 127.0.0.1:631             0.0.0.0:*                                                                                      
LISTEN   0         1                   0.0.0.0:5000            0.0.0.0:*        users:(("nc",pid=32400,fd=3))                                                 
LISTEN   0         32                        *:21                    *:*                                                                                      
LISTEN   0         128                    [::]:22                 [::]:*                                                                                      
LISTEN   0         5                     [::1]:631                [::]:*  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# unshare -n /bin/bash

port list, host의 PID까지 다 보임
# ps
   PID TTY          TIME CMD
 32419 pts/0    00:00:00 sudo
 32420 pts/0    00:00:00 bash
 32476 pts/0    00:00:00 ps



격리를 했기 때문에 나오지 않음
# ss -nlt
State   Recv-Q    Send-Q        Local Address:Port        Peer Address:Port


다시 listen
# nc -l 5000 >nc_host_outout.txt  & 
[1] 32479


# ss -nlt
State    Recv-Q    Send-Q        Local Address:Port       Peer Address:Port    
LISTEN   0         1                   0.0.0.0:5000            0.0.0.0:*   


위의 창은 그대로 유지한채로 터미널을 한개 더 켜서 확인해본다.

1
2
3
4
5
6
7
8
9
10
11
# lsns
        NS TYPE   NPROCS   PID USER             COMMAND
4026531835 cgroup    330     1 root             /sbin/init splash
4026531836 pid       330     1 root             /sbin/init splash
4026531837 user      330     1 root             /sbin/init splash
4026531838 uts       330     1 root             /sbin/init splash
...
4026532583 net         2 32420 root             /bin/bash
4026532672 mnt         1  1274 root             /usr/lib/bluetooth/bluetoothd
...

방금 연결한 net /bin/bash가 보인다. 여기서 unshare -pf --mount-proc /bin/bash를 하면 NPROCS가 올라가는 것을 볼 수 있다.


Docker

LXC(Linux Container)

  • 현재 cononical에서 공식적으로 지원하고 있고, 초창기 리눅스 컨테이너 기술의 발판이 되었다. docker의 경우도 초창기에는 lxc를 사용했다.

docker는 container runtime 기술로 2008년에 설립하여 2013년에 릴리즈했다. docker는 container를 세련된 방식으로 구현한 제품의 일종으로 격리된 자원의 묶음과 런타임으로 구성된다. 기본적으로 C/S 구조를 가지므로 daemon이 작동된다.

docker는 host os위에서 작동되는 격리된 프로세스의 일종이므로 virtual machine과 달리 memory, file system 등의 문제가 발생하지 않는다. 그러나 단점으로는 daemon으로 작동하기 때문에 daemon이 버그가 걸리면 밑에 있던 모든 container가 죽어버린다. 또 docker는 관리자 권한으로 실행해야 한다. 따라서 보안적 문제가 발생하기 쉽다.


왼쪽은 conatiner의 작동방식이고, 오른쪽은 virtual machine의 작동 방식이다. container는 바로 바로 전달받아서 동작하나, virtual machine의 경우 hypervisor를 통해 전달해서 virtual machine으로 들어갔다가 다시 나온다. 이 hypervisor과 virtual machine간의 통신이 성능이 매우 저하되는 곳이다.

podman

docker의 보안성문제로 인해 대안책으로 podman이라는 linux container가 있다. podman은 RedHat에서 지원하고, daemon을 사용하지 않고, 관리자 권한도 사용하지 않으며, systemd와 잘 어울리므로 중앙집중이 잘된다.


container 기술은 매우 자주 변화하는 기술이다. 따라서 1가지만 사용하는 것이 아닌 트랜드에 맞춰 바꿔가며 사용하는 것이 좋다.


Virtualization

Virtual machine

가상 머신도 격리의 일종이다. 가상머신의 경우는 full virtualization을 사용한다. 가상머신은 소프트웨어로 가상화된 하드웨어를 구현시켜준다. 이를 통해 격리된 공간을 제공한다. 하지만 이로 인해 실행을 하면 성능이 너무 안좋아진다. 또 독점적인 자원을 점유한다. 즉 서로 VM끼리는 자원을 공유할 수 없다.


Sandbox

사전적 의미 그대로 격리된 공간을 의미한다. 다양한 방법이 가능하다. (VM, container, chroot ,…), 이 sandbox는 테스트 유닛으로서 격리된 공간, 보안 공간, 복제된 서비스 공간에 사용된다.

  • 장점 프로그램이 작동하기 위해서 많은 외부 자원을 필요로 한다. 예를 들어 게임을 설치할 때 directX, VC+ redist, library 2014,,, 등에 대한 버전을 여러 개 설치해야 하는데, 이에 대한 충돌이 발생할 수 있는데, 이를 해결해줄 수 있다.

단지 격리만을 목적이라면 VM을 사용할 필요도 없이 lightweight container을 사용하면 된다. 이는 공유할 부분은 공유하고, 따로 사용할 부분은 따로 사용할 수 있다. 그러나 host OS와 공유하는 부분이 있으므로 이기종의 OS를 사용할 수 없다.



docker 설치

docker 사이트

old version 삭제

docker 버전이 여러 개 있으면 충돌이 발생하기 때문에 옛살 버전을 삭제해야 한다.

1
2
3
4
5
6
$ su -
# apt list docker{,-engine,.ip} containerd runc
Listing... Done
containerd/bionic-updates,bionic-security 1.5.5-0ubuntu3~18.04.2 amd64
docker/bionic 1.5-1build1 amd64
runc/bionic-updates 1.0.1-0ubuntu2~18.04.1 amd64

[installed] 되어 있으면 제거한다.


필요 패키지 설치 및 key file 추가

1
2
3
4
5
# apt update

# apt -y install apt-transport-https  ca-certificates  curl  gnupg  lsb-release

# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

위 명령이 실행되면 /usr/share/keyrings/docker-archive-keyring.gpg에 key파일이 생성된다. 이는 https에 필요한 파일이다.


APT 저장소 source.list 추가

1
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list

이 명령이 성공하면 /etc/apt/source.list.d에 docker.list가 생성된다.

  • deb : 데비안 패키지
  • arch=amd64 : intel 호환 x86 64bit를 사용
  • signed-by= : 앞에서 저장한 key 파일 위치
  • url : 다운로드 경로
  • lsb_Reelase -cs : binoic stable 파일을 다운


docker engine 설치

1
2
3
# apt update

# apt -y install docker-ce docker-ce-cli containerd.io
  • docker daemon 실행 확인
1
2
3
4
5
6
7
8
9
10
# systemctl status docker
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: e
   Active: active (running) since Fri 2022-04-15 16:39:55 KST; 14s ago
     Docs: https://docs.docker.com
 Main PID: 8891 (dockerd)
    Tasks: 13
   CGroup: /system.slice/docker.service
           └─8891 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/contain


docker 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete 
Digest: sha256:10d7d58d5ebd2a652f4d93fdd86da8f265f5318c6a73cc5b6a9798ff6d2b2e67
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

  1. hello-world라는 image가 없으므로 다운
  2. docker client가 docker daemon에 접속
  3. docker daemon이 hello-world image를 가져온다. (amd64)
  4. image를 통해 새로운 컨테이너를 생성
  5. 만든 이미지 안에서 실행된 결과를 터미널로 가져온다.

완료되면 $ docker run -it ubuntu bash 를 실행해보라는 말이 나온다. 이를 실행하면 docker안으로 들어갈 수 있게 된다.

1
2
3
4
5
6
7
# docker run -it ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
e0b25ef51634: Pull complete 
Digest: sha256:9101220a875cee98b016668342c489ff0674f247f6ca20dfc91b91c0f28581ae
Status: Downloaded newer image for ubuntu:latest
root@3102cd9f9b3f:/# 

옵션

  • -i : interactive mode (open stdin) = shell을 쓸 수 있게 하는 옵션
  • -t : terminal (allocate a pseudo-tty , stdio) = 터미널을 쓸 수 있게 하는 옵션


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
root@3102cd9f9b3f:/# ps
  PID TTY          TIME CMD
    1 pts/0    00:00:00 bash
    9 pts/0    00:00:00 ps
root@3102cd9f9b3f:/# cd
root@3102cd9f9b3f:~# pwd
/root
root@3102cd9f9b3f:~# ls -al
total 16
drwx------ 2 root root 4096 Apr  5 05:02 .
drwxr-xr-x 1 root root 4096 Apr 15 07:50 ..
-rw-r--r-- 1 root root 3106 Dec  5  2019 .bashrc
-rw-r--r-- 1 root root  161 Dec  5  2019 .profile

root@3102cd9f9b3f:~# exit
exit

이 때, root는 docker안의 루트이고, ps 를 치면 이는 격리된 공간으로서 1번을 받게 된다. ls -al을 통해 파일을 보게 되면 실제 우리 host os의 root 디렉토리가 아니라는 것을 알 수 있다.

1
2
3
4
# docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
3102cd9f9b3f   ubuntu        "bash"     4 minutes ago    Exited (0) 4 seconds ago              interesting_chandrasekhar
cf5599884127   hello-world   "/hello"   13 minutes ago   Exited (0) 13 minutes ago             practical_antonelli

생성된 시간과 현재 상태를 보여준다. 여기서 중요한 것은 container의 Id와 이름 둘다 나오고 있으며, 이것들을 통해 구분이 가능하다.



docker CLI(Command Line Interface)

docker는 기본적으로 docker이라는 binary명령을 사용한다. docker는 docker daemon과 통신하기 때문에 먼저 daemon을 실행시켜줘야 한다. 추가적으로 docker group을 supplemetary group에 포함시켜야 사용이 가능하다. 즉 나의 일반 유저의 그룹에 추가해야 한다.

1
2
3
4
5
6
7
8
9
10
11
$ whoami
jhyoon

$ sudo usermod -aG docker jhyoon

$ id
uid=1000(jhyoon) gid=1000(jhyoon) groups=1000(jhyoon),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)

$ id jhyoon
uid=1000(jhyoon) gid=1000(jhyoon) groups=1000(jhyoon),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare),999(docker)

그냥 id에서는 docker가 나오지 않는 이유는 id는 런타임된 부분을 보여주고 이름까지 타이핑하면 설정된 그룹을 표시해주기 때문이다. 그룹 추가 후 session을 재생성해야 보인다. 즉, 재로그인을 해야 하므로 컴퓨터를 재부팅을 하거나 다음과 같은 방법을 사용한다.

  1. X Window 로그아웃 (우측 상단에 logout)
  2. <CTRL - ALT - F4>를 눌러 tty4로 이동한 뒤 console에서 root로 로그인
  3. systemctl restart gdm
  4. <cTRL - ALT - F1>를 눌러 X window 로그인

그 후 Docker가 들어가 있는지 확인한다.

1
2
$ id
uid=1000(jhyoon) gid=1000(jhyoon) groups=1000(jhyoon),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare),999(docker)

여기서 안나오면, 그냥 재부팅하기를 추천한다.


이를 통해 루트가 아닌 개인 계정으로도 docker를 실행할 수 있게 되었다.

1
2
3
4
5
6
7
$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
3102cd9f9b3f   ubuntu        "bash"     25 minutes ago   Exited (0) 20 minutes ago             interesting_chandrasekhar
cf5599884127   hello-world   "/hello"   34 minutes ago   Exited (0) 34 minutes ago             practical_antonelli

그냥 ps를 치면 현재 진행중인 컨테이너를 보여주는데, 여기에 -a 옵션을 추가하면 종료된 컨테이너도 보여준다. image 부분이 컨테이너 이미지, Command는 컨테이너 안에서 실행된 명령어이다. status는 명령어에 대한 출력이다.0이면 성공을 의미한다. 이름은 랜덤으로 주어지는 이름이다.


1
2
3
4
5
6
7
8
9
10
$ docker run hello-world
...

$ docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
dcb316d5d2f6   hello-world   "/hello"   40 seconds ago   Exited (0) 25 seconds ago             happy_euler
3102cd9f9b3f   ubuntu        "bash"     30 minutes ago   Exited (0) 25 minutes ago             interesting_chandrasekhar
cf5599884127   hello-world   "/hello"   39 minutes ago   Exited (0) 38 minutes ago             practical_antonelli

$ docker rm dcb316d5d2f6

rm 명령을 통해 컨테이너를 삭제할 수 있다. 여기서 id또는 이름을 지정해주면 된다.


컨테이너의 이름을 직접 지정해줄 수 있다.

1
2
3
4
5
6
7
8
9
10
$ docker run --name hello-world_01 hello-world
...

$ docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
dcb316d5d2f6   hello-world   "/hello"   40 seconds ago   Exited (0) 25 seconds ago             hello-world_01
3102cd9f9b3f   ubuntu        "bash"     30 minutes ago   Exited (0) 25 minutes ago             interesting_chandrasekhar
cf5599884127   hello-world   "/hello"   39 minutes ago   Exited (0) 38 minutes ago             practical_antonelli

$ docker rm hello-world_01

이처럼 이름을 직접 지정해주면 사용 용도나 제작 의도를 알 수 있다.


10개의 hello-world 컨테이너를 실행해보자.

1
2
3
4
5
$ for ii in {1..10}; do docker run --name hello-world_${ii} hello-world; done

$ docker ps -a



filter 기능

docker cLI의 option에는 filter 기능이 제공한다. 모든 명령어에 제공되는 것은 아니지만, ps에서는 제공된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
5e64b7abf4f5   hello-world   "/hello"   2 minutes ago    Exited (0) 2 minutes ago              hello-world_10
912c9f14910a   hello-world   "/hello"   2 minutes ago    Exited (0) 2 minutes ago              hello-world_9
ccdda8a7d6e7   hello-world   "/hello"   2 minutes ago    Exited (0) 2 minutes ago              hello-world_8
7e8f2369071f   hello-world   "/hello"   2 minutes ago    Exited (0) 2 minutes ago              hello-world_7
eed82f62df9d   hello-world   "/hello"   3 minutes ago    Exited (0) 3 minutes ago              hello-world_6
64fb8ef6f1e3   hello-world   "/hello"   3 minutes ago    Exited (0) 3 minutes ago              hello-world_5
a93fe3dbd077   hello-world   "/hello"   3 minutes ago    Exited (0) 3 minutes ago              hello-world_4
92e289160f8f   hello-world   "/hello"   3 minutes ago    Exited (0) 3 minutes ago              hello-world_3
5115345eec80   hello-world   "/hello"   4 minutes ago    Exited (0) 4 minutes ago              hello-world_2
f41fdc72a8f5   hello-world   "/hello"   4 minutes ago    Exited (0) 4 minutes ago              hello-world_1
dcb316d5d2f6   hello-world   "/hello"   10 minutes ago   Exited (0) 9 minutes ago              happy_euler
3102cd9f9b3f   ubuntu        "bash"     39 minutes ago   Exited (0) 35 minutes ago             interesting_chandrasekhar
cf5599884127   hello-world   "/hello"   48 minutes ago   Exited (0) 48 minutes ago             practical_antonelli

$ docker ps -af 'name=hello-world_[1-3]'
   PORTS     NAMES
5e64b7abf4f5   hello-world   "/hello"   3 minutes ago   Exited (0) 2 minutes ago             hello-world_10
92e289160f8f   hello-world   "/hello"   4 minutes ago   Exited (0) 4 minutes ago             hello-world_3
5115345eec80   hello-world   "/hello"   4 minutes ago   Exited (0) 4 minutes ago             hello-world_2
f41fdc72a8f5   hello-world   "/hello"   5 minutes ago   Exited (0) 5 minutes ago             hello-world_1

glob pattern패턴을 지원하는 것을 볼 수 있다. 그러나 extglob은 지원하지 않는다.


filter에는 name외에 id, label, status 등이 가능하다.


필터와는 비슷한 기능으로 format option을 사용하면 원하는 column만 뽑을 수 있다.

1
2
3
4
5
6
7
8
$ docker ps -af 'name=hel' --format "   "
5e64b7abf4f5 hello-world Exited (0) 8 minutes ago hello-world_10
912c9f14910a hello-world Exited (0) 8 minutes ago hello-world_9
ccdda8a7d6e7 hello-world Exited (0) 8 minutes ago hello-world_8
7e8f2369071f hello-world Exited (0) 9 minutes ago hello-world_7
eed82f62df9d hello-world Exited (0) 9 minutes ago hello-world_6
64fb8ef6f1e3 hello-world Exited (0) 9 minutes ago hello-world_5
a93fe3dbd077 hello-world Exited (0) 10 minutes ago hello-world_4

formatting에 사용될 수 있는 항목은 많다.

  • .ID
  • .Image
  • .Command
  • .CreatedAt
  • .Ports
  • .Status
  • .State

앞에 항상 .이 붙어 있고, 대/소문자 구분을 반드시 해줘야 한다.


삭제할 때 format을 사용하여 삭제해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ docker ps -af 'name=hello' --format ""

$ docker ps -af 'name=hello' --format "" | xargs docker rm
hello-world_10
hello-world_9
hello-world_8
hello-world_7
hello-world_6
hello-world_5
hello-world_4
hello-world_3
hello-world_2
hello-world_1

대신 을 사용해도 된다. xargs를 통해 파이프 앞에서 출력되는 결과를 인자로 받아주어 삭제한다. 앞에서 실행한 결과의 hello-world_10을 docker rm hello-world_10로 실행된다.



rm을 통해 컨테이너는 삭제했지만, 컨테이너에서 사용되었던 images를 지우지는 않는다. 그래서 rmi를 통해 삭제한다.

1
2
$ docker rmi hello-world
Error response from daemon: conflict: unable to remove repository reference "hello-world" (must force) - container cf5599884127 is using its referenced image feb5d9fea6a5

이 때 에러가 나오는 이유는 container가 존재하기 때문에 실패한 것이다. 강제로 지울 수 있지만, 되도록이면 container를 삭제한 뒤 image를 삭제해야 한다.

1
2
3
4
5
6
7
8
9
10
11
$ docker ps -af 'ancestor=hello-world' --format "" | xargs docker rm
happy_euler
practical_antonelli



$ docker rmi hello-world
Untagged: hello-world:latest
Untagged: hello-world@sha256:10d7d58d5ebd2a652f4d93fdd86da8f265f5318c6a73cc5b6a9798ff6d2b2e67
Deleted: sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412
Deleted: sha256:e07ee1baac5fae6a26f30cabfe54a36d3402f96afda318fe0a96cec4ca393359

anscestor은 이미지를 통해 검색하는 것이다. hello-world를 사용하는 컨테이너를 모두 삭제한다.



docker image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ docker image

Usage:  docker image COMMAND

Manage images

Commands:
  build       Build an image from a Dockerfile
  history     Show the history of an image
  import      Import the contents from a tarball to create a filesystem image
  inspect     Display detailed information on one or more images
  load        Load an image from a tar archive or STDIN
  ls          List images
  prune       Remove unused images
  pull        Pull an image or a repository from a registry
  push        Push an image or a repository to a registry
  rm          Remove one or more images
  save        Save one or more images to a tar archive (streamed to STDOUT by default)
  tag         Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE

Run 'docker image COMMAND --help' for more information on a command.

현재는 아직 이미지를 직접 생성하지 않을 것이므로 운용 명령인 inspect, load, ls, pull, rm, save 를 중점적으로 보도록 하자.


ls

1
2
3
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
ubuntu       latest    825d55fb6340   9 days ago   72.8MB
  • REPOSITORY : docker image 저장소 이름
  • TAG : 태그 이름 ( 버전 )


pull

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ docker image pull nginx
Using default tag: latest
latest: Pulling from library/nginx
c229119241af: Pull complete 
2215908dc0a2: Pull complete 
08c3cb2073f1: Pull complete 
18f38162c0ce: Pull complete 
10e2168f148a: Pull complete 
c4ffe9532b5f: Pull complete 
Digest: sha256:2275af0f20d71b293916f1958f8497f987b8d8fd8113df54635f2a5915002bf1
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest


$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
ubuntu       latest    825d55fb6340   9 days ago    72.8MB
nginx        latest    12766a6745ee   2 weeks ago   142MB

run에는 이미지가 없으면 Pull하는 기능이 포함되어 있다. 그러나 run이 목적이 아니라 save나 build를 하기 위한 목적인 경우 pull한다.


inspect

이미지 안에 있는 설정이나 환경을 보기 위한 명령이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker image inspect nginx
[
    {
        "Id": "sha256:12766a6745eea133de9fdcd03ff720fa971fdaf21113d4bc72b417c123b15619",
        "RepoTags": [
            "nginx:latest"
        ],
        "RepoDigests": [
            "nginx@sha256:2275af0f20d71b293916f1958f8497f987b8d8fd8113df54635f2a5915002bf1"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2022-03-29T16:02:44
...

이렇게 하면 모든 환경을 출력하는데, 특정 포맷만 출력할 수도 있다.

1
2
3
4
5
6
$ docker image inspect -f '' nginx
[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin NGINX_VERSION=1.21.6 NJS_VERSION=0.7.2 PKG_RELEASE=1~bullseye]


$ docker image inspect -f '' nginx
["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.21.6","NJS_VERSION=0.7.2","PKG_RELEASE=1~bullseye"]

json 포멧으로 보여달라 하면 json 옵션을 추가하면 된다.


save/load

docker에서 큰 용량을 다운받거나 계속 다운/삭제를 반복하게 되면 해당 ip가 불이익을 받을 수 있다. 그래서 자주 사용하는 이미지는 저장해놓는 것이 좋다. save를 하면 stdout형태로 출력되므로 redirection 을 통해 저장하거나 옵션 -o 를 사용한다.

1
2
3
4
5
$ docker image save nginx > docker_nginx_1.21.6.tar

$ file docker_nginx_1.21.6.tar
docker_nginx_1.21.6.tar: POSIX tar archive

image가 tar파일로 저장된다. 작성할 때 inspect를 통해 버전을 확인한 후 저장하는 것이 좋다.


불러올 때는 stdin으로 불러와야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
ubuntu       latest    825d55fb6340   9 days ago    72.8MB
nginx        latest    12766a6745ee   2 weeks ago   142MB

$ docker image rm nginx
Untagged: nginx:latest
Untagged: nginx@sha256:2275af0f20d71b293916f1958f8497f987b8d8fd8113df54635f2a5915002bf1
Deleted: sha256:12766a6745eea133de9fdcd03ff720fa971fdaf21113d4bc72b417c123b15619
Deleted: sha256:3ea962f6f388096ab9798790d363fc6f9c779c924a5eddf5c699d8da080114f7
Deleted: sha256:091a2aef7242e42505b69f1ad027d6a442cfce2403e260ac914f0fd6cc2d275f
Deleted: sha256:4e72a31f1cd6fd655cc0826c91e886967b6e965e13ac21f31f9f66c27a3b7732
Deleted: sha256:e3d1cdf9772a260b3e81a22c1940d63ac45dfe67720f78f00ca73834d9498934
Deleted: sha256:af40da71a8618ea9cbcdc333d5e60bd5b6df820f0d07a55f7c9a1c21fd930095
Deleted: sha256:608f3a074261105f129d707e4d9ad3d41b5baa94887f092b7c2857f7274a2fce

$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
ubuntu       latest    825d55fb6340   9 days ago   72.8MB

$ docker image load < docker_nginx_1.21.6.tar
608f3a074261: Loading layer   83.9MB/83.9MB
ea207a4854e7: Loading layer     62MB/62MB
33cf1b723f65: Loading layer  3.072kB/3.072kB
5c77d760e1f4: Loading layer  4.096kB/4.096kB
fac199a5a1a5: Loading layer  3.584kB/3.584kB
ea4bc0cd4a93: Loading layer  7.168kB/7.168kB
Loaded image: nginx:latest

이 때, image rm 이나 rmi나 동일하게 작동한다.


또 위에서 배운 docker image의 모든 명령어는 다 단축이 가능하다.

docker image load == docker load



run

일단 시작 전에 이미 존재하는 컨테이너를 모두 삭제하는 것이 좋다.

1
2
$ docker ps -a --format '' | xargs docker rm


터미널을 2개 실행해서 각각 docker run을 해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ docker run -it --name ubuntu_top ubuntu "top" "-d 1"
top - 09:59:24 up  1:48,  0 users,  load average: 2.33, 2.46, 2.19
Tasks:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s): 34.5 us,  5.1 sy,  0.0 ni, 60.0 id,  0.0 wa,  0.0 hi,  0.4 si,  0.0 st
MiB Mem :  15680.4 total,   7678.9 free,   3078.9 used,   4922.6 buff/cache
MiB Swap:   6939.0 total,   6939.0 free,      0.0 used.  11506.3 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND    
    1 root      20   0    6088   3208   2704 R   0.0   0.0   0:00.10 top  



ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

$ docker run -it --name ubuntu_top ubuntu "top" "-d 1"
docker: Error response from daemon: Conflict. The container name "/ubuntu_top" is already in use by container "5d31836dac6bfee687ad4bfc050dec2893ab56426518535625a3a752250085df". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.

ubuntu_top이라는 이름의 anscestor이 ubuntu인 컨테이너를 실행하고, 명령어는 top, argument는 딜레이를 1준다는 것이다. 두번째에서 에러가 나는 이유는 동일한 이름을 가진 컨테이너를 만들 수 없기 때문이다.



exec

그 다음으로는 같은 컨테이너에서 2개의 top을 실행시켜보고자 한다. 1번 터미널의 ubuntu_top은 그대로 두고 2번에서 docker ps -a 를 확인해보면 ubuntu_top이 실행중인 것을 확인할 수 있다.

1
2
3
$ docker ps -a
CONTAINER ID   IMAGE     COMMAND        CREATED         STATUS         PORTS     NAMES
5d31836dac6b   ubuntu    "top '-d 1'"   4 minutes ago   Up 4 minutes             ubuntu_top

이 2개의 top을 실행시키기 위해 exec를 사용한다. run은 새롭게 컨테이너를 실행하는 명령이고, exec는 기존에 존재하는 컨테이너에서 실행하는 명령이다.

1
2
3
4
5
6
7
8
9
10
11
$ docker exec -it ubuntu_top top "-d 0.2"
top - 10:04:03 up  1:53,  0 users,  load average: 2.42, 2.44, 2.26
Tasks:   2 total,   1 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  5.0 us,  2.5 sy,  0.0 ni, 92.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  15680.4 total,   7561.3 free,   3139.4 used,   4979.7 buff/cache
MiB Swap:   6939.0 total,   6939.0 free,      0.0 used.  11390.6 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND    
    1 root      20   0    6088   3208   2704 S   0.0   0.0   0:00.24 top        
    7 root      20   0    6088   3156   2652 R   0.0   0.0   0:00.05 top        

이 때 보이는 7번이 방금 실행시킨 exec에 의한 process이다.


이번에는 3번째 터미널에서 bash를 실행시켜본다.

1
2
3
4
5
6
7
8
9
$ docker exec -it ubuntu_top bash

root@5d31836dac6b:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 09:56 pts/0    00:00:00 top -d 1
root         7     0  0 10:03 pts/1    00:00:00 top -d 0.2
root        13     0  0 10:05 pts/2    00:00:00 bash
root        22    13  0 10:05 pts/2    00:00:00 ps -ef

그 후 1번 터미널에서 다시 확인해보면 bash가 추가된 것을 볼 수 있다.

1
2
3
4
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND    
    7 root      20   0    6088   3156   2652 S   1.0   0.0   0:00.42 top        
    1 root      20   0    6088   3208   2704 R   0.0   0.0   0:00.31 top        
   13 root      20   0    4116   3460   2912 S   0.0   0.0   0:00.02 bash  


컨테이너를 종료하기 위해서는 1번 터미널만 닫아도 된다. 1번이 오리지널 프로세스이므로 1번이 꺼지면 나머지는 쫓겨나게 된다.


Binding

컴네이너의 자원을 외부와 연결하는 명령이다. 일반적으로는 I/O 나 storage관련, 또는 환경을 연결한다.

i/o에서는 대표적으로 network을 연결하는데 연결 방식은 2가지가 있다.

  • port binding : host OS의 port 와 컨테이너의 port를 바인딩해서 port를 통해 연결
  • network : docker network를 사용

파일에서는 대표적으로 directory나 file, block device 등을 연결한다. 이에 대해서도 3가지의 방법이 있다.

  • mount binding : host OS의 디렉토리를 바인딩
  • volume : docker volume 저장소를 사용
  • device : host os의 device, gpu를 바인딩

환경에서는 shell environment를 지정해줘서 연결할 수 있다. docker는 컨테이너라서 커스터마이징이 가능하다. 환경 변수를 통해 어떤 값이 들어오면 특정 설정으로 작동한다는 식으로 만들 수 있다.


port binding

port binding은 네트워크 서비스를 사용한다는 것이므로 nginx web server를 사용한다. 그래서 nginx container가 사용할 port를 확인해보고자 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ docker inspect nginx
"Config": {
    "Hostname": "",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "ExposedPorts": {
        "80/tcp": {}
    },
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "NGINX_VERSION=1.21.6",
        "NJS_VERSION=0.7.2",
        "PKG_RELEASE=1~bullseye"
    ],
    "Cmd": [
        "nginx",
        "-g",
        "daemon off;"

config부분을 확인해보면 포트를 80/tcp를 외부에 노출하고 있음을 알 수 있다. 또는 stdin/stdout을 사용하지 않는 것으로 되어 있다. 그 이유는 기본적으로 daemon서비스는 std를 사용하지 않기 때문이다. SIGTTIN/SIGTTOUT을 발생시키지 않기 위해서이다.


2개의 터미널을 통해 사용한다. 1번 터미널에서 run을 한다. 8080이 host의 Port번호 컨테이너의 port번호이다. 2번에서는 listen을 통해 확인한다.

1
2
3
4
5
6
7
8
9
10
11
12
$ docker run --rm -p 8080:80/tcp --name nginx_8080 nginx
...
2022/04/15 10:20:44 [notice] 1#1: start worker process 34
2022/04/15 10:20:44 [notice] 1#1: start worker process 35
2022/04/15 10:20:44 [notice] 1#1: start worker process 36
2022/04/15 10:20:44 [notice] 1#1: start worker process 37
2022/04/15 10:20:44 [notice] 1#1: start worker process 38

$ ss -nlt 'sport = :8080'
State    Recv-Q    Send-Q        Local Address:Port        Peer Address:Port    
LISTEN   0         4096                0.0.0.0:8080             0.0.0.0:*       
LISTEN   0         4096                   [::]:8080                [::]:*

1번 터미널의 옵션

  • –rm : 컨테이너를 껐을 때 자동 삭제
  • -p <> : 뒤에 오는 것은 호스트 port번호와 컨테이너 port번호

2번 터미널의 옵션

  • -n : numeric
  • -l : listen
  • -t : tcp

filter

  • port가 8080인 것만 보여달라


연결이 잘 되어 있는지 확인하기 위해 curl을 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ curl 127.0.0.1:8080
<!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>


동작하고 있는 1번 터미널을 죽이기 위해서는 <CTRL-C>를 누른다.


detach

위에서는 foreground에서 실행되고 있지만, 관리하기 귀찮을 경우 background에 놓고`` 실행시키고자 한다. 대부분 background에 놓고 실행한다.


1
2
3
4
5
6
7
8
$ docker ps -a

$ docker run -d --rm -p 8080:80/tcp --name nginx_8080 nginx
0c501f5680b67dfdbcd9585ea6798b3250e553389652fc416e0c1f3194998001

$ docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS         PORTS                                   NAMES
0c501f5680b6   nginx     "/docker-entrypoint.…"   13 seconds ago   Up 4 seconds   0.0.0.0:8080->80/tcp, :::8080->80/tcp   nginx_8080


현재는 background에서 실행했기 떄문에 로그가 나오지 않는다. 이 로그들을 보기 위해서 다른 터미널을 열어서 확인한다.

1
2
3
4
5
6
7
8
$ docker logs nginx_8080
2022/04/15 10:50:42 [notice] 1#1: start worker process 32
2022/04/15 10:50:42 [notice] 1#1: start worker process 33
2022/04/15 10:50:42 [notice] 1#1: start worker process 34
2022/04/15 10:50:42 [notice] 1#1: start worker process 35
2022/04/15 10:50:42 [notice] 1#1: start worker process 36
2022/04/15 10:50:42 [notice] 1#1: start worker process 37
2022/04/15 10:50:42 [notice] 1#1: start worker process 38

이 때, 추적해서 관찰하고 싶다면 -f를 추가한다.

1
$ docker logs -f nginx_8080


위에서 docker run -it 을 사용할 때, interactive mode와 terminal을 사용하는 경우 container을 running 상태로 두고 잠시 빠져나올 때는 ^P^Q를 통해 detach를 할 수 있다. 또는 처음 실행할 때 detach mode를 추가하면 된다.

1
$ docker run --rm -itd --name ubuntu_bash ubuntu bash

이것만 실행하면 shell로 진입하지 않는다. 그래서 attach를 하여 진입한다.

1
2
3
4
5
6
7
8
9
10
11
12
$ docker attach ubuntu_bash
root@4042467bb952:/# ps
  PID TTY          TIME CMD
    1 pts/0    00:00:00 bash
    9 pts/0    00:00:00 ps

^P^Q를 누르면
root@4042467bb952:/# read escape sequence

$ docker attach ubuntu_bash

root@4042467bb952:/# 


mount

nginx에 외부 디렉토리를 mount해보고자 한다. 그전에 nginx 웹서버가 사용하는 디렉토리 구조를 알아야 한다. 이는 공식 사이트 에 들어가면 정리되어 있다. 설정 파일 내용은 직접 컨테이너로 들어가 보는 것도 좋다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ docker run --rm -it nginx bash

# alias ls='ls --color'

# cd /etc/nginx/conf.d
# ls
default.conf

# more default.conf
server {
  listen      80;
  server_name localhost;

  #charset koi8-r;
  #access_log /var/log/nginx/host.access.log  main;

  location  / {
    root  /usr/share/nginx/html;
    index intex.html  index.htm;l
  }
}

/usr/share/nginx/html이 바로 web document root이다. 여기를 외부 host에서 mount로 binding해야 한다.

host : home/nginx_doc_root -> container : /usr/share/nginx/html 로 바인딩한다.


마운트를 위해 다시 host os로 넘어온다.

1
2
3
4
5
6
7
8
9
$ mkdir ~/nginx_doc_root
$ readlink -f ~/nginx_doc_root
/home/jhyoon/nginx_doc_root

$ echo "Hello Document root dir" > ~/nginx_doc_root/hello.txt
$ ls ~/nginx_doc_root/
hello.txt

$ docker run --rm -d -p 8080:80/tcp -v /home/jhyoon/nginx_doc_root:/usr/share/nginx/html --name nginx_8080 nginx
  • -v host_file:container_file : 지금은 1개만 했지만, 여러 개 지정해줄 수 있다.


위에서 작업했던 터미널 말고 1개더 실행해서 다음을 명령한다.

1
2
3
4
5
6
7
8
9
$ curl 127.0.0.1:8080


$ curl http://127.0.0.1:8080/hello.txt

$ echo "Hello World" >> ~/nginx_doc_root/hello.txt
$ curl http://127.0.0.1:8080/hello.txt

$ docker stop nginx_8080

예전에 있던 파일에 바인딩했으므로 그 파일은 보이지 않고, 내가 생성한 파일로 출력된다. 이에 >>를 통해 내용을 더 추가했다. 컨테이너가 죽으면 --rm을 설정했기 때문에 바로 삭제된다.


-v 대신 –mount를 사용할 수도 있다.

1
$ docker run --rm -d -p 8080:80/tcp --mount type=bind,src=/home/jhyoon/nginx_doc_root,dst=/usr/share/nginx/html -- name nginx_8080 nginx

두 방법은 동일하다. 단지 직관성이 다를 뿐이므로 마음에 드는 것을 사용하면 된다.


Environment variables

환경 변수 관리이다.

  • –env KEY=value
  • –env-file env_file
1
2
3
4
5
6
7
$ docker run --rm -it --name ubuntu_bash ubuntu bash
# echo $LANG
# exit

$ docker run --rm -it -e LANG=en_US.utf8 --name ubuntu_bash ubuntu bash
# echo $LANG
en_US.utf8

–env를 사용해도 되지만, -e로 사용해도 된다.



stop/start

docker stop/start는 어떤 경우에 사용하나?

  • stop
    • detach mode로 실행중이라면, 정지시키기 위해서 외부에서 docker stop으로 정지한다.
    • -it를 사용하지 않는 시스템은 signal이나 docker stop으로 정지할 수 밖에 없다.
  • start
    • docker run –rm을 쓰지 않는 경우 exit를 하면 container가 남아있어서 stop 후 재시작할 때 start로 가능하다.


1
2
3
4
5
6
7
8
9
10
$ docker run -d -p 8080:80/tcp --mount type=bind,src=/home/jhyoon/nginx_doc_root,dst=/usr/share/nginx/html -- name nginx_8080 nginx

$ docker ps


$ docker stop nginx_8080 && docker ps -a

$ docker start nging_8080 && docker ps

$ docker rm ID



Docker Compose

docker를 실행시킬 때 설정들을 저장해서 만들어놓을 수 있다. 파일의 이름은 docker-compose.yml 이다. 이를 통해 매번 길게 치지 않아도 되기에 편리하다.


1
docker-compose [-f <arg>...] [--profile <name>...] [options] [COMMAND] [ARGS...]
  • -f
    • config : yaml filetype
    • default filename : docker-compose.yml
    • docker-compose 파일을 사용할 때는 안써도 되지만, 다른 파일을 지정하고 싶은 경우 사용

compose를 할 경우 알아서 build, rebuild, create, start 까지 해준다.

더 궁금한 내용이 있다면 help(-h)를 사용한다.

1
$ docker-compose -h


docker-compose 설치

1
$ sudo apt -y install docker-compose

docker-compose 설정

설치 완료 후 간단한 설정을 만들어보자. 먼저 추후 편리하게 사용하기 위해 docker-compose를 작업할 디렉토리를 만들 것이다.

1
2
3
$ mkdir ~/docker-compose
$ cd ~/docker-compose
$ vim docker-compose.yml


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version: '3'
services:
  nginx_8080:
    image: nginx
    restart: always
    hostname: nginx1
    container_name: cmp_nginx1_1
    network:
      mynet:
        ipv4_address: 172.20.0.10
    ports:
      - 8080.80    
    volume:
      - /home/jhyoon/nginx_doc_root:/usr/share/nginx/html

networks:
  mynet:
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/24

compose사이트

  • version : 2, 3 등 원하는 것으로 쓰면 되지만, 3을 가장 많이 쓴다
  • nginx_8080 : 서비스의 이름
  • image : 이미지, 버전 명이 있는 경우 옆에 적으면 된다.
  • restart : 해당컨테이너가 종료되면 자동으로 재시작
  • hostname : 호스트 이름
  • container_name : 이를 지정하지 않으면 directory이름을 prefix로 자동 생성된다.
  • network : 쓸 network이름, 이는 아래에 선언함.
  • port : port binding, hostport.containerport
  • volume : mount binding, hostdir/containerdir


docker-compose 실행

1
$ docker-compose up


다른 터미널 창에서 다음을 명령한다.

1
2
3
4
5
6
7
8
$ pwd
/home/jhyoon/docker-compose

$ docker-compose ps

$ docker ps

$ curl http://127.0.0.1:8080/hello.txt

이를 detach로 실행하려면 -d를 추가해야 한다.

1
2
3
4
5
6
7
$ docker-compose up -d

$ docker-compose ps

$ docker-compose stop

$ docker-compose ps


web 서버를 2개 실행

2개를 실행하기 위해 docker-compose2.yml 파일을 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
version: '3'
services:
  nginx_8080:
    image: nginx
    restart: always
    hostname: nginx1
    container_name: cmp_nginx1_1
    network:
      mynet:
        ipv4_address: 172.20.0.10
    ports:
      - 8080:80    
    volume:
      - /home/jhyoon/nginx_doc_root:/usr/share/nginx/html

  nginx_8081:
    image: nginx
    restart: always
    hostname: nginx2
    container_name: cmp_nginx2_1
    network:
      mynet:
        ipv4_address: 172.20.0.20
    ports:
      - 8081:80    
    volume:
      - /home/jhyoon/nginx2_doc_root:/usr/share/nginx/html

networks:
  mynet:
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/24
...

여기서 DB나 API서버를 사용하여 container 끼리 서로 접속하려면 hostname을 설정해줘야 하고, 이런 경우에는 /etc/hosts에 서버를 등록한다.

1
2
172.20.0.10 nginx1.domainname
172.20.0.20 nginx2.domainname


2개를 실행할 때는 -f를 추가해야 한다.

1
2
3
4
5
6
7
$ docker-copmose -f compose-compose2.yml up -d



$ docker-copmose -f compose-compose2.yml ps



network

1
2
3
4
5
6
7
8
$ docker network ls


$ docker-compose -f docker-compose2.yml stop
$ docker network rm dockercompose_mynet

$ docker-compose -f docker-compose2.yml up -d

실행을 하면 에라가 뜬다. 왜냐하면 network가 재생성되면서 id 값이 달라지기 때문이다. 따라서 재생성해야 한다.

1
$ docker-compose -f docker-compose2.yml up --force-recreate -d



wsl2에 systemd 사용하기

wsl2에 도커를 사용하기 위해서는 설정을 해줘야 한다.

1
git clone https://github.com/DamionGans/ubuntu-wsl2-systemd-script.git
1
cd ubuntu-wsl2-systemd-script/
1
bash ubuntu-wsl2-systemd-script.sh

이 후, wsl을 닫고, powershell에 들어가서 wsl --shutdown을 한다. 그리고 wsl을 다시 실행한다.

만약 이 때, cannot execute daemonize to start systemd.라는 에러가 뜬다면, powershell을 다시 실행한다. 그리고 wsl에 접속해야 한다.

1
wsl -u root


그리고 daemonize 패키지가 있는지 확인한다.

1
sudo apt install daemonize

없으면 설치하고, 있다면 삭제하고 재설치한다.


설치가 끝나면, wsl을 빠져나와 다시 wsl --shutdown 한다. 그리고 wsl을 실행하면 systemctl을 사용할 수 있게 될 것이다.

  • reference
    • https://velog.io/@guswns3371/wsl2-%EC%97%90%EC%84%9C-systemctl-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
This post is licensed under CC BY 4.0 by the author.