K8S 파드에서 노드를 관리하기 위해 실습한 내용을 공유합니다.
실습 내용은 가시다님이 진행하는 CloudNet 스터디를 참고하였습니다.
파드에서 노드를 관리하기 위한 상황은 많습니다.
서버 모니터링이 될 수 도 있고, 서버 관리를 위해 서드 파티의 솔루션을 사용할 수 있습니다.
노드 관리를 위한 방법을 정리하겠습니다.
방법
|
장점
|
단점
|
사용 추천 상황
|
AWS SSM
|
SSH 없이 원격 실행 가능
|
SSM IAM 권한 필요, 에이전트가 설치되어 있어야 함 수동으로 설치해야 함
|
운영 중인 노드에서 수동으로 설치해야 할 때
|
골든 이미지(AMI)
|
모든 노드에 동일 환경 보장, 빠른 부팅 시간
|
AMI 변경 시 재배포 필요, AMI 관리 부담 있음
|
자주 재사용되는 설정이 없고, 노드 부팅 속도가 필요할 때
|
UserData
|
새로운 노드에 자동 배포
|
기존 노드에는 적용되지 않음, 노드 교체시 전체 노드 재시작 필요
|
EKS에서 기본 소프트웨어 배포 시
|
DaemonSet
|
모든 워커노드에서 자동 실행, 유연함
|
보안적 이슈 (권한 관리 필요) 관리
|
변경 사항이 많을 때
|
Q. AWS SSM 로 노드 관리 ?
AWS System Managers로 대규모 명령 실행이 가능합니다. 공식 문서
targets 를 지정하여 선택한 서버들에 대해 명령을 실행할 수 있습니다.
# 예시
aws ssm send-command \
--document-name document-name \
--targets Key=resource-groups:Name,Values=resource-group-name \
[...]
Daemonset을 통한 SSM 설치 방법 공식 문서

cat << EOF > ssm_daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
k8s-app: ssm-installer
name: ssm-installer
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: ssm-installer
template:
metadata:
labels:
k8s-app: ssm-installer
spec:
containers:
- name: sleeper
image: busybox
command: ['sh', '-c', 'echo I keep things running! && sleep 3600']
initContainers:
- image: amazonlinux
imagePullPolicy: Always
name: ssm
command: ["/bin/bash"]
args: ["-c","echo '* * * * * root yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm & rm -rf /etc/cron.d/ssmstart' > /etc/cron.d/ssmstart"]
securityContext:
allowPrivilegeEscalation: true
volumeMounts:
- mountPath: /etc/cron.d
name: cronfile
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumes:
- name: cronfile
hostPath:
path: /etc/cron.d
type: Directory
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
terminationGracePeriodSeconds: 30
EOF
# 일관 적용
kubectl apply -f ssm_daemonset.yaml
- Daemonset셋을 통해 각 노드에 SSM 에이전트를 설치합니다.
- 노드에 접근하기 위해 컨테이너 보안컨텍스트 특권 권한을 사용합니다.
- 초기화 컨테이너를 통해 SSM 에이전트를 설치합니다. 이는 파드가 재시작되어도 한번만 실행하도록 설정하기 위함입니다.
- 각 파드들은 60분동안 유지되며 종료됩니다. (설치 시간 확보를 위함)
활성화 확인 - 노드 SSM 접근

