도커 네트워크

도커 네트워크

도커에서 제공하는 대표적인인 네트워크 드라이버로는 호스트(host), 브리지(bridge), 사용안함(none) 등이 있습니다.

$ docker network ls

NETWORK ID          NAME                DRIVER              SCOPE
d4c7abefb75d        bridge              bridge              local
8c897fb9e7da        host                host                local
9fb15fe19162        none                null                local

도커의 기본 네티워크 모드는 bridge 입니다. 만약 다른 모드를 사용하여 컨테이너를 생성하고 싶다면 --net 을 이용하여 설정할 수 있습니다.

docker run --net=<NETWORK>

도커를 설치하면, 기본적으로 docker0 이라는 가상 브리지(bridge)가 생성 됩니다.

$ ifconfig

...
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:6c:3d:0f:04  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
$ ip addr

...
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:6c:3d:0f:04 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

이 브리지는 컨테이너를 기본 브리지 모드로 실행할 때 사용되면, CIRD 표기법으로 172.17.0.1/16 의 주소 범위를 가지고 있습니다. 172.17.0.1 부터 172.17.255.254 까지의 아이피를 사용할 수 있습니다. 그래서 컨테이너가 기본 브리지 모드로 실행될 때, 해당 범위에서 아이피를 할당받습니다.

만약 이 범위를 변경하고 싶다면, 도커 설정 파일인 /etc/docker/daemon.json"bip" 항목을 추가 하면 됩니다.


Bridge Mode Networking

Docker는 연결된 다른 네트워크 인터페이스 간에 패킷을 자동으로 전달하는 가상 이더넷 브리지인 docker0을 생성합니다. 기본적으로 호스트의 모든 컨테이너는 이 브리지를 이용하여 내부 네트워크에 연결이 됩니다. 이 모드는 컨테이너를 분리된 네트워크 네임스페이스에 배치하고, 네트워크 주소 변환을 사용하여 여러 컨테이너 간에 호스트의 외부 IP 주소를 공유합니다.

브리지 모드 네트워킹은 동일한 호스트에서 여러 컨테이너를 실행할 때 네트워크 포트 충돌을 일으키지 않습니다. 즉, 동일한 포트를 사용하는 다수의 컨테이너를 하나의 호스트에서 실행할 수 있습니다. 각 컨테이너는 호스트와 분리된 전용 네트워크 네임스페이스를 소유하고 있습니다. 그래서 이 모드는 NAT의 사용으로 인해 네트워크 처리량과 지연 시간에 영향을 미치고, 호스트와 컨테이너 간의 네트워크 포트 매핑을 제어해야하는 단점이 있습니다.

컨테이너가 생성되면, 해당 컨테이너를 위해서 페어 인터페이스(pair interfaces)가 생성됩니다. 이 인터페이스들은 두 개가 한 쌍으로 구성되어 있는데, 마치 직접 연결된 것 처럼 서로 패킷을 주고 받습니다.

컨테이너가 생성되면, 페어 인터페이스의 한쪽은 컨테이너 내부 네임스페이스에 eth0 이라는 이름으로 할당됩니다. 나머자 하나는 vethXXXX 라는 이름으로 docker0 브리지에 바인딩 됩니다.

컨테이너를 실행할 때 브리지 네트워킹 모드를 사용하려면 별다른 설정을 추가할 필요 없습니다. 기본값이 브리지 네트워킹 모드이기 때문입니다.

docker run -i -t --rm --name network_bridge ubuntu:18.04

정상적으로 실행되면, 쉘이 나타나고, 명령어를 입력할 수 있습니다. 우분투 이미지에서 네트워크 관련 도구가 설치되어 있지 않기 때문에, 필요한 도구들을 설치해 줍니다.

