티스토리 뷰

728x90
반응형

Cgroup 드라이버

cgroups(control groups의 약자)는 프로세스들의 자원의 사용(CPU, 메모리, 디스크 입출력, 네트워크 등)을 제한하고 격리시키는 리눅스 커널 기능입니다.


[root@ciserver ~]# ls -la /sys/fs/cgroup
total 0
drwxr-xr-x. 13 root root 340 Sep 17 05:28 .
drwxr-xr-x.  7 root root   0 Sep 17 05:28 ..
drwxr-xr-x.  2 root root   0 Sep 17 05:28 blkio
lrwxrwxrwx.  1 root root  11 Sep 17 05:28 cpu -> cpu,cpuacct
drwxr-xr-x.  2 root root   0 Sep 17 05:28 cpu,cpuacct
lrwxrwxrwx.  1 root root  11 Sep 17 05:28 cpuacct -> cpu,cpuacct
drwxr-xr-x.  2 root root   0 Sep 17 05:28 cpuset
drwxr-xr-x.  4 root root   0 Sep 17 05:28 devices
drwxr-xr-x.  2 root root   0 Sep 17 05:28 freezer
drwxr-xr-x.  2 root root   0 Sep 17 05:28 hugetlb
drwxr-xr-x.  2 root root   0 Sep 17 05:28 memory
lrwxrwxrwx.  1 root root  16 Sep 17 05:28 net_cls -> net_cls,net_prio
drwxr-xr-x.  2 root root   0 Sep 17 05:28 net_cls,net_prio
lrwxrwxrwx.  1 root root  16 Sep 17 05:28 net_prio -> net_cls,net_prio
drwxr-xr-x.  2 root root   0 Sep 17 05:28 perf_event
drwxr-xr-x.  2 root root   0 Sep 17 05:28 pids
drwxr-xr-x.  4 root root   0 Sep 17 05:28 systemd
[root@ciserver ~]#


우리는 컨테이너를 활용하기 위해 컨테이너 기술 즉 Container Runtime을 통한 Linux Kernel을 사용합니다.

Container 기술을 구현하기 위해 Linux Kernel이 제공하는 주요 기능은 Namespace, C-Group, SELinux 등이 있다.

- Namespace : 프로세스 격리 기술

- cgroup (process 감사, 제어, 추적) : namespace + cgroup 형태로 구성되며, namespace에 대한 관리, 추적을 위한 역할

위 이미지는 kernel 2.4 이전 / 이후 버전을 커널 영역을 기반으로 설명하는 도식도입니다.

커널의 위쪽은 최근 2.4 버전에서 지원하는 영역으로 Namespace 기반 System 영역과 User영역을 구분하고 있습니다.

커널의 아래쪽은 2.4 이전에 지원했던 방식으로 init process를 기반으로 모든 프로세스가 fork되는 방식입니다.

2.4 이후 방식은 Namespace로 인해 장애 격리를 실현할 수 있지만, 구분된 Namespace 방식을 기존 모놀리식 방식과 다르게 분산되어 관리해야하므로 이를 통합하여 추적하고 관리하는 cgroup을 사용해야 합니다.

Container 시스템의 문제점

Docker와 같은 일반적인 컨테이너 모델은 Host의 커널을 공유하기 때문에 때로는 Host OS를 공격할 수 있는 그 루트가 될 수도 있습니다. 효과적으로 컨테이너 시스템을 보호하기 위해 SELinux나 SCC, RBAC등의 권한 부여 방식을 통해 시스템을 제한시킬 수 있지만, 이 역시 완벽하게 격리된 시스템이라고 볼 수 없습니다.

이는 VM과의 상반되는 측면이 있는데, VM의 경우 무겁지만, 완전한 격리가 이루어지는 시스템이라는 점에서 컨테이너보다 보안이 뛰어나다고 설명하곤합니다.

아래 이미지와 같이 Usre 영역에 존재하는 Docker 위의 Container들은 기본 격리된 상태로 동작하지만, 아래와 같은 요구사항이 발생할 경우 syscall을 통해 Host OS로 접근하게 됩니다.

 

a. PV로 HostPath를 사용할 경우 또는 Volume으로 HostPath를 지정할 경우

b. Telemetry 컴포넌트를 구성할 경우 즉 모니터링 로그, Metrics 수집 등을 수행할 경우

c. Host OS의 보안 모듈을 공유하려고 할 경우 즉 SELinux, SECComp 등을 사용하고자 할 경우

d. GPU를 사용할 경우 

 

이와 같은 컨테이너 보안을 완전히 해소하기 위해서는 보다 많은 노력이 필요하겠지만, Host OS의 기술(Kernel)을 활용하여 보안의 위협으로부터 지켜 낼 수 있습니다. 대표적인 방식이 바로 SELinux와 최신 커널을 사용하는 방법입니다.

지금부터 하나씩 살펴보도록 하겠습니다.

Kernel 보안 요소

- SELinux (보안, 차단)

Linux의 강력한 방화벽 정책관리인 selinux를 사용하여 보안을 강화할 수 있습니다.

disable 하지 않고 방화벽 관리를 통해 컨테이너로의 접근을 제어할 수 있도록 구성하는 기능을 Linux Kernel을 통해 제공합니다.

- 최신 Linux Kernel 5.x 사용 권고 : 기능 설정 (포트, 파일, 디렉터리..)

이를 Container Runtime에 적용하면 다음과 같습니다.

