배경


사내에서 사용하는 EKS Worker EC2 AMI 를 AL2 에서 AL2023 으로 마이그레이션 하면서 발생한 일련의 부팅 실패와 빌드 실패를 디버깅한 기록이다. AMI 빌드는 Packer + Ansible 로 진행했고, base AMI 가 CIS hardening 적용본이라는 점이 모든 문제의 출발점이었다.

빌드/부팅 흐름:

[CIS-hardened AL2023 base AMI]
        ↓ Packer + Ansible provisioners
[install-worker.sh, configure-selinux.sh, cleanup.sh, ...]
        ↓ AMI snapshot
[EC2 launch with user-data (NodeConfig YAML)]
        ↓ first boot
nodeadm-boot-hook → nodeadm-config → nodeadm-run → kubelet → join EKS

vanilla EKS AMI 빌드 스크립트는 표준 AL2023 의 권한과 umask 를 가정하지만 CIS hardening 이 이 가정을 곳곳에서 깨뜨린다.

CIS Hardening 부작용 정리


이번 마이그레이션에서 마주친 CIS hardening 효과들과 실제로 부딪힌 증상:

umask 022 → 027

표준 AL2023 의 umask 는 022 라서 새 파일은 0644, 새 디렉토리는 0755 로 생성된다. CIS 는 umask 를 027 로 바꿔 새 파일이 0640, 새 디렉토리가 0750 으로 생성되도록 한다.

→ install-worker.sh 가 다운받아 /usr/bin/ 으로 옮긴 kubelet 바이너리가 0640 이라 ec2-user 가 실행 불가했다. cleanup.sh 가 touch /etc/machine-id 로 빈 파일을 만들 때도 0640 으로 생성되어 dbus-broker 가 못 읽었다.

/etc/machine-id 권한

표준 AL2023 은 0644. CIS 환경에서는 cleanup.sh 의 rm + touch 조합 + umask 027 효과로 0640 이 된다.

→ dbus-broker.service 가 부팅 시 EACCES 로 실패. 부팅 체인 전체가 무너졌다 (이 문서의 가장 큰 디버깅 케이스).

/etc/eks/ 디렉토리 권한

표준 AL2023 EKS AMI 는 0755 root:root. CIS 환경에서는 install-worker.sh 가 mkdir 할 때 umask 027 이 적용되어 0750 으로 생성된다.