apt-get update
apt-get install net-tools
apt-get install iproute2
root@6a5f6efd7f52:/# ifconfig

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 3899  bytes 19574414 (19.5 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3318  bytes 224386 (224.3 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
root@6a5f6efd7f52:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

컨테이너 내부의 eth0 인터페이스의 번호가 4번인것을 확인 할 수 있습니다.

새로운 터미널을 열어서, 호스트에서 인터페이스를 조회해 보겠습니다.

root@magi:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:15:0e:00 brd ff:ff:ff:ff:ff:ff
    inet 192.168.21.39/24 brd 192.168.21.255 scope global dynamic eth0
       valid_lft 4714sec preferred_lft 4714sec
    inet6 fe80::215:5dff:fe15:e00/64 scope link
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:6c:3d:0f:04 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:6cff:fe3d:f04/64 scope link
       valid_lft forever preferred_lft forever
5: vethab05419@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether 96:0e:66:36:cd:d0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::940e:66ff:fe36:cdd0/64 scope link
       valid_lft forever preferred_lft forever

vethab05419 라는 새로운 인터페이스 생성된 것을 확인할 수 있습니다. vethab05419 와 컨테이너안의 eth0 인터페이스가 맺어져 있다는 것을 ethtool 을 이용하여 확인할 수 있습니다.

root@magi:~# ethtool -S vethab05419

NIC statistics:
     peer_ifindex: 4

peer_ifindex 가 4로 설정되어 있습니다. 앞서 컨테이너 안에서 확인한 eth0 인터페이스의 번호인 것을 알 수 있습니다.

컨테이너 게이트웨이

컨테이너의 게이트웨이를 확인해 보겠습니다. 컨테이너 안에서 route 명령어를 실행합니다.

root@6a5f6efd7f52:/# route

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

출력된 결과처럼 컨테이너 내부의 모든 패킷은 default 인 172.17.0.1 로 가게 됩니다. 이 주소는 docker0 의 IP 입니다.

브리지 모드에 대한 자세한 정보를 얻고 싶다면, 다음과 같이 확인할 수 있습니다.

docker network inspect bridge

[
    {
        "Name": "bridge",
        "Id": "022cf8e85d00a623504732098d6c99f3a6bf74fbd632787b4d3bf70a1ad03256",
        "Created": "2020-07-28T11:56:57.739214Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "6a5f6efd7f5235d46d1ae332b003566494bb0e0255bf5edc05899b2e7c71191b": {
                "Name": "network_bridge",
                "EndpointID": "53666d16b8a932aaef1f241535ae2944758ad40cbbddb923d1a1621a286b3e2e",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

브리지 생성하기

브리지는 다음 명령어로 생성할 수도 있습니다.

docker network create --driver bridge <브리지 이름>

다음은 mybridge 라는 이름의 브리지를 생성하는 예제입니다.

docker network create --driver bridge mybridge 

생성한 브리지는 컨테이너를 실행할 때 --net 설정을 통해 사용할 수 있습니다.

docker run -i -t --name mybridge_container --net mybrdige ubuntu:18.04

Host Mode Networking

호스트 모드는 컨테이너가 호스트의 네트워킹 네임스페이스를 공유하고 있으며, 외부 네트워크에 직접 노출됩니다. 호스트의 IP 주소와 호스트의 TCP 포트 공간을 사용하여, 컨테이너 내부에서 실행 중인 서비스를 노출합니다.

컨테이너를 실행할 때 호스트 네트워킹 모드를 사용하려면 다음과 같이 --net=host 라고 설정하면 됩니다.

$ docker run -i -t --rm --net=host --name network_host ubuntu:18.04

이 네트워킹 모드는 간단하기 때문에, 개발자가 이해하기 쉽고, 사용하기 쉽습니다. 하지만 호스트 네트워크를 그대로 사용하기 때문에 동일한 네트워크 포트를 사용할 경우 충돌이 발생합니다. 동일한 포트를 사용하는 다수의 컨테이너를 하나의 호스트에서 실행할 경우, 포트 충돌이 발생하여 서비스가 시작되지 않을 수 있습니다.

apt-get update
apt-get install net-tools
apt-get install iproute2
root@docker-desktop:/# ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:e3ff:fe89:ac76  prefixlen 64  scopeid 0x20<link>
        ether 02:42:e3:89:ac:76  txqueuelen 0  (Ethernet)
        RX packets 6303  bytes 257694 (257.6 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 13671  bytes 20016034 (20.0 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.65.3  netmask 255.255.255.0  broadcast 192.168.65.255
        inet6 fe80::50:ff:fe00:1  prefixlen 64  scopeid 0x20<link>
        ether 02:50:00:00:00:01  txqueuelen 1000  (Ethernet)
        RX packets 146158  bytes 147055882 (147.0 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 77952  bytes 6736462 (6.7 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 2  bytes 140 (140.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2  bytes 140 (140.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

호스트 모드에 대한 자세한 정보를 얻고 싶다면, 다음과 같이 확인할 수 있습니다.

docker network inspect host

[
    {
        "Name": "host",
        "Id": "8c897fb9e7da39ec5c0cbceaf25935d9fd32cd9cca5eea5a665085eb7a793070",
        "Created": "2019-01-04T16:21:19.013894618Z",
        "Scope": "local",
        "Driver": "host",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": []
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

None Mode Networking

none은 말 그대로, 네트워크를 사용하지 않는 다는것을 의미합니다. none 네트워크로 설정을 하면, 컨테이너에는 lo 인터페이스만 나타납니다. 이 모드로 설정된 컨테이너는 외부와 단절 됩니다.

docker run -i -t --rm --name network_none --net none ubuntu:18.04


Container Mode Networking

컨테이너 네트워크를 사용하면, 다른 컨테이너의 네트워크 환경을 공유할 수 있습니다.

--net container:<다른 컨테이너 이름 또는 아이디>
docker run -i -t --rm --name network_container_1 ubuntu:18.04

root@1fae52d25cbd:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 26111  bytes 38303216 (38.3 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 9567  bytes 526098 (526.0 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

다음과 같이 ifconfig 명령을 실행해보면, 동일한 네트워크를 사용하고 있다는 것을 확인할 수 있습니다.

docker run -i -t --rm --name network_container_2 --net container:network_container_1  ubuntu:18.04

root@1fae52d25cbd:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 13034  bytes 19150595 (19.1 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4542  bytes 250008 (250.0 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

도커 구조

도커 구조

도커 초기에는 LXC(LinuX Container)를 기반으로 구현하였습니다. 그리고 0.9 버전부터 LXC를 대신한는 libcontainer를 개발하여 사용하고 있습니다.

다음 그림은 도커 0.9 버전의 구조입니다.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/2c21284c-197c-4393-8448-a00d37533ecc/docker-execdriver-diagram.png

출처 : https://www.docker.com/blog/docker-0-9-introducing-execution-drivers-and-libcontainer/

도커 1.11 버전부터 containerdrunC 를 컨테이너 런타임으로 사용합니다. 앞서 사용한 libcontainer 프로젝트는 OCI에 기부되었고, runc 라는 이름으로 바뀌게 됩니다.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5af65736-5226-47ae-9c67-af23bc193744/docker-containerd.png

dockerd

dockerd 는 컨테이너를 지속적으로 관리하는 데몬 프로세스로서, docker CLI 같은 클라이언트가 사용할 수 있는 RESTful API를 제공하고 있습니다. 흔히 명령어로 사용하는 docker 실행 파일이 docker CLI 입니다. 사용자가 입력한 docker 명령어는, 이 dockerd 로 전달되고, 실행됩니다.

dockerd는 unix, tcp, fd의 세 가지 소켓 유형을 통해, 도커 API 요청을 수신할 수 있습니다.

containerd

containerd는 이미지를 push 하고 pull 하고, 스토리지를 관리하고, 네트워크 기능을 정의할 수 있는 독립 실행형 고수준(high-level) 컨테이너 런타임입니다. runc 같은 저수준(low-level)의 컨테이너 런타임에 해당 명령을 전달하여, 컨테이너를 실행하는 등의 라이프사이클을 관리합니다.

도커에 의해 빠르게 확산되고 있던 컨테이너 환경에서, 컨테이너 런타임을 특정 벤더에 의존하지 않고, 중립적인 입장에서 컨테이너 표준에 맞게 구현하는 것을 목적으로 만들어졌습니다.

Docker Inc는 2016 년 12 월 컨테이너 런타임 부분을 독립적인 오픈 소스 프로젝트인 containerd 로 분리하여, 마이크로 소프트, Google, AWS, IBM 등과 공동으로 개발하기로 발표하였습니다.

그리고, 2017년 3월에는 CNCF (Cloud Native Computing Foundation)에 기부되었고, 이후 이를 담당해왔습니다.

도커는 1.11 이후 버전부터 containerd를 컨테이너 런타임으로 사용하고 있습니다.

containerd-shim

containerd-shimrunc를 실행하고, 컨테이너 프로세스를 제어하는 경량 데몬입니다. 컨테이너와 containerd 의 모든 통신은 containerd-shim 을 통해서 이루어 집니다.

containerd-shim 은 보통 다음과 같은 역할을 담당합니다.

  • 컨테이너의 stdout 및 stderr의 스트림을 제공해 주고 있습니다. 그래서 containerd 가 재시작 중에도 문제가 발생하지 않습니다. containerd 는 stdout 및 stderr의 스트림을 받아서 로그 파일로 저장을 할 수 있습니다.
  • runc 는 컨테이너 프로세스를 실행(fork)한 다음, 포그라운드 프로세스를 종료하여, 컨테이너 프로세스를 의도적으로 데몬화 합니다. 이렇게 되면, 컨테이너 프로세스는 호스트의 init 프로세스가 담당하게 되어서, 컨테이너의 관리가 어려워집니다. 이 문제를 해결하기 위해 shim 프로세스를 subreaper로 만들어서, 컨테이너 프로세스를 shim 프로세스가 관리하도록 합니다.

runc

OCI 런타임 스펙을 구현하고 있는 저수준 컨테이너 런타임입니다. 저수준 컨테이너 런타임이라고 부르는 이유는, 오직 실행 중인 컨테이너 관리에만 그 범위를 집중시키고 있기 때문입니다. 리눅스 커널의 네임스페이스와 cgroups 을 사용하여 격리시키는 기능을 제공합니다. 컨테이너를 생성(spawning)과 실행(running) 할 수있는 CLI로 구현되어 있습니다.

runc 는 도커 프로젝트(이전 이름은 libcontainer)에서 나와, OCI에 기부되었고, 이후 이를 담당해 왔습니다.

dockerdcontainerd 확인해 보기

도커가 설치된 호스트에서 프로세스를 조회해 보면, dockerdcontainerd 가 작동중인 것을 확인할 수 있습니다.

다음은 우분투 18.04 버전에 설치한 도커 19.03.12 버전인 경우 조회해 본 결과입니다.

dockerd 를 조회해 보았습니다.

sudo systemctl status docker.service
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2020-07-25 06:31:07 UTC; 3h 11min ago
     Docs: <https://docs.docker.com>
 Main PID: 3375 (dockerd)
    Tasks: 8
   CGroup: /system.slice/docker.service
           └─3375 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Jul 25 06:31:07 magi dockerd[3375]: time="2020-07-25T06:31:07.242010835Z" level=warning msg="Your kernel does not support swap memory lim
Jul 25 06:31:07 magi dockerd[3375]: time="2020-07-25T06:31:07.242240538Z" level=warning msg="Your kernel does not support cgroup rt perio
Jul 25 06:31:07 magi dockerd[3375]: time="2020-07-25T06:31:07.242342040Z" level=warning msg="Your kernel does not support cgroup rt runti
Jul 25 06:31:07 magi dockerd[3375]: time="2020-07-25T06:31:07.242520743Z" level=info msg="Loading containers: start."
Jul 25 06:31:07 magi dockerd[3375]: time="2020-07-25T06:31:07.430146872Z" level=info msg="Default bridge (docker0) is assigned with an IP
Jul 25 06:31:07 magi dockerd[3375]: time="2020-07-25T06:31:07.580818585Z" level=info msg="Loading containers: done."
Jul 25 06:31:07 magi dockerd[3375]: time="2020-07-25T06:31:07.630878720Z" level=info msg="Docker daemon" commit=48a66213fe graphdriver(s)
Jul 25 06:31:07 magi dockerd[3375]: time="2020-07-25T06:31:07.636328410Z" level=info msg="Daemon has completed initialization"
Jul 25 06:31:07 magi systemd[1]: Started Docker Application Container Engine.
Jul 25 06:31:07 magi dockerd[3375]: time="2020-07-25T06:31:07.677294494Z" level=info msg="API listen on /var/run/docker.sock"

containerd 를 조회해 보았습니다.

sudo systemctl status containerd.service
containerd.service - containerd container runtime
   Loaded: loaded (/lib/systemd/system/containerd.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2020-07-25 06:31:05 UTC; 3h 11min ago
     Docs: <https://containerd.io>
 Main PID: 3118 (containerd)
    Tasks: 9
   CGroup: /system.slice/containerd.service
           └─3118 /usr/bin/containerd

Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.738189803Z" level=info msg="loading plugin "io.containerd.grpc.v1.images
Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.738198104Z" level=info msg="loading plugin "io.containerd.grpc.v1.leases
Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.738208904Z" level=info msg="loading plugin "io.containerd.grpc.v1.namesp
Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.738217304Z" level=info msg="loading plugin "io.containerd.internal.v1.op
Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.744531308Z" level=info msg="loading plugin "io.containerd.grpc.v1.snapsh
Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.744556608Z" level=info msg="loading plugin "io.containerd.grpc.v1.tasks"
Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.744565509Z" level=info msg="loading plugin "io.containerd.grpc.v1.versio
Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.744574009Z" level=info msg="loading plugin "io.containerd.grpc.v1.intros
Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.745122618Z" level=info msg=serving... address="/run/containerd/container
Jul 25 06:31:05 magi containerd[3118]: time="2020-07-25T06:31:05.745135418Z" level=info msg="containerd successfully booted in 0.165150s"

dockerdcontainerd 가 분리되어 있기 때문에, 도커 버전을 올릴 때 재시작 하여도, 컨테이너의 재시작 없이 사용할 수 있습니다.

컨테이너 실행 과정 살펴보기

pstree 명령어를 이용하여, 프로세스를 트리 모양으로 확인해 보겠습니다.

sudo pstree

실행중인 컨테이너가 없다면, 다음과 같은 결과를 확인할 수 있습니다.

systemd─┬─accounts-daemon───2*[{accounts-daemon}]
        ├─atd
        ├─containerd───8*[{containerd}]
        ├─cron
...

nginx 컨테이너를 실행해 보겠습니다.

sudo docker run -d --name nginx nginx:latest

docker ps 명령어를 실행하여, 실행중인 컨테이너를 확인할 수 있습니다.

sudo docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
79c0a9468509        nginx:latest        "/docker-entrypoint.…"   5 seconds ago       Up 4 seconds        80/tcp              nginx

pstree 명령어를 이용하여, 프로세스를 트리 모양으로 확인해 보겠습니다.

sudo pstree

containerd의 자식 프로세스로 containerd-shim 이 생성된 것을 알 수 있습니다.

systemd─┬─accounts-daemon───2*[{accounts-daemon}]
        ├─atd
        ├─containerd─┬─containerd-shim─┬─nginx───nginx
        │            │                 └─9*[{containerd-shim}]
        │            └─8*[{containerd}]
        ├─cron
        ├─dbus-daemon

앞서 살펴본, docker run 을 이용한 nginx 컨테이너의 실행 과정을 정리하면 다음과 같습니다.

  • docker 명령어, 즉 docker CLI 를 실행하면, dockerd 로 요청을 전달합니다.
  • dockerd는 gRPC를 통해 containerd 요청을 전달합니다.
  • containerdexec를 통해, containerd-shim 을 자식으로 생성합니다.
  • containerd-shimrunc 를 이용하여, 컨테이너를 생성하고 실행합니다.
  • runc 는 컨테이너가 정상적으로 실행되면 종료됩니다.
  • containerd-shim 은 컨테이너에서 실행되는 프로세스의 부모가 됩니다.

도커에는 컨테이너 안에 프로세스를 새로 실행할 수 있는 docker exec 라는 명령어가 있습니다.

다음 명령어를 실행하여, nginx 컨테이너에 bash 쉘을 추가로 실행해 보겠습니다.

sudo docker exec -it nginx bash

nginx 컨테이너 bash 쉘이 실행되고, -it 옵션을 사용했기 때문에 터미널에서 쉘을 이용할 수 있습니다.

다른 터미널을 열어서, pstree 를 실행해 보겠습니다.

pstree
systemd─┬─accounts-daemon───2*[{accounts-daemon}]
        ├─atd
        ├─containerd─┬─containerd-shim─┬─bash
        │            │                 ├─nginx───nginx
        │            │                 └─9*[{containerd-shim}]
        │            └─8*[{containerd}]
        ├─cron
...

containerd-shim 프로세스의 자식 프로세스로 bash 프로세스가 추가된 것을 확인할 수 있습니다.

참고 문서

우분투에 Docker 설치하기

Ubuntu Bionic 18.04 (LTS) 에 도커 설치하기

사전 확인

이전 버전 삭제하기

만약 이전 버전의 도커가 설치되어 있다면 먼저 삭제해야합니다. 패키지명은 docker, docker.io 또는 docker-engine 입니다

sudo apt-get remove docker docker-engine docker.io containerd runc

도커 설치하기

리포지토리를 이용하여 설치하기

새로운 호스트 시스템에, 처음으로 도커 엔진을 설치하기 위해서는, 도커 리포지토리를 설정해야 합니다. 설정 후후 리포지토리를 이용하여 도커를 설치하고 업데이트할 수 있습니다.

리포지토리 설정하기

https를 통해 리포지토리를 사용할 수 있도록, 적절한 패키지 인덱스 및 설치 패키지 업데이트 합니다.

sudo apt-get update

sudo apt-get install \\
    apt-transport-https \\
    ca-certificates \\
    curl \\
    gnupg-agent \\
    software-properties-common

도커의 공식 GPG key를 추가합니다

curl -fsSL <https://download.docker.com/linux/ubuntu/gpg> | sudo apt-key add -

도커를 설치하기 위한 리포지토리를 추가합니다.

sudo add-apt-repository \\
   "deb [arch=amd64] <https://download.docker.com/linux/ubuntu> \\
   $(lsb_release -cs) \\
   stable"

도커 설치하기

apt 패키지 인덱스를 업데이트하고, 도커 엔진과, containerd 를 설치합니다.

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli 

도커 확인하기

docker 를 일반 사용자 계정에서 사용하려면, docker 그룹에 사용자 계정을 추가해 줘야합니다.

sudo usermod -aG docker <your-username>

로그 아웃을 한 다음, 다시 접속을 해야 설정이 적용됩니다.

docker info 명령어를 실행하여, 설치된 도커의 정보를 확인할 수 있습니다.

docker info

정상적으로 실행되면, 다음과 같은 결과를 확인할 수 있습니다.

Client:
 Debug Mode: false

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 Server Version: 19.03.12
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd
 init version: fec3683
 Security Options:
  apparmor
  seccomp
   Profile: default
 Kernel Version: 4.15.0-76-generic
 Operating System: Ubuntu 18.04.4 LTS
 OSType: linux
 Architecture: x86_64
 CPUs: 1
 Total Memory: 3.852GiB
 Name: magi
 ID: 53WF:R22P:ZLLX:5564:H5MV:2WDA:S7UK:CK5Y:G647:TH6W:D74Z:UPDZ
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: <https://index.docker.io/v1/>
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No swap limit support

PC에 kubeflow 설치하기 – 3부 kubeflow 설치하기

Service Account Token Volume Projection 활성화

kubeflow에서는 인증/권한 기능을 위해서 istio 를 사용합니다. 그래서 istio-system 이라는 네임스페이스에 istio 관련 컴포넌트가 설치됩니다. 그 중에 하나인 istio-ingressgateway 포드의 내용을 보면 다음과 같은 부분을 발견할 수 있습니다.

...
  volumes:
  - name: istio-token
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          audience: istio-ca
          expirationSeconds: 43200
          path: istio-token
...

바로 Service Account Token Volume Projection 이라는 것입니다. 이 기능은 쿠버네티스 1.15에서 비활성화 되어 있습니다. 그래서 해당 기능을 사용하기 위해서는 활성화 해줘야 합니다.

기능을 활성화 하기 위해서는, 다음과 같이 kube-apiserver 매니페스트에 몇 가지 플래그를 추가해야합니다. 매니페스트 파일은 /etc/kubernetes/manifests/kube-apiserver.yaml에 위치합니다.

$ sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
...
        - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
        - --service-account-issuer=api
        - --service-account-api-audiences=api,vault

매니페스트 파일을 수정하고, kube-apiserver 포드가 자동으로 다시 시작됩니다.

dynamic volume provisioner 설치

kubeflow를 쉽게 설치하기 위해서는 동적 볼륨 프로비져너(dynamic volume provisioner)가 필요합니다. 이 글에는 로컬 디렉토리를 이용하는 Local Path Provisioner 를 사용하겠습니다.

다음은 Local Path Provisioner 를 설치하는 명령어입니다.

$ kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml

namespace/local-path-storage created
serviceaccount/local-path-provisioner-service-account created
clusterrole.rbac.authorization.k8s.io/local-path-provisioner-role created
clusterrolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
deployment.apps/local-path-provisioner created
storageclass.storage.k8s.io/local-path created
configmap/local-path-config created

스토리지 클래스(storage class)를 조회해 보겠습니다.

$ kubectl get storageclass
NAME         PROVISIONER             AGE
local-path   rancher.io/local-path   63s

kubeflow는 기본 스토리지 클래스를 사용하기 때문에, local-path 스토리지 클래스를 기본 클래스로 설정해야합니다..

다음은 기본 스토리지 클래스를 설정하는 명령어입니다.

$ kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

storageclass.storage.k8s.io/local-path patched

다시 스토리지 클래스를 조회해 보면, 기본 클래스가 설정된 것을 확인할 수 있습니다.

$ kubectl get sc
NAME                   PROVISIONER             AGE
local-path (default)   rancher.io/local-path   11m

kubeflow 설치하기

kubeflow를 설치하기 위해서, kftctl 릴리즈 페이지에서 다운로드 합니다. 이 글을 쓰는 시점에서는 v1.0버전이 최신이므로, v1.0 버전을 기준으로 설명하겠습니다.

$ mkdir ~/kubeflow
$ cd ~/kubeflow

$ curl -L -O https://github.com/kubeflow/kfctl/releases/download/v1.0/kfctl_v1.0-0-g94c35cf_linux.tar.gz

다운 받은 tar ball 을 풉니다.

$ tar -xvf kfctl_v1.0-0-g94c35cf_linux.tar.gz

kubflow 배포를 쉽게 하기 위해서, 다음과 같은 환경 변수들을 생성합니다. 환경 변수들의 자세한 내용은 해당 페이지를 확인 하시기 바랍니다.

export PATH=$PATH:"/home/kangwoo/kubeflow"

export CONFIG_URI="https://raw.githubusercontent.com/kubeflow/manifests/v1.0-branch/kfdef/kfctl_istio_dex.v1.0.1.yaml"


export KF_NAME=kf-test

export BASE_DIR=/home/kangwoo/kubeflow
export KF_DIR=${BASE_DIR}/${KF_NAME}

kfctl_existing_arrikto.yaml 설정 파일을 가지고, kubeflow를 배포하겠습니다. 해당 파일에는 다중 유저와 권한/인증 기능을 지원하는 부분이 정의 되어 있습니다.

mkdir -p ${KF_DIR}
cd ${KF_DIR}

# Download the config file and change the default login credentials.
wget -O kfctl_istio_dex.yaml $CONFIG_URI
export CONFIG_FILE=${KF_DIR}/kfctl_istio_dex.yaml

kfctl apply -V -f ${CONFIG_FILE}

kfctl apply 명령어를 실행하면, kubeflow가 설치되기 시작합니다.

다음은 kubeflow 네임스페이스와, istio-system 네임스페이스의 포드를 조회해 본 것입니다.

$ kubectl -n kubeflow get pod
NAME                                                           READY   STATUS      RESTARTS   AGE
admission-webhook-deployment-7b7888fc9b-9dlj9                  1/1     Running     0          2d23h
application-controller-stateful-set-0                          1/1     Running     0          2d23h
argo-ui-7ffb9b6577-xqcxc                                       1/1     Running     0          2d23h
centraldashboard-6944c87dd5-sfqc7                              1/1     Running     0          2d23h
jupyter-web-app-deployment-679d5f5dc4-6278n                    1/1     Running     0          2d23h
katib-controller-7f58569f7d-62pgx                              1/1     Running     1          2d23h
katib-db-manager-54b66f9f9d-wsxv5                              1/1     Running     5          2d23h
katib-mysql-dcf7dcbd5-4fpmj                                    1/1     Running     0          2d23h
katib-ui-6f97756598-dk8fz                                      1/1     Running     0          2d23h
metadata-db-65fb5b695d-b92zm                                   1/1     Running     0          2d23h
metadata-deployment-65ccddfd4c-242t7                           1/1     Running     1          2d23h
metadata-envoy-deployment-7754f56bff-6vftm                     1/1     Running     0          2d23h
metadata-grpc-deployment-7557fdc6bb-n7jd7                      1/1     Running     7          2d23h
metadata-ui-7c85545947-wwkh9                                   1/1     Running     0          2d23h
minio-69b4676bb7-zglc6                                         1/1     Running     0          2d23h
ml-pipeline-5cddb75848-tjbh9                                   1/1     Running     1          2d23h
ml-pipeline-ml-pipeline-visualizationserver-7f6fcb68c8-pw529   1/1     Running     0          2d23h
ml-pipeline-persistenceagent-6ff9fb86dc-ghj74                  1/1     Running     3          2d23h
ml-pipeline-scheduledworkflow-7f84b54646-mztnf                 1/1     Running     0          2d23h
ml-pipeline-ui-6758f58868-qc72b                                1/1     Running     0          2d23h
ml-pipeline-viewer-controller-deployment-745dbb444d-2xwdn      1/1     Running     0          2d23h
mysql-6bcbfbb6b8-6qb6p                                         1/1     Running     0          2d23h
notebook-controller-deployment-54f455c5c9-rpv8s                1/1     Running     0          2d23h
profiles-deployment-6fcb86d54c-9pdrs                           2/2     Running     0          2d23h
pytorch-operator-cf8c5c497-lntsm                               1/1     Running     0          2d23h
seldon-controller-manager-6b4b969447-b4gcj                     1/1     Running     0          2d23h
spark-operatorcrd-cleanup-knfpw                                0/2     Completed   0          2d23h
spark-operatorsparkoperator-76dd5f5688-2689x                   1/1     Running     0          2d23h
spartakus-volunteer-5dc96f4447-6ss4g                           1/1     Running     0          2d23h
tensorboard-5f685f9d79-7d8kp                                   1/1     Running     0          2d23h
tf-job-operator-5fb85c5fb7-vxkgv                               1/1     Running     0          2d23h
workflow-controller-689d6c8846-lmmdz                           1/1     Running     0          2d23h
k -n istio-system get pod
NAME                                                         READY   STATUS      RESTARTS   AGE
authservice-0                                                1/1     Running     0          2d23h
istio-citadel-79b5b568b-kqjw9                                1/1     Running     0          3d
istio-galley-756f5f45c4-kxkhr                                1/1     Running     0          3d
istio-ingressgateway-77f74c944c-z5frv                        1/1     Running     0          3d
istio-nodeagent-478g7                                        1/1     Running     0          3d
istio-nodeagent-rmbc5                                        1/1     Running     0          3d
istio-nodeagent-z49bb                                        1/1     Running     0          3d
istio-pilot-55f7f6f6df-k5mdm                                 2/2     Running     0          3d
istio-policy-76dbd68445-vskkp                                2/2     Running     0          3d
istio-security-post-install-release-1.3-latest-daily-2j5tr   0/1     Completed   0          3d
istio-sidecar-injector-5d9f474dcb-l5qjj                      1/1     Running     0          3d
istio-telemetry-697c8fd794-9hz9q                             2/2     Running     0          3d
prometheus-b845cc6fc-d7cq6                                   1/1     Running     0          3d

kubeflow 접속하기

kubeflow GUI에 접속해 보겠습니다. istio-ingressgateway 를 통해서 접속합니다. 여기서는 편의를 위해서 노드 포트(NodePort)를 사용하겠습니다.

다음은 istio-ingressgateway 서비스를 조회해 본 결과입니다.

$ kubectl -n istio-system get service istio-ingressgateway
NAME                   TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                                                                                                      AGE
istio-ingressgateway   NodePort   172.30.86.239   <none>        15020:30113/TCP,80:31380/TCP,443:31390/TCP,31400:31400/TCP,15029:31134/TCP,15030:31251/TCP,15031:32398/TCP,15032:30455/TCP,15443:32764/TCP   3d

서비스 타입이 NodePort 이고, 80번 포트가 31380이라는 노드 포트로 열려있습니다. 브라우저를 실행하고, 해당 포트로 접속해보겠습니다.

기본 설정을 바꾸지 않았다면, 사용자 이름은 admin@kubeflow.org 이고, 비밀번호는 12341234 입니다.

로그인이 성공적으로 되면, 다음과 같이 사용할 네임스페이스를 생성하는 화면이 나옵니다. 원하는 이름을 입력하시면 됩니다. 기본값을 admin 으로 되어있습니다.

네임스페이스가 생성되면, kubeflow 대시보드 화면을 볼 수 있습니다.

참고

PC에 kubeflow 설치하기 – 2부 kubernetes, nvidia device-plugin 설치하기

swap 비활성화 하기

쿠버네티스(kubernetes)를 설치 하기 위해서 swap을 비활성화 합니다.

$ sudo swapoff -a

그리고 재부팅 하였을때 swap이 다시 활성화되는 것을 막기 위해서, /etc/fstab 에 있는 swap 관련 부분을 주석 처리하거나, 제거해 줍니다.

$ sudo vi /etc/fstab
 
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
/dev/mapper/ubuntu--vg-root /               ext4    errors=remount-ro 0       1
# /boot/efi was on /dev/nvme0n1p1 during installation
UUID=D21A-9B89  /boot/efi       vfat    umask=0077      0       1
# /dev/mapper/ubuntu--vg-swap_1 none            swap    sw              0       0

iptables 설치하기

iptables을 설치하고, 필요에 따라서 iptables tooling을 legacy 모드로 변경합니다.

# ensure legacy binaries are installed
$ sudo apt-get install -y iptables arptables ebtables

# switch to legacy versions
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy

kubelet, kubeadm, kubectl 설치하기

쿠버네티스 설치에 필요한 kubelet, kubeadm, kubectl을 설치합니다. 버전을 명시해 주지 않으면, 최선 버전으로 설치됩니다. kubeflow 문서에 따르면 현재 권장하는 쿠버네티스 버전은 1.14 입니다. 1.15 버전도 호환이 되기 때문에, 이 글에서는 1.15.10-00 버전으로 설치 하였습니다.

$ sudo apt-get update && sudo apt-get install -y apt-transport-https curl
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
$ sudo apt-get update
$ sudo apt-get install -y kubelet=1.15.10-00 kubeadm=1.15.10-00 kubectl=1.15.10-00
$ sudo apt-mark hold kubelet kubeadm kubectl

쿠버네티스 설치하기

kubeadm을 사용해서 쿠버네티스를 설치합니다. 포드 네트워크 애드온을 cilium을 사용할 것이기 때문에 --pod-network-cidr=10.217.0.0/16 옵션을 사용하겠습니다.

다음 명령어를 실행해서 쿠버네티스를 설치합니다.

$ sudo kubeadm init --pod-network-cidr=10.217.0.0/16

설치가 완료되면, kubectl을 사용하기 위해서 관리자 설정 파일을 유저 디렉토리로 복사합니다.

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

쿠버네티스 접속을 테스트 하기 위해서, 다음 명령어를 실행합니다.

$ kubectl cluster-info
Kubernetes master is running at https://192.168.21.36:6443
KubeDNS is running at https://192.168.21.36:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

이제 Cilium을 쿠버네티스에 설치합니다.

$ kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.6/install/kubernetes/quick-install.yaml

cilim 포드의 READY가 1/1이 되면, 쿠버네티스 클러스터를 사용할 수 있습니다.

$ kubectl get pods -n kube-system --selector=k8s-app=cilium
NAME           READY   STATUS    RESTARTS   AGE
cilium-k4l5b   1/1     Running   0          70s

Control plane 노드 격리 해제하기

기본적으로 쿠버네티스 클러스터의 컨트롤 플레인(control-plane) 노드에는 보안상의 이유로 노드가 격리되어 있어서, 포드가 스케줄링되지 않습니다. 이 문서에는 1대의 머신만을 사용하기 때문에 노드 격리를 해제하겠습니다.

다음 명령어로 노드 격리를 해제 시킵니다.

$ kubectl taint nodes --all node-role.kubernetes.io/master-
node/mortar untainted

nvidia plugin 설치하기

쿠버네티스에서 GPU를 사용하기 위해서, nvidia k8s-device-plugin 을 설치합니다. 이 문서를 작성하는 시점에서는 1.12가 최신버전이라서 1.12로 설치하겠습니다.

$ kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v1.12/nvidia-device-plugin.yml

daemonset.extensions/nvidia-device-plugin-daemonset-1.12 created

참고로, 쿠버네티스 1.15 버전을 설치했을 경우에는 문제가 없을 건데, 1.16 버전 이상을 설치 했을 경우, 다음과 같은 에러가 발생할 것입니다. 자세한 사항은 해당 페이지를 참고 바랍니다.

error: unable to recognize "https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v1.12/nvidia-device-plugin.yml": no matches for kind "DaemonSet" in version "extensions/v1beta1"

쿠버네티스 버전이 올라가면서, Daemonsetextensions/v1beta1 버전을 더 이상 지원하지 않아서 입니다. 버전을 apps/v1 으로 변경하고 selector를 추가한 후, k8s-device-plugin 을 다시 설치합니다.

다음은 변경한 1.17 버전에 맞게 변경한 메니페스트 파일입니다.

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nvidia-device-plugin-daemonset-1.12
  namespace: kube-system
spec:
  updateStrategy:
    type: RollingUpdate
  selector:
    matchLabels:
      name: nvidia-device-plugin-ds
  template:
    metadata:
      # Mark this pod as a critical add-on; when enabled, the critical add-on scheduler
      # reserves resources for critical add-on pods so that they can be rescheduled after
      # a failure.  This annotation works in tandem with the toleration below.
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ""
      labels:
        name: nvidia-device-plugin-ds
    spec:
      tolerations:
      # Allow this pod to be rescheduled while the node is in "critical add-ons only" mode.
      # This, along with the annotation above marks this pod as a critical add-on.
      - key: CriticalAddonsOnly
        operator: Exists
      - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule
      containers:
      - image: nvidia/k8s-device-plugin:1.11
        name: nvidia-device-plugin-ctr
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
        volumeMounts:
          - name: device-plugin
            mountPath: /var/lib/kubelet/device-plugins
      volumes:
        - name: device-plugin
          hostPath:
            path: /var/lib/kubelet/device-plugins
EOF

device-plugin 포드가 정상적으로 작동했는지 확인해 봅니다.

$ kubectl -n kube-system get pod -l name=nvidia-device-plugin-ds
NAME                                        READY   STATUS    RESTARTS   AGE
nvidia-device-plugin-daemonset-1.12-4kt95   1/1     Running   0          24s

$ kubectl -n kube-system logs  -l name=nvidia-device-plugin-ds
2020/02/09 09:05:10 Loading NVML
2020/02/09 09:05:10 Fetching devices.
2020/02/09 09:05:10 Starting FS watcher.
2020/02/09 09:05:10 Starting OS watcher.
2020/02/09 09:05:10 Starting to serve on /var/lib/kubelet/device-plugins/nvidia.sock
2020/02/09 09:05:10 Registered device plugin with Kubelet

GPU 테스트 하기

다음은 텐서플로를 이용해서, GPU를 테스트 해 보는 예제입니다. 단순한 테스트이기 때문에 무시하고 넘어가도 됩니다.

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: tf-gpu-jupyter
  name: tf-gpu-jupyter
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tf-gpu-jupyter
  template:
    metadata:
      labels:
        app: tf-gpu-jupyter
    spec:
      containers:
      - image: tensorflow/tensorflow:2.1.0-gpu-py3-jupyter
        imagePullPolicy: IfNotPresent
        name: tf-gpu-jupyter
        ports:
        - containerPort: 8888
          protocol: TCP
        resources:
          limits:
            nvidia.com/gpu: "1"
EOF

tf-gpu-jupyter 라는 이름을 가진 GPU를 사용할 수 있는 텐서플로 주피터(jupyter)를 생성하였습니다. 포드가 정상적으로 작동했는지 확인해 봅니다.

$ kubectl get pod -l app=tf-gpu-jupyter
NAME                              READY   STATUS    RESTARTS   AGE
tf-gpu-jupyter-66f89b64cd-vrllc   1/1     Running   0          5m58s

주피터 접속에 필요한 토큰 정보를 얻기 위해서 로그를 조회해 보겠습니다.

$ kubectl logs -l app=tf-gpu-jupyter
[I 09:15:25.009 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 09:15:25.012 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
    Or copy and paste one of these URLs:
        http://tf-gpu-jupyter-66f89b64cd-vrllc:8888/?token=6527214998b8d895f6f14a8901a39ba6d8420c43e68f6919
     or http://127.0.0.1:8888/?token=6527214998b8d895f6f14a8901a39ba6d8420c43e68f6919
[I 09:17:20.916 NotebookApp] 302 GET / (127.0.0.1) 0.53ms
[I 09:17:20.926 NotebookApp] 302 GET /tree? (127.0.0.1) 0.65ms

포드에 접속하기 위해서 port-forward를 사용하겠습니다.

$ kubectl port-forward pod/tf-gpu-jupyter-66f89b64cd-vrllc 8888:8888
Forwarding from 127.0.0.1:8888 -> 8888
Forwarding from [::1]:8888 -> 8888

kubectl delete deployment tf-gpu-jupyter
deployment.apps "tf-gpu-jupyter" deleted

포트 포워드가 활성화 되면, 브라우저에서 주피터 주소를 입력합니다. 포드 로그에서 봤던 주소를 입력하면 됩니다. 이 예제에서 주소는 http://127.0.0.1:8888/?token=6527214998b8d895f6f14a8901a39ba6d8420c43e68f6919 입니다.

정상적으로 접속이 되면, 다음과 같은 화면을 보실 수 있습니다.

사용 가능한 GPU 갯수를 확인하겠습니다. 파이썬3 노트북을 생성한 후, 다음 코드를 입력합니다.

from __future__ import absolute_import, division, print_function, unicode_literals

import tensorflow as tf
print("Num GPUs Available: ", len(tf.config.experimental.list_physical_devices('GPU')))

코드를 실행하면 다음과 같이 사용 가능한 GPU 개수가 출력될 것입니다.

확인을 다 했으면, 브라우저를 종료하고, 자원 낭비를 막기 위해서 디플로이먼트를 삭제하도록 하겠습니다. 우리의 GPU는 소중하니까요.

$ kubectl delete deploy tf-gpu-jupyter

참고

  • https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
  • https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/
  • https://github.com/NVIDIA/k8s-device-plugin
  • https://hub.docker.com/r/tensorflow/tensorflow/
  • https://www.tensorflow.org/guide/gpu

PC에 kubeflow 설치하기 – 1부 nvidia 드라이버, docker 설치하기

이 글은 지적 유희를 위해서 작성하였습니다. kubeflow 자체가 목적이신 분들은, miniKFGCP를 사용하시길 추천드립니다.

시스템 사항

다음은 이 글에서 사용한 PC의 사양입니다. kubeflow를 원활하게 설치하기 위해서는 램이 16GB 이상, CPU는 4코어 이상을 추천합니다. GPU를 사용하기 위해서 nvidia 그래픽 카드도 필요합니다.

프로세서amd 라이젠 5 3600
RAM32GB
그래픽 카드RTX-2060
스토리지 공간 다다익선

설치 목록

  • ubuntu 18.04 LTS
  • nvidia driver 435
  • docker CE 18.9
  • nvidia-docker2
  • kubernetes 1.15.10
  • cilium 1.6
  • nvidia-device-plugin-daemonset 1.12
  • kubeflow 1.0RC4 with istio 1.3

전체 목록

우분투 설치하기

우분투는 데스크탑 18.04 LTS 버전을 사용합니다. 설치 방법은 다른곳에 많이 나와있기 때문에 생략합니다. 다만 nvidia 그래픽 카드를 사용할 경우 문제가 생기기 때문에, 그 부분만 다루겠습니다.

nvidia driver 설치하기

우분투 18.04 환경에서 RTX-2060을 사용할 경우 nouveau 문제가 있습니다. RTX-2060가 장착되어 있는 장비에서 우분투를 설치할 경우 nouveau로 자동 설정되기 때문이다. 그래서 nouveau 를 제거하는 작업이 필요합니다.

우분투 설치 화면이 깨져서 보이지 않는다면, 설치 전에 nomodset 옵션을 추가해줘야 정상적인 화면을 볼 수 있습니다.

우분투를 설치 하기 전 GRUB 메뉴 화면에서 e 키를 누룹니다.

e 키를 누르면, 다음과 같이 파라메터를 편집할 수 있는 화면이 나옵니다.

quiet splash 뒤에 nomodeset 을 추가해 줍니다. 그리고 F10 키를 눌러서 부팅 합니다.

정상적으로 화면이 보일 것입니다. 우분투를 설치하는 나머지 과정은 생략하겠습니다.

nouveau 설치 확인 하기

우분투가 정상적으로 설치되었다면, 재부팅 후 nouveau 확인 작업을 합니다. 시스템이 다시 시작되면, 앞서 한 것과 동일한 방법으로, nomodset 옵션을 추가해줘야 정상적인 화면을 볼 수 있을것입니다.

부팅이 완료되면 터미널을 열어서 작업을 시작합니다.

터미널에서 다음 명령어를 실행한 후, 결과가 보이면 nouveau가 설치되어 있는 것입니다. nvidia 드라이버 설치를 위해서 제거해야 합니다.

$ lsmod | grep nouveau
nouveau              1863680  0
mxm_wmi                16384  1 nouveau
video                  49152  1 nouveau
i2c_algo_bit           16384  1 nouveau
ttm                   102400  1 nouveau
drm_kms_helper        180224  1 nouveau
drm                   479232  3 drm_kms_helper,ttm,nouveau
wmi                    28672  3 wmi_bmof,mxm_wmi,nouveau

/etc/modprobe.d/ 경로에 blacklist 파일을 생성합니다.

$ sudo vi /etc/modprobe.d/blacklist-nouveau.conf

blacklist nouveau
options nouveau modset=0

다음 명령어를 실행 한 후, 재부팅 합니다.

$ sudo update-initramfs -u
$ sudo service gdm stop

Nvidia 드라이버 설치하기

컨테이너(Container)를 이용해서 GPU를 사용할 예정이기 때문에, Nvidia 드라이버가 설치합니다.

$ sudo add-apt-repository ppa:graphics-drivers/ppa
$ sudo apt-get update

$ sudo apt-get install nvidia-driver-435
$ sudo reboot

재부팅 후, nvidia-smi 명령어를 실행해서, 드라이버가 정상적으로 설치되어 있는지 확인해 볼 수 있습니다.

$ nvidia-smi
Sun Feb 16 17:26:22 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 435.21       Driver Version: 435.21       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce RTX 2060    Off  | 00000000:26:00.0  On |                  N/A |
| 32%   45C    P8     9W / 190W |    189MiB /  5931MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0       975      G   /usr/lib/xorg/Xorg                            96MiB |
|    0      1123      G   /usr/bin/gnome-shell                          91MiB |
+-----------------------------------------------------------------------------+

docker 설치하기

apthttps저장소를 사용할 수 있도록 패키지를 추가합니다.

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

도커의 GPG 키를 추가합니다.

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

저장소를 추가합니다

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

apt 패키지의 인덱스를 업데이트합니다.

$ sudo apt-get update

이 문서를 작성할 당시(2020-02-08)에 도커의 최선 버전은 19.03이였습니다. 19.03 버전 부터 GPU 관련한 내용이 변경되었습니다. 쿠버네티스상에서 GPU 관련 작업을 하라면, k8s-device-plugin이 필요한데, 아직 19.03 버전을 지원하지 않는 것 같습니다. 그래서 18.9 버전을 설치하였습니다.

도커 엔진 18.9 버전을 설치합니다.

$ sudo apt-get install docker-ce=5:18.09.9~3-0~ubuntu-bionic docker-ce-cli=5:18.09.9~3-0~ubuntu-bionic containerd.io

$ sudo apt-mark hold docker-ce docker-ce-cli

설치 가능한 도커 버전을 보려면 다음 명령어를 실행하면 됩니다.

$ apt-cache madison docker-ce
 docker-ce | 5:19.03.5~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:19.03.4~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:19.03.3~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:19.03.2~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:19.03.1~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:19.03.0~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:18.09.9~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:18.09.8~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
 docker-ce | 5:18.09.7~3-0~ubuntu-bionic | https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages
...

도커가 정상적으로 설치되었는지 확인해 보기 위해서 hello-world 이미지를 실행합니다.

$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete 
Digest: sha256:9572f7cdcee8591948c2963463447a53466950b3fc15a247fcad1917ca215a2f
Status: Downloaded newer image for hello-world:latest

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

nvidia-docker2 설치하기

컨테이너에서 GPU를 사용하기 위해서 nvidia-docker2 을 설치합니다. 그리고 도커를 재시작 합니다.

# Add the package repositories
$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

$ sudo apt-get update
$ sudo apt-get install nvidia-docker2
$ sudo systemctl restart docker

nvidia-docker2 가 정상적으로 설치되었는지 확인해 보기 위해서, 다음 명령어를 실행합니다.

$ sudo docker run --runtime nvidia nvidia/cuda:10.0-base nvidia-smi
Sat Feb  8 11:19:15 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 435.21       Driver Version: 435.21       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce RTX 2060    Off  | 00000000:26:00.0  On |                  N/A |
| 29%   40C    P8     9W / 190W |    229MiB /  5931MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

도커의 기본 런타임을 변경해 줍니다. /etc/docker/daemon.json 파일이 생성되었을 것입니다. 해당 파일을 열여서 "default-runtime": "nvidia"을 추가해주면 됩니다.

$ sudo vi /etc/docker/daemon.json

    {
      "default-runtime": "nvidia", 
      "runtimes": {
        "nvidia": {
          "path": "nvidia-container-runtime",
          "runtimeArgs": []
        }
      }
    }

파일을 수정한 후, 도커를 재시작합니다.

$ sudo systemctl restart docker

참고

  • https://docs.docker.com/install/linux/docker-ce/ubuntu/
  • https://github.com/NVIDIA/nvidia-docker

docker 명령어

도커 이미지 관련 명령어

docker login [repository] : 저장소(repository)에 로그인한다. 저장소 주소를 적지 않으면 Docker Hub repository 로 로그인한다.

docker create [image] : 해당 이미지로부터 새로운 컨테이너를 생성한다.

docker pull [image] : 이미지를 저장소로부터 가져온다.

docker push [image] : 이미지를 저장소에 올린다.

docker tag [source] [target] : 원본 이미지 새로운 태그를 부여한다.

docker search [term] : 해당 단어로 저장소에 있는 이미지를 검색한다.

docker images : 로컬 시스템에 저장되어 있는 이미지 목록을 보여준다.

docker history [image] : 해당 이미지의 히스토리를 보여준다.

도커 컨테이너 관련 명령어

docker ps : 현재 실행중인 컨테이너 목록을 보여준다.

docker run [image] : 해당 이미지로 도커 컨테이너를 실행한다.

docker start [container] : 도커 컨테이너를 시작한다.

docker stop [container] : 도커 컨테이너를 중지한다. (SIGTERM -> SIGKILL)

docker stop $(docker ps -q) : 현재 작동하는 모든 도커 컨테이너를 중지한다.

docker kill [container] : 도커 컨테이너를 강제로 중지한다. (SIGKILL)

docker inspect [container] : 컨테이너의 상세 정보를 보여준다.

docker rm [container] : 중지된 도커 컨테이너를 삭제한다.

docker rm $(docker ps -a -q) : 중지된 모든 도커 컨테이너를 삭제한다.

docker exec -it [container] [command] : 대상 도커 컨테이너에 명령어를 실행한다.

기타 명령어

docker info : 도커 상세 정보를 보여준다.

docker version : 도커 버전을 보여준다.

docker stats : 현재 도커 컨테이너들의 상태로 보여준다.

Docker 로그 관리

도커(docker)는 로깅 드라이버(logging driver) 통해, 로그를 남기게 되어 있습니다. 로깅 드라이버의 기본 값을 json-file입니다. 즉, 로그를 json 형식으로 파일로 저장하게 됩니다.

아래 명령어를 실행하면, 해당 도커의 로깅 드라이버가 뭔지 알 수 있습니다.

$ docker info --format ''
json-file

json-file 로깅 드라이버를 사용하는 경우, 시간이 지날 수록 로그 파일이 쌓이기 때문에 주기적으로 파일을 삭제해줘야합니다. 주기적으로 파일을 삭제하는 방법은, 도커 데몬의 설정을 변경하거나, logrotate를 이용하는 것입니다.

도커 데몬 설정 파일 변경하기

/etc/docker/ 디렉토리에 있는 daemon.json 파일에 아래와 같은 내용을 추가해 주면 됩니다.

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3" 
  }
}

도커 재시작하면 변경 사항이 반영됩니다.

logrotate 이용하기

logrotate는 로그를 관리하기 위해 사용되는 범용툴입니다. 서버에 설치가 안되어 있다면, 설치가 필요합니다. 아래와 같이 컨테이너 로그를 정리하는 설정 파일을 추가해 주면 됩니다.

cat > /etc/logrotate.d/container << EOF
/var/lib/docker/containers/*/*.log {
    rotate 100
    copytruncate
    missingok
    notifempty
    compress
    maxsize 100M
    maxage 30
    daily
    dateext
    dateformat -%Y%m%d-%s
    create 0644 root root
}
EOF

참고 문서