Block 3: 노드 부트스트랩 & 라이프사이클

사전 준비 → nodeadm → Cilium → 업그레이드 → 트러블슈팅 → 크리덴셜

조인 전에 끝낼 AWS 사전 준비

노드 IAM 역할에 HYBRID_LINUX 타입 access entry를 만듭니다. 일반 SSM 관리 역할만으로는 조인되지 않습니다.

{ "clusterName": "my-hybrid-cluster", "principalArn": "arn:aws:iam::123456789012:role/EKSHybridNodeRole", "type": "HYBRID_LINUX" }
  • 매핑은 system:node:{{SessionName}} + node-bootstrap 그룹 사용
  • 기존 mapping을 지우는 한-role aws-auth ConfigMap은 적용하지 않기

클러스터 생성 시 원격 노드/Pod CIDR을 선언합니다. 기존 클러스터에도 remote network 구성을 추가할 수 있습니다.

# ClusterConfig 중 hybrid 관련 부분 remoteNetworkConfig: iam: provider: SSM roleARN: arn:aws:iam::123456789012:role/EKSHybridNodeRole vpcGatewayID: tgw-0123456789abcdef0 remoteNodeNetworks: - cidrs: - 10.80.0.0/16 remotePodNetworks: - cidrs: - 10.85.0.0/16
  • CIDR은 IPv4, 종류별 최대 15개, RFC1918 또는 CGNAT(100.64.0.0/10)

AmazonEKSWorkerNodeMinimalPolicy만으로는 eks:DescribeCluster 권한이 없습니다. 역할에 직접 추가합니다.

{ "Effect": "Allow", "Action": "eks:DescribeCluster", "Resource": "arn:aws:eks:ap-northeast-2:123456789012:cluster/my-hybrid-cluster" }
  • SSM: ECR pull + SSM core + 등록 해제 권한 (ssm:DeregisterManagedInstance)
  • IAM Roles Anywhere: 프로필에 acceptRoleSessionName: true
  • Pod Identity는 eks-auth:AssumeRoleForPodIdentity를 별도로 부여

nodeadm: 명령은 여섯 개뿐

install
kubelet, containerd, SSM agent 등 의존성 설치
config check
로컬 NodeConfig 검사 (조인 시험 아님)
init
호스트 구성 + EKS 조인
upgrade
in-place 버전 변경 (중단 작업)
uninstall
구성 요소 제거 + SSM 등록 해제
debug
AWS/클러스터 연결, 자격 증명 진단
v1.0.20: 저장소 aws/eks-hybrid (EC2용 amazon-eks-ami의 nodeadm과 다름). SSM 신규 설치, 업그레이드는 1.0.19 이상 필요.
없는 것: upgrade --rollback, reset, init --dry-run은 존재하지 않습니다.
# releases/latest 대신 승인한 릴리스를 받고 체크섬을 검증한 뒤 설치 printf '%s %s\n' "$NODEADM_SHA256" "$STAGING_DIR/nodeadm" | sha256sum --check - sudo install -m 0755 "$STAGING_DIR/nodeadm" /usr/local/bin/nodeadm

부트스트랩은 다섯 걸음

1install
kubelet, containerd 등 의존성 설치. 등록은 하지 않음
2config check
NodeConfig 로컬 검사. 통과해도 조인을 보증하지 않음 (dry-run 아님)
3init
호스트 구성, SSM 등록 등 자격 증명 설정, EKS 조인
4CNI
CNI 설치 전까지 Node가 NotReady인 것은 정상일 수 있음
5Ready 검증
노드 UID 대조, 라벨, DNS, 이미지 풀, 자격 증명 갱신
Ready는 필요 조건일 뿐: 실제 워크로드까지 확인한 뒤에 서비스에 투입합니다.

NodeConfig는 공급자 하나로 충분