# 접근 인스턴스 확인
aws ssm describe-instance-information \
--region ap-northeast-2 \
--query "InstanceInformationList[*].{InstanceId:InstanceId, Ping:PingStatus, Name:ComputerName}"
..
{
"InstanceId": "i-082e2721128b863e1",
"Ping": "Online",
"Name": "ip-172-16-0-155.ap-northeast-2.compute.internal"
},
{
"InstanceId": "i-0f8350c95e23e0d8b",
"Ping": "Online",
- system manager 에서 인벤토리 구성에서 연결 후 명령어 실행이 가능합니다.


워크로드에 따라 노드 관리 방법이 다르지만, 이번 블로그 글에서는 Daemonset을 통한 관리 방법을 다루겠습니다.
Daemonset 을 통해 노드 관리를 하는 이점으로 유연함을 들 수 있습니다.
변경 사항이 있을 때마다 노드 교체 없이 Daemonset만 교체하면 되기 때문입니다.
하지만, 고려사항이 많습니다.
파드 권한과 호스트 네임스페이스 공유로 호스트 탈취될 수 있기 때문입니다.
kubectl create -n kube-system -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: root-shell
namespace: kube-system
spec:
containers:
- command:
- /bin/cat
image: alpine:3
name: root-shell
securityContext:
privileged: true
tty: true
stdin: true
volumeMounts:
- mountPath: /host
name: hostroot
hostNetwork: true
hostPID: true
hostIPC: true
tolerations:
- effect: NoSchedule
operator: Exists
- effect: NoExecute
operator: Exists
volumes:
- hostPath:
path: /
name: hostroot
EOF
# 호스트 탈취!
kubectl -n kube-system exec -it root-shell -- chroot /host /bin/bash

위 예제처럼 컨테이너 보안컨텍스트 특권 권한인 privileged 없이 노드(호스트)를 관리해야 합니다.
컨테이너 보안컨텍스트
침해사고 발생 시 침해사고를 당한 권한을 최대한 축소하여 그 사고에 대한 확대를 축소해야 합니다.
이를 위해 컨테이너 보안컨텍스트 종류 중 capabilities 를 이용하겠습니다.
capabilities는 슈퍼 유저의 힘을 작은 조각으로 나누는 옵션입니다. 링크
종류
|
개요
|
privileged
|
특수 권한을 가진 컨테이너로 실행
|
capabilities
|
Capabilities 의 추가와 삭제
|
allowPrivilegeEscalation
|
컨테이너 실행 시 상위 프로세스보다 많은 권한을 부여할지 여부
|
readOnlyRootFilesystem
|
root 파일 시스템을 읽기 전용으로 할지 여부
|
runAsUser
|
실행 사용자
|
runAsGroup
|
실행 그룹
|
runAsNonRoot
|
root 에서 실행을 거부
|
seLinuxOptions
|
SELinux 옵션
|
# Linux Capabilities 확인 : 현재 38개
capsh --print
..
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,cap_perfmon,cap_bpf,cap_checkpoint_restore
# CapPrm 프로세스가 가질 수 있는 전체 권한, CapEff 실제 사용 가능한 권한
# 37비트가 전부 1인 상태
cat /proc/1/status | egrep 'CapPrm|CapEff'
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
# Linux Capabilities 권한
cap_chown 파일이나 디렉토리의 소유자를 변경할 수 있는 권한
cap_dac_override 파일이나 디렉토리의 접근 권한을 무시하고 파일이나 디렉토리에 대한 접근을 수행할 수 있는 권한 (DAC의 약자는 Discretionary access control이다)
cap_dac_read_search 파일이나 디렉토리를 읽거나 검색할 수 있는 권한
cap_fowner 파일이나 디렉토리의 소유자를 변경할 수 있는 권한
cap_fsetid 일이나 디렉토리의 Set-User-ID (SUID) 또는 Set-Group-ID (SGID) 비트를 설정할 수 있는 권한
cap_kill 다른 프로세스를 종료할 수 있는 권한
cap_setgid 프로세스가 그룹 ID를 변경할 수 있는 권한
cap_setuid 프로세스가 사용자 ID를 변경할 수 있는 권한
cap_setpcap 프로세스가 자신의 프로세스 권한을 변경할 수 있는 권한
cap_linux_immutable 파일의 immutability(불변성) 속성을 변경할 수 있는 권한을 제공
cap_net_bind_service 프로그램이 특정 포트에 바인딩(bind)하여 소켓을 개방할 수 있는 권한
cap_net_broadcast 프로세스가 네트워크 브로드캐스트 메시지를 보낼 수 있는 권한
cap_net_admin 네트워크 인터페이스나 소켓 설정을 변경할 수 있는 권한
cap_net_raw 네트워크 패킷을 송수신하거나 조작할 수 있는 권한
cap_ipc_lock 메모리 영역을 잠금(lock)하고 언락(unlock)할 수 있는 권한
cap_ipc_owner IPC 리소스(Inter-Process Communication Resources)를 소유하고, 권한을 변경할 수 있는 권한
cap_sys_module 커널 모듈을 로드하거나 언로드할 수 있는 권한
cap_sys_rawio 입출력(I/O) 포트와 같은 하드웨어 리소스를 직접 접근할 수 있는 권한
cap_sys_chroot 프로세스가 chroot() 시스템 콜을 호출하여 프로세스의 루트 디렉토리를 변경할 수 있는 권한
cap_sys_ptrace 다른 프로세스를 추적(trace)하거나 디버깅할 수 있는 권한
cap_sys_pacct 프로세스 회계(process accounting)를 위한 파일에 접근할 수 있는 권한
cap_sys_admin 시스템 관리자 권한을 제공하는 권한
cap_sys_boot 시스템 부팅과 관련된 작업을 수행할 수 있는 권한
cap_sys_nice 프로세스의 우선순위를 변경할 수 있는 권한
cap_sys_resource 자원 제한(resource limit)과 관련된 작업을 수행할 수 있는 권한
cap_sys_time 시스템 시간을 변경하거나, 시간 관련 시스템 콜을 사용할 수 있는 권한
cap_sys_tty_config 터미널 설정을 변경할 수 있는 권한
cap_mknod mknod() 시스템 콜을 사용하여 파일 시스템에 특수 파일을 생성할 수 있는 권한
cap_lease 파일의 잠금과 관련된 작업을 수행할 수 있는 권한
cap_audit_write 시스템 감사(audit) 로그에 대한 쓰기 권한
cap_audit_control 시스템 감사(audit) 설정과 관련된 작업을 수행할 수 있는 권한
cap_setfcap 파일 시스템 캡러빌리티(file system capability)을 설정할 수 있는 권한
cap_mac_override SELinux 또는 AppArmor과 같은 MAC(Mandatory Access Control) 시스템을 우회하고 자신의 프로세스가 접근 가능한 파일, 디바이스, 네트워크 등을 제한 없이 접근할 수 있는 권한
cap_mac_admin SELinux 또는 AppArmor과 같은 MAC(Mandatory Access Control) 시스템을 관리하고 수정할 수 있는 권한
cap_syslog 시스템 로그를 읽거나, 쓸 수 있는 권한
cap_wake_alarm 시스템의 RTC(Real-Time Clock)를 사용하여 장치를 깨우거나 슬립 모드를 해제할 수 있는 권한
cap_block_suspend 시스템의 전원 관리 기능 중 하나인 Suspend(절전 모드)를 방지하는 권한
cap_audit_read 시스템 감사(audit) 로그를 읽을 수 있는 권한
파드의 Capabilities 권한 확인
# 샘플 파드 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: sample-capabilities
spec:
containers:
- name: nginx-container
image: masayaaoyama/nginx:capsh
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
kubectl exec -it sample-capabilities -- capsh --print | grep Current
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+ep
cap_chown # 파일이나 디렉토리의 소유자를 변경할 수 있는 권한
cap_dac_override # 파일이나 디렉토리의 접근 권한을 무시하고 접근할 수 있는 권한 (DAC = Discretionary Access Control)
cap_fowner # 소유하지 않은 파일/디렉토리에 대해 권한 변경 등 소유자 권한 수행 가능
cap_fsetid # SUID/SGID 비트를 설정하거나 유지할 수 있는 권한
cap_kill # 다른 사용자의 프로세스에 시그널(SIGNAL)을 보낼 수 있는 권한
cap_setgid # 그룹 ID(GID)를 변경할 수 있는 권한
cap_setuid # 사용자 ID(UID)를 변경할 수 있는 권한
cap_setpcap # 프로세스의 capability를 추가/제거할 수 있는 권한
cap_net_bind_service # 1024 이하의 privileged 포트(예: 80, 443)에 바인딩할 수 있는 권한
cap_net_raw # raw socket을 사용하여 네트워크 패킷을 조작하거나 수신/전송할 수 있는 권한 (예: ping)
cap_sys_chroot # chroot() 시스템 콜을 사용하여 루트 디렉토리를 변경할 수 있는 권한
cap_mknod # mknod()를 사용하여 특수 파일(디바이스 노드 등)을 생성할 수 있는 권한
cap_audit_write # 커널의 audit 로그에 기록을 쓸 수 있는 권한
cap_setfcap+ep # 파일에 file-based capability를 설정할 수 있는 권한 (+ep는 현재 활성화 중임을 의미)
# proc 에서 확인 : bit 별 Capabilities
kubectl exec -it sample-capabilities -- cat /proc/1/status | egrep 'CapPrm|CapEff'
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
# 시간 변경 시도
kubectl exec -it sample-capabilities -- date
Thu Apr 10 15:05:16 UTC 2025
# 불가
kubectl exec -it sample-capabilities -- date -s "12:00:00"
date: cannot set date: Operation not permitted
Thu Apr 10 12:00:00 UTC 2025
command terminated with exit code 1
# 시간 확인
kubectl exec -it sample-capabilities -- date
Thu Apr 10 15:06:47 UTC 2025
파드 내 Capabilities 부여
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: sample-capabilities2
spec:
containers:
- name: nginx-container
image: masayaaoyama/nginx:capsh
command: ["tail"]
args: ["-f", "/dev/null"]
securityContext:
capabilities:
add: ["NET_ADMIN", "SYS_TIME"] # 네트워크IP 어드민 및 시간 권한 부여
drop: ["AUDIT_WRITE"]
terminationGracePeriodSeconds: 0
EOF
# 권한 추가 확인
kubectl exec -it sample-capabilities2 -- capsh --print | grep Current
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_admin,cap_net_raw,cap_sys_chroot,cap_sys_time,cap_mknod,cap_setfcap+ep
# 파드 상세 확인
kubectl get pod sample-capabilities2 -o jsonpath="{.spec.containers[0].securityContext}" | jq
{
"capabilities": {
"add": [
"NET_ADMIN",
"SYS_TIME"
],
"drop": [
"AUDIT_WRITE"
]
}
# 시간 변경 시도
kubectl exec -it sample-capabilities2 -- date -s "12:00:00"
Thu Apr 10 12:00:00 UTC 2025
# 배포 노드 시간 확인, 변경 확인
[root@ip-100-64-191-230 /]# date
Thu Apr 10 12:01:07 UTC 2025
왜 추가 구성이 필요할까?
쿠버네티스에서는 Job 대상을 노드 별로 관리하는 오브젝트는 따로 없습니다.
Daemonset 오브젝트에서 추가 관리르 추가하여 관리가 필요합니다.
기능 요구사항은 인터넷 서치를 통해 찾을 수 있지만, 명확한 가이드는 없습니다.
오픈소스 확인하기
모니터링 파드들의 경우 어떻게 노드 메트릭을 가져오는 지 확인하겠습니다.
- 노드의 메트릭을 수집하여 Prometheus로 전송
- 노드의 상태와 자원 사용량을 모니터링
apiVersion: apps/v1
kind: DaemonSet
metadata:
annotations:
deprecated.daemonset.template.generation: "1"
meta.helm.sh/release-name: kube-prometheus-stack
meta.helm.sh/release-namespace: kube-prometheus-stack
creationTimestamp: "2025-04-08T01:34:42Z"
generation: 1
labels:
app.kubernetes.io/component: metrics
app.kubernetes.io/instance: kube-prometheus-stack
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: prometheus-node-exporter
app.kubernetes.io/part-of: prometheus-node-exporter
app.kubernetes.io/version: 1.6.0
helm.sh/chart: prometheus-node-exporter-4.18.1
jobLabel: node-exporter
release: kube-prometheus-stack
name: kube-prometheus-stack-prometheus-node-exporter
namespace: kube-prometheus-stack
resourceVersion: "4845"
uid: 44407fc6-018f-4d31-a49f-79666502face
spec:
revisionHistoryLimit: 10
selector:
matchLabels:
app.kubernetes.io/instance: kube-prometheus-stack
app.kubernetes.io/name: prometheus-node-exporter
template:
metadata:
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
creationTimestamp: null
labels:
app.kubernetes.io/component: metrics
app.kubernetes.io/instance: kube-prometheus-stack
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: prometheus-node-exporter
app.kubernetes.io/part-of: prometheus-node-exporter
app.kubernetes.io/version: 1.6.0
helm.sh/chart: prometheus-node-exporter-4.18.1
jobLabel: node-exporter
release: kube-prometheus-stack
spec:
automountServiceAccountToken: false
containers:
- args:
- --path.procfs=/host/proc
- --path.sysfs=/host/sys
- --path.rootfs=/host/root
- --path.udev.data=/host/root/run/udev/data
- --web.listen-address=[$(HOST_IP)]:9100
- --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+|var/lib/kubelet/.+)($|/)
- --collector.filesystem.fs-types-exclude=^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$
env:
- name: HOST_IP
value: 0.0.0.0
image: quay.io/prometheus/node-exporter:v1.6.0
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /
port: 9100
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
name: node-exporter
ports:
- containerPort: 9100
name: http-metrics
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /
port: 9100
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources: {}
securityContext:
readOnlyRootFilesystem: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /host/proc
name: proc
readOnly: true
- mountPath: /host/sys
name: sys
readOnly: true
- mountPath: /host/root
mountPropagation: HostToContainer
name: root
readOnly: true
dnsPolicy: ClusterFirst
hostNetwork: true
hostPID: true
nodeSelector:
kubernetes.io/os: linux
restartPolicy: Always
schedulerName: default-scheduler
securityContext:
fsGroup: 65534
runAsGroup: 65534
runAsNonRoot: true
runAsUser: 65534
serviceAccount: kube-prometheus-stack-prometheus-node-exporter
serviceAccountName: kube-prometheus-stack-prometheus-node-exporter
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoSchedule
operator: Exists
volumes:
- hostPath:
path: /proc
type: ""
name: proc
- hostPath:
path: /sys
type: ""
name: sys
- hostPath:
path: /
type: ""
name: root
updateStrategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
type: RollingUpdate
status:
currentNumberScheduled: 2
desiredNumberScheduled: 2
numberAvailable: 2
numberMisscheduled: 0
numberReady: 2
observedGeneration: 1
updatedNumberScheduled: 2
- hostPID: true 및 hostNetwork: true: 파드가 호스트의 PID 네임스페이스와 네트워크 네임스페이스를 공유하여 노드에 대한 정확한 정보를 수집합니다.
- readOnlyRootFilesystem: true: 보안 강화를 위해 컨테이너 내부 파일시스템을 읽기 전용으로 설정하여 파일 시스템 변경을 방지합니다.
노드 관리 요구사항
Daemonset를 통해 노드 관리를 하기 위한 고려 사항을 검토하겠습니다.
- 노드당 한 번만 실행 (initcontainer)
- 스크립트를 통해 명령어 실행하기
- 실행 결과 확인 가능(kubectl logs)
- 보안 이슈 최소화(Linux Capabilities) 이용
위에서 예제로 활용한 AWS SSM daemonset 설치 스크립트를 적용하겠습니다.
# 스크립트 분리
cat << 'EOT' > install.sh
#!/bin/bash
set -e
echo "[INFO] 사용자 스크립트 실행 중"
echo "노드 이름: \$(hostname)"
echo "현재 시간: \$(date)"
touch /tmp/ssm-installed.marker
# 예: SSM 설치
yum install -y \
https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
echo "[INFO] 설치 완료"
EOT
# 컨피그맵 설정
kubectl create configmap user-install-script \
--from-file=install.sh=./install.sh \
-n kube-system
# 데몬셋 배포
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ssm-installer
namespace: kube-system
labels:
k8s-app: ssm-installer
spec:
selector:
matchLabels:
k8s-app: ssm-installer
template:
metadata:
labels:
k8s-app: ssm-installer
spec:
initContainers: # 한번만 실행
- name: user-script-runner
image: amazonlinux:2023
command: ["/bin/bash", "/scripts/install.sh"]
volumeMounts:
- name: script-volume
mountPath: /scripts
securityContext:
allowPrivilegeEscalation: false # 프로세스에서 더 높은 권한을 못가짐
capabilities:
add: ["CAP_DAC_OVERRIDE"] # 파일 작업 권한 부여
containers:
- name: sleeper
image: busybox
command: ["sh", "-c", "sleep 3600"]
restartPolicy: Always
volumes:
- name: script-volume
configMap:
name: user-install-script
# 로그 확인
kubectl logs -n kube-system -l k8s-app=ssm-installer -c user-script-runner

'Cloud' 카테고리의 다른 글
EKS Version Upgrade (0) | 2025.04.02 |
---|---|
EKS Fargate & AutoMode (0) | 2025.03.23 |
EKS Security (2) | 2025.03.16 |
EKS Karpenter (0) | 2025.03.09 |
EKS Autoscaling (0) | 2025.03.09 |