커널의 기능을 Container Runtime(Docker)에 적용하여 APP 별 구분된 Namespace와 CGroup을 적용할 수 있습니다.

이는 장애 차단 및 리소스 할당, 추적 관리 등에 사용되는 Container Runtime의 핵심 기능이라 할 수 있습니다.


Linux init 시스템의 cgroup 드라이버가 systemd인 경우, init 프로세스는 root control group(cgroup)을 생성 및 사용하는 cgroup 관리자로 작동합니다.

Systemd는 cgroup과의 긴밀한 통합을 통해 프로세스당 cgroup을 할당합니다.

이때 컨테이너 런타임과 kubelet이 cgroup 드라이버로 cgroupfs를 사용하도록 설정할 수 있습니다. 이는 linux 시스템이 사용하는 systemd와 함께 kubernetes kubelet이 사용하는 cgroupfs 드라이버를 서로 다른 cgroup 관리자가 관리하게 됩니다.

Control group은 프로세스에 할당된 리소스를 제한하는데 사용됩니다. 단일 cgroup 관리자는 할당된 리소스가 무엇인지를 단순화하고, 기본적으로 사용가능한 리소스와 사용중인 리소스를 일관성있게 볼 수 있습니다. 관리자가 두 개인 경우, 리소스도 두 개의 관점에서 보게 됩니다.

kubelet과 Docker는 cgroupfs를 사용하고 나머지 프로세스는 systemd를 사용하도록 노드가 설정된 경우, 리소스가 부족할 때 불안정해지는 현상이 발생할 수 있습니다. 이로 인해 시스템 리소스 부족 현상이 발생할 수 있으므로 리눅스 init 시스템이 사용하는 cgroups 드라이버와 docker, kubelet의 드라이버를 맞춰주는 것이 효율적입니다.

지금부터 살펴볼 내용은 바로 이와 같은 이유로 docker process의 cgroups를 변경하는 방법에 대해 살펴보고자 합니다.

Docker Cgroup 확인

먼저 Docker의 Cgroup 정보를 확인해 보도록 하겠습니다. Cgroup 정보는 docker info 명령어로 확인이 가능합니다.

[root@kubemaster docker.service.d]# docker info
Client:
 Debug Mode: false

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 16
 Server Version: 18.09.7
 Storage Driver: overlay2
  Backing Filesystem: xfs
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host 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: b34a5c8af56e510852c35414db4c1f4fa6172339
 runc version: 3e425f80a8c931f88e6d94a8c831b9d5aa481657
 init version: fec3683
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 3.10.0-957.21.3.el7.x86_64
 Operating System: Red Hat Enterprise Linux Server 7.6 (Maipo)
 OSType: linux
 Architecture: x86_64
 CPUs: 16
 Total Memory: 15.51GiB
 Name: kubemaster
 ID: 475Y:IORI:HYG7:JY4V:U665:V4MZ:AQFU:BMNG:2FIK:6GMR:7CB6:YXAO
 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
 Product License: Community Engine

[root@kubemaster docker.service.d]# docker info | grep -i cgroup
 Cgroup Driver: cgroupfs
[root@kubemaster docker.service.d]#

위와 같이 docker를 설치하면 기본 cgroup 드라이버는 cgroupfs로 적용되어 있을 것입니다.

이를 Kubernetes에서 권고하는 systemd로 변경하기 위해서는 다음과 같이 적용해야 합니다.

Docker Cgroup 변경 방법

1. Cgroup 수정

a. docker service를 직접 수정하는 방법 (ExecStart 구문에 --exec-opt native.cgroupdriver=systemd 추가)

[root@kubemaster docker.service.d]# vi /etc/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.com
After=network.target  containerd.service


[Service]
Type=notify
Environment=GOTRACEBACK=crash
ExecReload=/bin/kill -s HUP $MAINPID
Delegate=yes
KillMode=process
ExecStart=/usr/bin/dockerd --exec-opt native.cgroupdriver=systemd\
          $DOCKER_OPTS \
          $DOCKER_STORAGE_OPTIONS \
          $DOCKER_NETWORK_OPTIONS \
          $DOCKER_DNS_OPTIONS \
          $INSECURE_REGISTRY
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=1min
# restart the docker process if it exits prematurely
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s

[Install]
WantedBy=multi-user.target
[root@kubemaster docker.service.d]#

b. docker daemon.json 파일 수정

cat > /etc/docker/daemon.json <{ 
  "exec-opts": ["native.cgroupdriver=systemd"], 
  "log-driver": "json-file", 
  "log-opts": { 
    "max-size": "100m" 
  }, 
  "storage-driver": "overlay2", 
  "storage-opts": [ 
    "overlay2.override_kernel_check=true" 
  ] 
} 
EOF

2. docker service 재기동

위와 같이 두가지 케이스 중 하나를 적용하고 docker service를 재기동 합니다.

[root@kubemaster docker.service.d]# systemctl daemon-reload
[root@kubemaster docker.service.d]# systemctl restart docker
[root@kubemaster docker.service.d]#

3. Docker cgroup 확인

[root@kubemaster docker.service.d]# docker info | grep -i cgroup
 Cgroup Driver: systemd
[root@kubemaster docker.service.d]#

위와 같은 방법으로 Kubernetes kubelet과 docker process의 cgroup driver를 systemd로 동일하게 맞추고 적용할 경우 하나의 cgroup 매니저가 관리를 하게 되어 리소스 사용 효율을 증대 시킬 수 있습니다.

728x90
반응형