apiVersion: node.eks.aws/v1alpha1 kind: NodeConfig spec: cluster: name: my-hybrid-cluster region: ap-northeast-2 hybrid: ssm: activationCode: REPLACE_WITH_ACTIVATION_CODE activationId: REPLACE_WITH_ACTIVATION_ID kubelet: config: maxPods: 110 flags: - --node-labels=example.com/location=onprem

파일은 root 소유 0600으로 보호하고 활성화 코드를 로그에 남기지 않습니다. 등록 후 Node 이름은 mi-*입니다.

apiVersion: node.eks.aws/v1alpha1 kind: NodeConfig spec: cluster: name: my-hybrid-cluster region: ap-northeast-2 hybrid: iamRolesAnywhere: nodeName: hybrid-node-001 trustAnchorArn: arn:aws:rolesanywhere:ap-northeast-2:123456789012:trust-anchor/REPLACE_ID profileArn: arn:aws:rolesanywhere:ap-northeast-2:123456789012:profile/REPLACE_ID roleArn: arn:aws:iam::123456789012:role/EKSHybridNodeRole certificatePath: /etc/iam/pki/server.pem privateKeyPath: /etc/iam/pki/server.key
  • 필수 입력은 클러스터 이름 + 리전 + 공급자 블록
  • API 엔드포인트, CA, CIDR은 직접 쓰지 않습니다. 역할의 eks:DescribeCluster로 nodeadm이 조회합니다
  • IAM Roles Anywhere는 nodeName, certificatePath, privateKeyPath가 추가로 필요
  • nodeName은 인증서 CN과 역할 신뢰 정책 조건에 묶이며 최대 64자
  • 인증서 하나를 여러 머신에 재사용하지 않습니다

Bottlerocket은 nodeadm이 없다

범위 VMware variant, x86_64, OS 1.37.0+ (vSphere 전용)
방식 settings.toml + eks-hybrid-setup bootstrap container, guestinfo.userdata.encoding=base64

오른쪽은 공통 조각입니다. 공급자별 설정은 AWS 가이드에서 조합하고, api-server, CA는 자동 조회되지 않으므로 직접 기입합니다. 이 TOML은 join에 필요한 최소 설정이라 static IP 같은 네트워크 설정은 Bottlerocket 문서로 따로 검증하세요.

주의: base64는 암호화가 아닙니다. 활성화 비밀과 개인 키가 들어갈 수 있으니 VM 구성, guestinfo를 보호하고, 등록된 VM을 템플릿으로 복제하지 않습니다.

[settings.kubernetes] cluster-name = "my-hybrid-cluster" api-server = "https://REPLACE_WITH_CLUSTER_ENDPOINT" cluster-certificate = "REPLACE_WITH_BASE64_CLUSTER_CA" hostname-override = "hybrid-node-001" provider-id = "eks-hybrid:///ap-northeast-2/my-hybrid-cluster/hybrid-node-001" authentication-mode = "aws" cloud-provider = "" server-tls-bootstrap = true [settings.aws] region = "ap-northeast-2" [settings.kubernetes.node-labels] "eks.amazonaws.com/compute-type" = "hybrid" [settings.bootstrap-containers.eks-hybrid-setup] mode = "always" user-data = "REPLACE_WITH_BASE64_PROVIDER_BOOTSTRAP_INPUT"

containerd 소스는 OS가 정한다

Ubuntu / AL2023
distro (기본)
RHEL
docker 또는 none, distro는 무효
AL2023
docker 사용 불가
none
containerd 설치를 건너뜀, 미리 설치, 관리해야 함
sudo nodeadm install "$KUBERNETES_VERSION" \ --credential-provider "$CREDENTIAL_PROVIDER" \ --containerd-source "$CONTAINERD_SOURCE" --region "$REGION" --timeout 20m

베어메탈은 이미지로 찍어낸다