→ configure-selinux.sh 가 ec2-user 로 실행되면서 matchpathcon -V /etc/eks/* 의 glob 확장이 실패 (“Permission denied”). ec2-user 는 root:root 0750 디렉토리의 항목을 읽을 수 없다.

containerd socket 접근 권한

표준 AL2023 EKS AMI 에서는 ec2-user 가 /run/containerd/containerd.sock 에 접근 가능하다 (특정 그룹 멤버십 또는 socket 권한). CIS 환경에서는 root 만 접근 가능.

→ cache-pause-container.sh 가 cache-pause-container -i ... 를 sudo 없이 호출하는데 ec2-user 권한으로는 containerd 와 통신 불가.

audit framework immutable mode

표준 환경에서는 auditctl 로 런타임에 룰 추가/삭제 가능. CIS 는 -e 2 플래그로 immutable mode 를 강제한다.

→ dbus-broker EPERM 의 syscall 을 audit 으로 잡으려 했으나 새 룰을 추가할 수 없어 실패. 결국 strace 로 우회.

문제 1: dbus-broker.service 시작 실패


증상

SSH 와 SSM 이 안 떠서 인스턴스에 접근할 수 없었다. EC2 콘솔의 System Log 부터 확인.

EC2 System Log

부팅 도중 dbus-broker.service 시작 실패가 보였다.

Booting 'Amazon Linux (6.12.79-101.147.amzn2023.x86_64) 2023'
RETBleed: WARNING: Spectre v2 mitigation leaves CPU vulnerable to RETBleed attacks, data leaks possible!
RPC: Registered named UNIX socket transport module.
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
...
ena 0000:00:05.0: Elastic Network Adapter (ENA) v2.16.1g
ena 0000:00:05.0: ENA controller version: 0.0.1 implementation version 1
ena 0000:00:05.0: Elastic Network Adapter (ENA) found at mem c0400000, mac addr 06:b4:29:11:48:c5
ena 0000:00:05.0 ens5: renamed from eth0
nvme nvme0: using unchecked data buffer
[FAILED] Failed to start dbus-broker.service - D-Bus System Message Bus.
...

ENA 인식까지는 정상인데 그 다음 dbus-broker 가 실패하면서 부팅 체인이 무너진다.

EC2 Serial Console 로 진입

SSH 가 없으니 EC2 Serial Console 로 root 로그인 (packer 빌드 시 임시로 chpasswd 로 password 박아둠). 거기서 journalctl -u dbus-broker --no-pager 실행:

systemd[1]: Starting dbus-broker.service - D-Bus System Message Bus...
dbus-broker-launch[1970]: ERROR launcher_run_child @ ../src/launch/launcher.c +325: Permission denied
systemd[1]: Started dbus-broker.service - D-Bus System Message Bus.
dbus-broker-launch[1946]: ERROR launcher_run @ ../src/launch/launcher.c +1411: Broken pipe
dbus-broker-launch[1946]:                   run @ ../src/launch/main.c +152
dbus-broker-launch[1946]:                   main @ ../src/launch/main.c +178
dbus-broker-launch[1946]: Exiting due to fatal error: -32
systemd[1]: dbus-broker.service: Main process exited, code=exited, status=1/FAILURE
systemd[1]: dbus-broker.service: Failed with result 'exit-code'.
systemd[1]: Starting dbus-broker.service - D-Bus System Message Bus...
dbus-broker-launch[1978]: ERROR launcher_run_child @ ../src/launch/launcher.c +325: Permission denied
dbus-broker-launch[1973]: ERROR service_add @ ../src/launch/service.c +989: Transport endpoint is not connected
dbus-broker-launch[1973]:                   launcher_add_services @ ../src/launch/launcher.c +804
dbus-broker-launch[1973]:                   launcher_run @ ../src/launch/launcher.c +1415
dbus-broker-launch[1973]: Exiting due to fatal error: -107
...반복...
systemd[1]: dbus-broker.service: Start request repeated too quickly.
systemd[1]: dbus-broker.service: Failed with result 'exit-code'.
systemd[1]: Failed to start dbus-broker.service - D-Bus System Message Bus.

핵심 단서는 두 줄:

  • ERROR launcher_run_child @ launcher.c +325: Permission denied — 자식이 launcher_run_child 안에서 실패
  • ERROR launcher_run @ launcher.c +1411: Broken pipe (errno -32 = EPIPE) — 부모가 자식과의 pipe 통신이 끊긴 후 종료

자식이 죽으면서 errno 를 보고하지 못했고, 부모는 빈 pipe 만 받고 EPIPE 로 종료했다는 뜻.

부팅 체인 cascade 확인

systemctl --failed --no-pager:

UNIT                              LOAD   ACTIVE SUB    DESCRIPTION
● acpid.service                   loaded failed failed ACPI Event Daemon
● cloud-init.service              loaded failed failed Initial cloud-init job ...
● dbus-broker.service             loaded failed failed D-Bus System Message Bus
● hibinit-agent.service           loaded failed failed Initial hibernation setup
● nodeadm-boot-hook.service       loaded failed failed EKS Nodeadm Boot Hook
● nodeadm-config.service          loaded failed failed EKS Nodeadm Config
● systemd-networkd-wait-online... loaded failed failed Wait for Network ...
● dbus.socket                     loaded failed failed D-Bus System Message Bus Socket

dbus 가 죽었을 때 망가지는 줄줄이.

dbus-broker 실패
    ↓ networkctl reload 실패 (networkd 가 dbus 로 hotplug 알림 못 보냄)
    ↓ ens5 미구성
    ↓ IMDS 169.254.169.254 unreachable
    ↓ nodeadm-boot-hook 실패
    ↓ nodeadm-config 실패
    ↓ EKS 클러스터 조인 불가

네트워크가 죽어있음

ip link show 는 ens5 가 UP 으로 잡혀있는데:

2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 ...

ip route show 는 비어있다.

$ ip route show
(출력 없음)

systemctl status systemd-networkd 의 결정적 한 줄:

ens5: Link UP
ens5: Gained carrier
ens5: Failed to configure ...ied
ens5: Failed

systemd-networkd 가 ens5 를 받았는데 dbus 를 통한 통신이 안 되니 IP 구성을 commit 하지 못한다.

처음 의심: SELinux

launcher_run_child +325 가 자식에서 exec 직전에 뭔가 실패하는 패턴이고 errno 도 EPERM 같으니, 가장 먼저 떠오르는 가설은 SELinux 가 dbus-broker exec 을 막는 것이었다.

$ getenforce
Enforcing

$ cat /etc/selinux/config | grep -v "^#" | grep -v "^$"
SELINUXTYPE=targeted
SELINUX=enforcing

$ ls -laZ /usr/bin/dbus-broker
-rwxr-xr-x. 1 root root system_u:object_r:dbusd_exec_t:s0 ...

$ ls -laZ /usr/bin/dbus-broker-launch
-rwxr-xr-x. 1 root root system_u:object_r:dbusd_exec_t:s0 ...

label 은 정상. 그래도 한 번 검증한다고 setenforce 0 으로 permissive 전환 후 dbus 재시작 — 결과는 동일하게 실패. dmesg AVC 도 없음. SELinux 는 무관했다.

여기서부터 EPERM 의 진짜 출처를 찾아 IMA, lockdown, capability, seccomp 등을 의심하게 된다.

dbus-broker 가 뭔지

AL2023 은 dbus-daemon 대신 dbus-broker 를 시스템 D-Bus 구현체로 사용한다. C 로 작성된 메시지 버스 데몬으로 systemd 가 socket activation 으로 띄운다.

  • dbus-broker-launch (launcher): 부모 프로세스. 설정 파싱, FD 와 env 준비, fork
  • dbus-broker (broker): 자식 프로세스. 실제 메시지 라우팅

자식이 launcher_run_child() 안에서 실패하면 errno 를 부모에게 pipe 로 전달하고, 부모가 launcher_run_child: <strerror> 로 로깅 후 종료한다.

EPERM vs EACCES 표시

자식의 errno 가 EPERM 이든 EACCES 든 strerror 출력은 모두 “Permission denied”. 로그만 보면 둘을 구분할 수 없다. 반드시 strace 로 raw errno 를 봐야 한다.

헛다리 짚은 가설들

EPERM 으로 추정하고 SELinux 외에 다른 보안 layer 들도 차례로 의심했다. 전부 무관했다.

  • IMA: policy 비어있음, violations=0
  • Linux Lockdown: /sys/kernel/security/lockdown = [none]
  • Mount noexec: findmnt / 에 noexec 없음
  • systemd sandbox: ProtectSystem, PrivateTmp, PrivateDevices 등 override 로 비활성화해도 동일
  • Capability bounding set: CapBnd=000001ffffffffff 로 모든 cap 보유
  • DefaultSystemCallFilter, system.conf.d: 무관

audit 으로 EPERM 잡으려 했지만 immutable mode 라 새 룰 추가 불가.

strace 로 정확한 syscall 잡기

audit 이 막혔으니 strace 로 wrapping 해야 했다. 하지만 dbus-broker.service 는 systemd socket activation 으로 시작하기 때문에 단순 직접 실행은 안 된다.

$ /usr/bin/dbus-broker-launch --scope system
No listener socket inherited

systemd 가 띄우면서 strace 로 감싸기 위해 service drop-in 작성:

# /etc/systemd/system/dbus-broker.service.d/strace.conf
[Service]
PrivateTmp=no
ExecStart=
ExecStart=/usr/bin/strace -f -tt -o /run/dbus-strace.txt /usr/bin/dbus-broker-launch --scope system

그런데 이것도 No listener socket inherited 로 종료. 원인은 systemd 의 LISTEN_PID 검증이다.

Socket Activation 과 LISTEN_PID 함정

systemd 가 socket activation 으로 서비스를 띄울 때:

  1. 서비스를 fork 해서 PID 가 P1
  2. listening socket FD #3 매핑, 환경변수 세팅:
    • LISTEN_PID=P1
    • LISTEN_FDS=1
  3. 서비스가 sd_listen_fds() 호출하면 라이브러리가 검증:
    e = getenv("LISTEN_PID");
    if (parse_pid(e, &pid) < 0) return 0;
    if (getpid() != pid) return 0;   // ← 여기
  4. PID 가 일치해야 FD 사용 가능 (조상 프로세스의 환경변수가 손자에게 잘못된 정보를 주는 것을 방지하는 보안 검사)

strace 가 끼면:

systemd → fork P1 → exec strace (PID=P1, LISTEN_PID=P1) ✓
strace → fork P2 (자식)
  P2 → exec dbus-broker-launch (PID=P2)
  dbus-broker-launch: getpid()=P2 ≠ LISTEN_PID=P1 → "No listener socket inherited"

Wrapper 로 LISTEN_PID 우회

strace 와 dbus-broker-launch 사이에 LISTEN_PID 를 자기 PID 로 갱신하는 wrapper 를 끼움.

#!/bin/sh
# /usr/local/bin/fix-listen-pid.sh
export LISTEN_PID=$$
exec "$@"
ExecStart=/usr/bin/strace -f -tt -o /run/dbus-strace.txt /usr/local/bin/fix-listen-pid.sh /usr/bin/dbus-broker-launch --scope system

exec "$@" 는 fork 없이 자기 자신을 새 프로그램으로 교체하므로 PID 가 유지된다. 그 결과 dbus-broker-launch 의 getpid() 가 LISTEN_PID 와 일치한다.

strace + systemd 결합 시 체크리스트

  1. service drop-in 으로 ExecStart 를 strace 로 감싸면 LISTEN_PID 가 깨진다 → wrapper 로 보정
  2. service 가 fork 한 child 까지 추적하려면 strace -f 필수
  3. /run/ 또는 /var/log/ 등 절대경로로 출력하기. 서비스가 PrivateTmp=yes/tmp/ 출력은 호스트에서 안 보임
  4. 출력 잘 안 나오면 drop-in 에 PrivateTmp=no 명시

진짜 원인: /etc/machine-id 권한

드디어 잡힌 strace 출력:

6912 ... openat(AT_FDCWD, "/etc/machine-id", O_RDONLY|O_NOCTTY|O_CLOEXEC) = -1 EACCES (Permission denied)

EPERM 이 아니라 EACCES 였다 (앞서 말한 strerror 동일 출력 함정).

확인:

$ ls -la /etc/machine-id
-rw-r-----. 1 root root 33 ...    # 0640

표준 AL2023 의 /etc/machine-id 는 0644 다. 그런데 CIS hardened base 에서는 0640. 이유는 cleanup.sh 의 동작에 umask 027 이 곱해진 결과다.

근본 원인 추적: cleanup.sh + umask 027

EKS AMI 의 vanilla cleanup.sh 마지막 부분:

sudo rm -rf \
  ...
  /etc/machine-id \
  ...
 
sudo touch /etc/machine-id

흐름:

  1. /etc/machine-id 를 삭제 (다음 인스턴스마다 고유한 ID 가 생성되도록)
  2. touch 로 빈 파일 생성. 이때 CIS umask 027 적용 → 파일 모드 0640
  3. AMI snapshot 직전 상태가 0640
  4. 첫 부팅에 systemd-machine-id-setup 이 빈 파일에 새 ID 를 씀. 이때 O_CREAT|O_RDWR 로 여는데 파일이 이미 존재하면 mode 인자가 무시되어 기존 모드 0640 보존
  5. dbus-broker 는 dbus 유저로 실행되어 root:root 0640 파일을 못 읽음

해결: cleanup.sh 끝에 chmod 0644 추가

문제가 만들어지는 위치에서 즉시 보정.

 sudo touch /etc/machine-id
+# CIS-hardened base AMI sets umask 027, so the touch above creates the file
+# with mode 0640. systemd-machine-id-setup writes the new ID via O_RDWR on
+# the existing file at first boot, preserving this mode -- which makes the
+# file unreadable to the dbus user and breaks dbus-broker. Force 0644.
+sudo chmod 0644 /etc/machine-id

systemd-machine-id-setup 이 첫 부팅에 빈 파일에 ID 를 쓸 때 O_RDWR 로 기존 파일을 여는 거라 모드는 보존된다 (POSIX open(O_CREAT) 동작 — 파일이 이미 있으면 mode 인자 무시).

왜 bake 시점의 단순 chmod provisioner 는 안 통하는가

처음에는 packer 에 별도 provisioner 로 다음을 추가해봤다.

provisioner "shell" { inline = ["sudo chmod 644 /etc/machine-id"] }
provisioner "shell" { script = ".../cleanup.sh" }

이 chmod 는 cleanup.sh 의 rm -rf /etc/machine-id + touch /etc/machine-id 에 의해 무효화된다. 파일이 삭제되고 다시 만들어지면서 umask 027 이 적용되어 0640 으로 돌아간다. cleanup.sh 의 touch 이후에 chmod 가 와야 한다.

대안으로 tmpfiles.d 를 쓸 수도 있다.

echo 'f /etc/machine-id 0644 root root - -' > /etc/tmpfiles.d/machine-id.conf

매 부팅 sysinit.target 단계에서 권한을 강제하므로, 누군가 권한을 바꿔도 자동 복구된다는 장점이 있다. 다만 이번 케이스는 한 번 정해지면 바뀌지 않으므로 cleanup.sh 의 단순 chmod 가 더 직접적이다.

문제 2: kubelet TLS cipher suite


증상

dbus 가 살아나고 nodeadm 까지 통과한 뒤 kubelet 이 무한 재시작.

kubelet[]: run.go:72] "command failed" err="failed to construct kubelet dependencies:
  Cipher suite TLS_RSA_WITH_AES_128_GCM_SHA256 not supported or doesn't exist"

원인

custom ansible provisioner 가 /etc/eks/nodeadm.d/kubelet-config.yaml 로 떨어뜨리는 tlsCipherSuites 목록에 deprecated cipher 포함:

tlsCipherSuites:
  ...
  - TLS_RSA_WITH_AES_256_GCM_SHA384   # deprecated
  - TLS_RSA_WITH_AES_128_GCM_SHA256   # deprecated

TLS_RSA_* 시리즈는 forward secrecy 가 없어 Kubernetes 1.30+ kubelet 이 거부한다.

해결

두 항목 제거. 남은 ECDHE 기반 cipher 만으로 호환성 충분.

tlsCipherSuites:
  - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
  - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
  - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
  - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
  - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384

부수적인 빌드 이슈들


CIS umask 027 때문에 vanilla provisioner 스크립트들이 곳곳에서 깨졌다. 각각 in-place 로 수정.

configure-selinux.sh

matchpathcon -V $DIR/* 가 ec2-user 로 실행되는데 /etc/eks/ 가 0750 root:root 라 glob 확장 실패.

 validate_directory_selinux_contexts() {
   local DIR=$1
   echo "Validating SELinux contexts in $DIR"
 
-  unverified_files=$(matchpathcon -V $DIR/* | grep -v verified)
+  unverified_files=$(sudo bash -c "matchpathcon -V ${DIR}/*" 2>/dev/null | grep -v verified || true)
 
   ...
 }

sudo bash -c 으로 root 가 glob 을 확장하도록 변경. 권한이 없는 디렉토리에서 발생하는 stderr 는 2>/dev/null 로 무시하고, set -o pipefail 환경에서 grep 이 실패해도 빌드가 깨지지 않도록 || true 추가.

cache-pause-container.sh

두 가지 문제. /usr/bin/cache-pause-container 가 umask 027 로 0640 으로 설치되어 실행 권한이 없고, ec2-user 가 containerd socket 에 접근할 수 없어 sudo 없이는 호출도 안 된다.

 set -o nounset
 set -o errexit
 set -o pipefail
 
+sudo chmod 0755 "/usr/bin/cache-pause-container"
+
 sudo systemctl start containerd
-cache-pause-container -i ${PAUSE_CONTAINER_IMAGE}
+sudo cache-pause-container -i ${PAUSE_CONTAINER_IMAGE}
 sudo systemctl stop containerd

install-worker.sh

세 가지 문제.

  1. /usr/bin/imds 가 umask 027 로 0640 으로 설치되어 사용 직전에 chmod 0755 필요.
 ### isolated regions can't communicate to awscli.amazonaws.com so installing awscli through dnf
-
+sudo chmod 0755 "/usr/bin/imds"
 PARTITION=$(imds /latest/meta-data/services/partition)
  1. AWS CLI 설치 시 vanilla 가 --bin-dir /bin/ 옵션을 쓰는데, hardened 환경에서는 생성된 심볼릭 링크와 설치 경로의 권한이 깨져 aws --version 도 동작하지 않았다. default 경로로 설치 후 수동 chmod + 심볼릭 링크 재구성으로 해결.
   unzip -q "${AWSCLI_DIR}/awscliv2.zip" -d ${AWSCLI_DIR}
-  sudo "${AWSCLI_DIR}/aws/install" --bin-dir /bin/ --update
+  sudo "${AWSCLI_DIR}/aws/install" --update
+  sudo chmod -R 755 /usr/local/aws-cli || true
+
+  for target_dir in /bin /usr/bin /usr/local/bin; do
+    sudo rm -f "${target_dir}/aws" 2>/dev/null || true
+    sudo ln -sf /usr/local/aws-cli/v2/current/bin/aws "${target_dir}/aws"
+  done
+
+  sudo chmod 755 /usr/local/aws-cli/v2/current/bin/aws
 fi
  1. 새로 다운받은 kubelet, ecr-credential-provider 바이너리가 /usr/bin/ 또는 /etc/eks/image-credential-provider/ 로 옮겨질 때 mode 0640 → kubelet 시작 실패. mv 직후 chmod 0755.
   sudo sha256sum -c $binary.sha256
   sudo chmod +x $binary
   sudo chown root:root $binary
   sudo mv --context $binary /usr/bin/
+  sudo chmod 0755 "/usr/bin/${binary}"
 done
 sudo chmod +x $ECR_CREDENTIAL_PROVIDER_BINARY
 sudo mkdir -p /etc/eks/image-credential-provider
 sudo mv $ECR_CREDENTIAL_PROVIDER_BINARY /etc/eks/image-credential-provider/
+sudo chmod 0755 "/etc/eks/image-credential-provider/${ECR_CREDENTIAL_PROVIDER_BINARY}"

cleanup.sh

이번 마이그레이션의 가장 큰 변경. 위 해결: cleanup.sh 끝에 chmod 0644 추가 참고.

 sudo touch /etc/machine-id
+# CIS-hardened base AMI sets umask 027, so the touch above creates the file
+# with mode 0640. systemd-machine-id-setup writes the new ID via O_RDWR on
+# the existing file at first boot, preserving this mode -- which makes the
+# file unreadable to the dbus user and breaks dbus-broker. Force 0644.
+sudo chmod 0644 /etc/machine-id

디버깅에서 배운 것


EPERM vs EACCES 구분

  • EACCES (Permission denied): 파일 모드/ACL 에 의한 거부 (stat 의 mode bits)
  • EPERM (Operation not permitted): 보안 layer 에 의한 거부 (SELinux, seccomp, capability, IMA, lockdown 등)

man errno 기준 다른 의미인데 application 에서 strerror 로 출력하면 비슷하게 보일 수 있다. 반드시 strace 등으로 raw errno 를 확인해야 한다.

Serial Console 디버깅 패턴

SSH 와 SSM 안 뜬 상태에서 EC2 Serial Console 만 가능한 상황:

  • launch template 의 자동 종료 (instance failed health check) 때문에 시간 제한이 있다 → EKS managed node group 으로 띄우지 말고 단독 EC2 launch template 으로 띄워서 시간 확보
  • root 로그인이 가능해야 하므로 packer 에 임시로:
    "echo 'root:temppass123' | sudo chpasswd"
  • 네트워크가 안 떠서 dnf install 불가 → strace 등 디버깅 도구는 빌드 시점에 미리 설치:
    "sudo dnf install -y strace"

dbus 가 죽었을 때 망가지는 것들

  • systemd-networkd (hotplug 알림)
  • systemd-logind
  • systemctl, networkctl, loginctl 모두 hang 또는 timeout
  • IMDS 접근 불가 (네트워크 미구성)

tmpfiles.d 의 적합한 사용처

  • 매 부팅 보정이 필요한 경우 (외부 코드가 설정을 바꿀 수 있는 경우)
  • bake-time 변경이 cleanup.sh 등 후속 단계에서 무효화되는 경우
  • 단순 일회성 설정이면 그냥 packer/provisioner 안에서 처리하는 게 깔끔하다

Kubernetes TLS Cipher Suite Deprecation

Kubernetes 1.30+ 의 kubelet 은 forward secrecy 가 없는 TLS cipher 들을 더 이상 지원하지 않는다.

  • 거부되는 것: TLS_RSA_*
  • 안전한 것: TLS_ECDHE_*

OS 튜닝 (sysctl·limits)


AMI 빌드 시 Ansible 로 박은 OS 한계 노브 3 종. EKS Worker node 가 컨테이너·multi-conn·고대역 트래픽을 안정적으로 처리하도록 디폴트보다 큰 값으로 명시.

적용 파일과 값

# /etc/sysctl.d/90-system-open-files-max.conf
fs.file-max = {{ system_file_descriptors_limit }}      # = 196590
# /etc/security/limits.d/30-user-open-files-max.conf
*    soft   nofile  {{ user_file_descriptors_limit }}  # = 65530
*    hard   nofile  {{ user_file_descriptors_limit }}  # = 65530
# /etc/sysctl.d/90-kernel-receive-buffer-size-max.conf
net.core.rmem_max = {{ kernel_buffer_size_max }}      # = 26214400 (25 MB)

Ansible variable 정리:

  • system_file_descriptors_limit: 196590
  • user_file_descriptors_limit: 65530
  • kernel_buffer_size_max: 26214400

노브별 의미와 이유

fs.file-max = 196590

  • 시스템 전체(system-wide) 가 한 번에 열 수 있는 fd 의 절대 한도. sysctl 커널 파라미터.
  • 디폴트가 보통 더 크다 (RAM 1MB 당 ~100 fd). 명시값을 박은 의도 = 인스턴스 type 무관 일관성 보장 (Ansible 패턴) — 실제로는 system 전체가 196K 도달은 비현실적이라 사실상 무해한 노브.

nofile = 65530 (soft = hard)

  • per-process 한도 = 한 프로세스가 동시에 열 수 있는 fd 최대.
  • 디폴트 1024 / 4096 → JVM·Nginx·containerd 같은 multi-conn 프로세스에 EMFILE 발생.
  • 65530 (16-bit 한도 65535 직전) = 충분한 여유.
  • 자세히 = 06. Linux Processes & Systemd > ulimit 과 process limits.

net.core.rmem_max = 26214400 (25 MB)

  • 소켓 receive buffer 의 절대 상한. 디폴트 ~208 KB → 약 125 배 증가.
  • 적용 배경 = 노드 DaemonSet 으로 뜬 Datadog Agent (DogStatsD) 가 high-throughput burst 메트릭을 UDP 로 받을 때 receive buffer 부족 → silent drop → metric 손실. Datadog 공식 권장 따라 rmem_max + Agent 의 dogstatsd_so_rcvbuf 같이 키움. 자세히 = 01. DogStatsD UDP 처리.
  • 부수 효과 = BDP (대역 × RTT) 가 큰 AWS 환경 (cross-AZ·cross-region·S3·EBS 대용량 IO) 에서 TCP throughput 도 같이 향상. 자세히 = 07. Linux Networking > rmem_max·tcp_rmem — TCP buffer 튜닝.

system vs user fd 관계

fs.file-max (196K) vs nofile (65K) → 한 process 가 시스템 fd 의 약 1/3 까지 점유 가능. 한 process 가 폭주해도 시스템 전체 마비 방지 (다른 process 가 fd 받을 여유 확보).

적용 검증

# sysctl
sysctl fs.file-max net.core.rmem_max
 
# nofile (해당 사용자로 실행)
ulimit -n -Hn
 
# 특정 프로세스의 실제 적용값
cat /proc/<pid>/limits | grep -i 'open files'

References