Packer
hybrid-nodes-template.pkr.hcl, vSphere OVA, QEMU qcow2/raw
Ubuntu Autoinstall
Subiquity YAML(cloud-init). 디스크는 승인한 serial로만 지정
RHEL Kickstart
ignoredisk와 clearpart를 같은 디스크로 제한
K8S_VERSION 검증: v1.0.20 템플릿은 1.26–1.31만 허용합니다. 템플릿 때문에 버전을 낮추지 말고 검토한 포크를 유지하세요.
export NODEADM_ARCH=amd # amd/arm (뒤에 64가 붙음) export CREDENTIAL_PROVIDER=ssm # ssm/iam (iam은 nodeadm의 iam-ra로 변환) export PACKER_OUTPUT_FORMAT=raw packer validate -syntax-only hybrid-nodes-template.pkr.hcl

Cilium: 하이브리드 노드에만 설치

affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: eks.amazonaws.com/compute-type operator: In values: [hybrid] ipam: mode: cluster-pool operator: clusterPoolIPv4MaskSize: 25 clusterPoolIPv4PodCIDRList: [10.85.0.0/16]
operator: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: eks.amazonaws.com/compute-type operator: In values: [hybrid] unmanagedPodWatcher: restart: false envoy: enabled: false kubeProxyReplacement: "false"
helm install cilium oci://public.ecr.aws/eks/cilium/cilium \ --version "$CILIUM_VERSION" --namespace kube-system \ --values cilium-values.yaml --wait --timeout 10m
  • AWS 지원 범위는 AWS 유지 관리 빌드(oci://public.ecr.aws/eks/cilium/cilium)
  • 공식 CNI 페이지: 1.17.x / 1.18.x (예: 1.17.9-0, 1.18.3-0)
  • Hybrid Nodes Gateway를 쓰면 branch별 VTEP 최소 1.17.13-1, 1.18.8-1, 1.19.2-1: 위 예시 버전으로는 Gateway 불가
  • 1.18.3은 커널 요구로 Ubuntu 20.04, RHEL 8 제외
  • 업스트림 최신 차트를 임의로 쓰지 않습니다

마스크가 노드당 Pod 수를 정한다

MaskSize전체 IPv4사용 가능 (−2 예약)maxPods 산정 예
/26646250
/25128126110
/24256254240
AWS 공식 가이드
설치 후 clusterPoolIPv4PodCIDRList와 MaskSize를 변경하지 않는다
Cilium 문서 기준
기존 항목, MaskSize는 수정 금지. 확장은 새 CIDR 항목 추가
산정 예일 뿐: maxPods 값은 검증된 권장값이 아닙니다. hostNetwork Pod, 데몬, CPU/메모리 여유를 함께 검토하세요.

라벨은 하나, 테인트는 없다

kubectl get nodes -l eks.amazonaws.com/compute-type=hybrid -o wide kubectl get node "$EXPECTED_NODE_NAME" --show-labels kubectl wait --for=condition=Ready "node/$EXPECTED_NODE_NAME" --timeout=5m kubectl -n kube-system get pods -o wide | grep cilium
자동 라벨
eks.amazonaws.com/compute-type=hybrid 하나뿐. --node-labels로 따로 넣을 필요 없음
추가 라벨
NodeConfig kubelet.flags의 --node-labels=...
테인트
자동 부여 없음. 선택 사항이며 쓰면 워크로드, CNI에 toleration 필요
kubectl taint nodes hybrid-gpu-001 gpu=true:NoSchedule # 운영 중 수동 테인트

kubelet 튜닝: 예약, 축출, maxPods

kubelet: config: systemReserved: cpu: "500m" memory: "1Gi" ephemeral-storage: "10Gi" kubeReserved: cpu: "500m" memory: "1Gi" ephemeral-storage: "5Gi"

Node Allocatable에서 차감하는 예약량입니다. OS, kubelet, 런타임, CNI 사용량을 측정해 정합니다. 위 값은 초기 산정 예입니다.

kubelet: config: evictionHard: memory.available: 200Mi nodefs.available: 10% imagefs.available: 15% nodefs.inodesFree: 5% imagefs.inodesFree: 5% evictionSoft: memory.available: 500Mi evictionSoftGracePeriod: memory.available: 1m30s

Hard 임계값은 유예 없이 처리됩니다. evictionHard를 바꾸면 inode 항목까지 전체 의도한 임계값을 명시하세요. 누락한 기본 임계값은 0이 됩니다.

kubelet: config: maxPods: 110 imageGCHighThresholdPercent: 85 imageGCLowThresholdPercent: 80 shutdownGracePeriod: 60s shutdownGracePeriodCriticalPods: 20s

maxPods는 Cilium 노드별 pool 크기(/25 → 사용 가능 126)와 함께 정합니다. 종료 유예 60초에는 critical Pod용 20초가 포함됩니다.

업그레이드는 교체가 기본이다

  • 권장: 새 호스트를 준비해 전환: 새 노드를 초기화, 검증한 뒤 이전 호스트를 비웁니다 (이전 호스트는 검증 완료까지 보존)
  • in-place: 비운(cordon + drain) 호스트에서 nodeadm upgrade 실행. 중단 작업이며 트랜잭션식 다운그레이드가 아님
  • kubelet은 API 서버보다 새 버전일 수 없고, n-3까지 뒤처질 수 있음 (계속 유지하라는 권장은 아님)
  • 뒤처진 노드를 먼저 현재 컨트롤 플레인 마이너로 맞춘 뒤, 다음 마이너는 컨트롤 플레인부터
sudo /usr/local/bin/nodeadm upgrade "$TARGET_MINOR" \ -c file:///etc/eks/nodeconfig.yaml --timeout 20m
  1. 현재 values, Helm revision 저장, 버전별 업그레이드 문서 확인
  2. Hybrid 노드로 한정한 별도 preflight 릴리스 실행 (preflight.enabled=true, agent=false, operator.enabled=false)
  3. DaemonSet 커버리지와 검증 Deployment readiness를 둘 다 대기
  4. 통과 후 preflight 릴리스만 제거
  5. 기존 values + 적절한 upgradeCompatibility로 업그레이드, 한 마이너씩

마이너 버전 사이에 --reuse-values를 쓰지 않습니다.

  • nodeadm upgrade --rollback은 없습니다
  • 실패 시 해당 노드의 cordon 유지, 나머지 작업 중단
  • 현재 컨트롤 플레인과 호환되는 승인된 이미지/버전의 호스트로 교체
  • /var/lib/kubelet, /etc/kubernetes 재귀 삭제는 롤백 단계가 아님
  • uninstall은 SSM 등록도 해제하므로 무조건 uninstall/reinstall 반복 금지
  • Cilium만 명시한 Helm revision으로 helm rollback 가능하나 데이터 경로 복구를 보장하지 않음 (CRD 호환성 검토 필요)

1.37 업그레이드 전 필수 점검

  • 1.37에서 SELinuxMount가 기본 활성화: SELinux가 켜진 노드(예: RHEL)만 영향
  • CSI driver가 seLinuxMount: true이고 Pod에 seLinuxOptions가 있으면 mount -o context=LABEL로 마운트
  • 같은 노드에서 한 volume을 다른 label, seLinuxChangePolicy, 권한 수준으로 공유하면 뒤에 뜬 Pod가 ContainerCreating에 멈춤
  • 공유 Pod끼리 seLinuxOptions.level과 seLinuxChangePolicy를 맞추고, privileged와 unprivileged가 섞이면 Recursive로 지정
kubectl get csidriver \ -o custom-columns=NAME:.metadata.name,SELINUXMOUNT:.spec.seLinuxMount
  • deprecated cAdvisor flag가 남아 있으면 kubelet이 기동하지 않음 (--housekeeping-interval만 예외)
  • eventRecordQPS: 0은 이제 무제한: 이전 동작(초당 5개)을 유지하려면 5로 변경
  • metric 제거: container_cpu_load_average_10s, container_cpu_load_d_average_10s, container_tasks_state → 대시보드, 알람 수정
  • kubelet이 시작할 때 전체 설정을 로그에 남김 → 노드 로그 읽기 권한 제한

"EKS 최적화 AMI는 영향 없음"은 Hybrid 노드에 해당하지 않습니다. NodeConfig의 kubelet.flags, kubelet.config와 골든 이미지에 넣은 인자를 점검합니다.

  • 1.35: cgroup v1 노드에서 kubelet 기동 거부 (기본값), Ubuntu 20.04, RHEL 8은 기본이 cgroup v1
  • 1.35: kubelet --pod-infra-container-image flag 제거, NodeConfig kubelet.flags, 골든 이미지에서 삭제
  • 1.36: containerd 2.0 이상 필요 (1.35가 containerd 1.x 마지막)
  • 1.36: gitRepo volume 비활성, StrictIPCIDRValidation 기본 활성, Service externalIPs deprecated
  • 업그레이드는 한 마이너씩, 이전 버전의 변경 사항도 모두 적용되므로 함께 점검

위 항목은 업그레이드 전에 끝낼 일입니다. 노드에서 확인하지 못했다면 새 호스트로 먼저 검증합니다.

제한망 ≠ 단절: AWS 연결은 필수

  • 제한망 = 퍼블릭 인터넷은 제한, 하지만 EKS 컨트롤 플레인과 자격 증명용 AWS 서비스 연결은 계속 필요
  • 물리적 단절(DDIL)은 Hybrid Nodes의 지원 운영 모델이 아님, IAM Roles Anywhere도 CreateSession 호출이 필요
  • 온프레미스에서 S3에는 Interface 엔드포인트 사용 (gateway 엔드포인트는 온프레미스에서 직접 접근 불가)
  • 소프트웨어는 승인한 manifest를 자체 미러에서 사용, artifact와 manifest는 검토한 절차로 직접 준비 (nodeadm은 SigV4 서명 없는 HTTP(S)로 다운로드)
sudo nodeadm install "$APPROVED_PATCH" --region "$REGION" \ --credential-provider iam-ra --containerd-source none \ --manifest-override "file://$LOCAL_MANIFEST" --private-mode sudo nodeadm init --config-source file:///etc/eks/nodeconfig.yaml \ --manifest-override file:///etc/eks/manifest.json --private-mode

--private-mode는 --manifest-override가 필수이며 OS 패키지 설치를 건너뜁니다. containerd, runc, iptables, CA 인증서는 미리 설치해 두세요.

hybrid-assets.eks.amazonaws.com을 Route 53 PHZ에서 S3로 CNAME 해도 투명 미러가 되지 않습니다.

  • TLS 인증서, SNI 호스트명 불일치
  • HTTP Host 헤더와 S3 버킷, 키 매핑 불일치
  • 요청 권한(서명) 미제공

TLS 검사를 끄는 방식으로 해결하지 않습니다. 대신 통제된 프록시, 사전 설치 이미지, 실제 미러 URL의 manifest override 중 선택하세요.

장애는 증상별로 좁혀 간다

sudo nodeadm debug --config-source file:///etc/eks/nodeconfig.yaml sudo journalctl -u kubelet --since '-15 min' --lines 200 --no-pager
  • 노드 IP가 EKS remote node CIDR 안에 있는가
  • DNS, 라우팅, TCP 443 반환 경로, 의도한 Hybrid 역할, HYBRID_LINUX access entry
  • 엔드포인트가 "Public and Private"로 설정되지 않았는가 (public IP로 해석되어 join 실패 가능)
  • eks:DescribeCluster 권한 여부
sudo systemctl status kubelet containerd --no-pager kubectl describe node "$NODE" kubectl -n kube-system get pods -o wide | grep cilium
  • CNI, agent 로그, 런타임, 디스크, 리소스 조건 확인
  • NotReady가 항상 CNI 누락 때문은 아님
  • NotReady만으로 등록 해제, 재초기화하지 않기 (nodeadm reset은 없음)
  • API → kubelet TCP 10250이 온프레미스 방화벽으로 들어오는가
  • Webhook은 실제 설정한 포트, Pod IP 경로가 열려 있는가
  • 이미지 풀/x509 오류: ECR api, dkr, S3, 레지스트리 CA와 호스트명 (TLS 검증 유지, curl -k 금지)
  • DNS: CoreDNS가 클라우드/온프레미스 양쪽에 있는지 확인
  • EKS cluster insights의 hybrid nodes 점검 항목도 함께 확인
openssl x509 -in "$CERT_PATH" -checkend 2592000 -noout sudo nodeadm debug --config-source file:///etc/eks/nodeconfig.yaml
  • SSM: 활성화 만료(신규 등록 제한)와 에이전트 갱신, 시각 동기화를 구분
  • IAM Roles Anywhere: 인증서 만료, 역할 세션 시간, AWS 접근 확인
  • sudo aws sts get-caller-identity는 kubelet이 쓰는 신원을 증명하지 않음

크리덴셜 만료를 먼저 감시하라

  • 활성화 기본 만료 24시간, 최대 30일, RegistrationLimit 1–1000
  • 활성화 만료는 신규 등록만 제한, 이미 등록한 노드는 영향 없음
  • 세션 자격 증명은 고정 1시간, 에이전트가 갱신
  • 새 호스트 등록이나 승인된 복구에만 새 활성화 생성
aws ssm create-activation --iam-role "$HYBRID_NODE_ROLE_NAME" \ --registration-limit "$NEW_NODE_COUNT" --region "$AWS_REGION" \ --output json > activation.private.json # 응답은 비공개 파일에 저장
  • 세션 기본 1시간, 범위 15분–12시간, 역할 MaxSessionDuration 이하
  • 노드 호스트 인증서 만료를 모니터링 (30일은 경고 임계값 예시)
# 만료까지 30일 미만이면 실패(종료 코드 1) openssl x509 -in "$CERT_PATH" -checkend 2592000 -noout
  • 인증서 갱신은 기존 PKI/CA 정책으로, 후보 인증서는 활성 파일과 분리 검증 후 교체
  • kubelet 재시작만으로 X.509가 갱신되지 않음

동일 CA에서 leaf만 갱신하면 trust anchor 교체는 보통 불필요합니다. CA rollover는 모든 호스트, profile, role에 영향을 줍니다.

jq -n --rawfile bundle "$APPROVED_CA_PEM" \ '{sourceType:"CERTIFICATE_BUNDLE", sourceData:{x509CertificateData:$bundle}}' \ > trust-anchor-source.private.json aws rolesanywhere update-trust-anchor --trust-anchor-id "$TRUST_ANCHOR_ID" \ --region "$AWS_REGION" --source file://trust-anchor-source.private.json

카나리 노드에서 새 자격 증명 발급을 검증한 뒤 마칩니다.

Block 3 Quiz (1/3)

Q1: nodeadm NodeConfig에서 반드시 직접 채워야 하는 최소 항목은?
Q2: nodeadm이 하이브리드 노드에 자동으로 붙여 주는 라벨은?

Block 3 Quiz (2/3)

Q3: nodeadm upgrade가 실패했을 때 올바른 대응은?
Q4: 제한망(air-gap)에서 비공개 manifest로 설치할 때 쓰는 옵션 조합은?

Block 3 Quiz (3/3)

Q5: EKS 1.37 kubelet에서 eventRecordQPS: 0의 의미는?

References & Q&A

nodeadm 레퍼런스
docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-nodeadm.html
자격 증명
docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-creds.html
Hybrid CNI
docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-cni.html
Bottlerocket
docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-bottlerocket.html
업그레이드, 릴리스 노트
docs.aws.amazon.com/eks/latest/userguide/hybrid-nodes-upgrade.html
docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions-standard.html
nodeadm 소스
github.com/aws/eks-hybrid (v1.0.20)