Skip to content

사전 요구 사항 ​

지원 버전: 예제 검토 기준 EKS 1.36 / hybrid nodeadm 1.0.20; OS별 조건은 아래 참조 마지막 업데이트: 2026년 9월 13일

Hybrid node join 전에 host·network·credential·cluster access를 준비합니다. 로컬 schema/crypto/input 검사는 실제 물리 네트워크·GPU runtime·프로덕션 cluster 검증이 아닙니다. 이번 감사에서 cloud·host network·GPU driver·image build 변경은 실행하지 않았습니다.

Hybrid 사전 조건과 VPC/on-prem 양방향 routing. Remote CIDR 선언만으로 모든 route/firewall rule이 생성되지는 않는다.

Hybrid 사전 조건과 VPC/on-prem 양방향 routing. Remote CIDR 선언만으로 모든 route/firewall rule이 생성되지는 않는다.전체 화면으로 열기 ↗

OS와 Runtime ​

AWS가 검증하는 hybrid 통합버전/범위
Ubuntu20.04·22.04·24.04; 배포판 security maintenance도 별도 검토
RHEL8·9; kernel/CNI·Red Hat 구독/지원 검토
AL2023On-prem 가상화 환경; 일반 bare metal이나 EC2 밖 AWS OS 지원 권리가 아님
BottlerocketVMware 변형 v1.37.0+, Kubernetes 1.28+용, x86_64만 제공; 별도 bootstrap 절차

위 기준이 오래된 Kubernetes 릴리스의 현재 EKS 지원을 뜻하지는 않습니다. 현재 지원되는 EKS·OS/runtime/CNI 조합을 고르세요. AWS는 Ubuntu/RHEL의 hybrid 통합을 지원하며 vendor OS 유지보수 자체를 대신하지 않습니다.

SSM 신규 설치/upgrade에는 **nodeadm 1.0.19+**가 필요합니다. 이전 binary에는 오래된 SSM signing key가 있습니다. 검토한 릴리스는 v1.0.20이며, EC2 EKS AMI의 동명 도구가 아닌 hybrid nodeadm을 사용합니다.

ARM의 EKS kube-proxy 1.31+에는 ARMv8.2+crypto가 필요합니다. Pi 5 이전/Cortex-A72는 충족하지 못하지만 Pi 5 CPU만으로 전체 stack이 검증되지는 않습니다. 이전 kube-proxy 1.30 workaround는 2026년 7월 EKS extended support가 종료됐습니다.

Containerd는 필요하지만 Docker Engine은 필수가 아닙니다. “containerd 1.6+”나 Docker 버전 확인만으로 현재 CRI 호환성이 입증되지는 않습니다.

OS/선택nodeadm containerd source
Ubuntu/AL2023기본 지원 source는 distro
RHELdocker 또는 호환 runtime 사전 설치 후 none; distro는 무효
AL2023docker는 미지원
수동 runtime 설치none은 설치 생략이며 runtime 제공이 아님

Ubuntu 24.04의 과거 Pod 종료/AppArmor 수정에는 containerd 1.7.19+ 또는 적절한 AppArmor 갱신이 필요했습니다. 오래된 버전 설치 권장이 아닙니다. 선택한 CNI의 kernel 요구도 검토하며 “kernel 5.4+”나 무조건적 kernel 교체만으로 해결하지 마세요.

용량과 Host 확인 ​

AWS는 1 vCPU/1 GiB RAM 이상을 권장하면서 엄격한 보편적 최소값은 없다고 설명합니다. OS/runtime/CNI/agent·image/log·실제 workload 용량을 추가해야 합니다. 이전 퀴즈의 2-core/2GB 최소값은 일치하지 않았습니다.

이전 disk 20/50/100GB, CPU 2/4-core, RAM 4/8GB는 계획 예시이며 검증된 workload 크기가 아닙니다.

bash
# Run these read-only checks on the intended hybrid host.
cat /etc/os-release
uname -m
uname -r
free -h
df -h /
swapon --show
ip -j link show
# When already installed, inspect the actual CRI runtime:
containerd --version

Binary 버전이 kubelet의 실제 runtime socket을 입증하지는 않습니다. Swap·cgroup·forwarding·CNI module·firewall은 host owner 절차로 검토합니다. Blanket swapoff/fstab/sysctl/MTU 변경은 모든 지원 host에 이식 가능한 절차가 아닙니다.

Install·검증·init 순서 ​

준비된 Ubuntu/AL2023 host에서는 join 전에 의존성을 설치합니다. RHEL은 앞 표의 runtime source를 사용하며 Bottlerocket은 다른 경로를 사용합니다.

bash
set -euo pipefail
# Installation/bootstrap sequence for prepared Ubuntu/AL2023 hosts.
# RHEL requires --containerd-source docker, or none with a preinstalled compatible runtime.
# These commands mutate the host; review OS/runtime/network/identity first.
sudo nodeadm install 1.36 --credential-provider ssm --containerd-source distro \
  --region ap-northeast-2 --timeout 20m
sudo nodeadm config check -c file:///etc/eks/nodeConfig.yaml
sudo nodeadm init -c file:///etc/eks/nodeConfig.yaml

Node config에 대상 cluster/provider 입력이 있어야 합니다. Credential이 있는 config는 mode 0600으로 보호하고 activation code를 golden image·log에 넣지 마세요. 이 명령은 host를 변경합니다. 실패를 숨기려고 validation phase를 건너뛰지 마세요. nodeadm upgrade는 disruptive 작업이며 workload를 먼저 이동해야 합니다.

Packer Image 준비 ​

AWS 예제는 Ubuntu 22.04/24.04·RHEL 8/9와 vSphere OVA·QEMU qcow2/raw target을 제공합니다. 검토한 v1.0.20 template은 EKS 1.36에서 바로 실행할 image pipeline이 아닙니다.

  • K8S_VERSION validation이 아직 1.26–1.31만 허용합니다. 검토한 current fork를 유지하고 template 때문에 버전을 낮추지 마세요.
  • NODEADM_ARCH는 amd 또는 arm 뒤에 64를 붙입니다. 이전 amd64는 amd6464가 됩니다.
  • Packer의 CREDENTIAL_PROVIDER=iam은 nodeadm의 **iam-ra**로 변환됩니다.
  • 실제 파일은 hybrid-nodes-template.pkr.hcl이며 general-build.qemu.al2023 source는 없습니다.
  • Mutable releases/latest 다운로드, vSphere insecure_connection=true, 기본 build password와 전역 변수 요구를 검토합니다. RHEL provisioner의 x86 repository는 고정돼 있어 binary 아키텍처만 바꿔도 ARM build가 되지는 않습니다.
입력 그룹검토 사항
도구Packer ≥1.11, vSphere plugin ≥1.4 또는 QEMU 1.x; 검증한 조합 고정
공통ISO_URL, 검증한 ISO_CHECKSUM, PKR_SSH_PASSWORD, K8S_VERSION, NODEADM_ARCH, CREDENTIAL_PROVIDER
RHELRH_USERNAME, RH_PASSWORD, RHEL_VERSION; 최종 image/log에서 secret 제외
vSphereServer/user/password, datacenter, cluster, datastore, network, VSPHERE_OUTPUT_FOLDER
QEMUPACKER_OUTPUT_FORMAT (qcow2/raw), CPU/가상화 호환성

소스에는 유료 AWS AMI builder도 있으며 cloud machine을 hybrid node로 운영하는 지원과는 별개입니다. 유지보수한 template과 결과 image를 검증하세요. 이번 감사에서는 Packer/VM build를 실행하지 않았습니다.

GPU 호환성 ​

GPU variant·CPU 아키텍처·OS/kernel·지원 NVIDIA driver·container toolkit/runtime·CUDA/framework image·memory를 함께 확인합니다. 이전 driver 525/535/545/550, CUDA 11.8/12.x는 과거 최소값·권장값이 섞여 있으며 보편적인 현재 Hybrid Nodes 요구가 아닙니다.

H100 80GB·H200 141GB·A100 40/80GB·L40S 48GB는 일부 variant 예시이며 전체 인증 목록이 아닙니다. 모든 model에 4GB 최소값을 적용할 수도 없습니다. 올바른 GPU container 실행만을 위해 host CUDA compiler가 필수는 아니고 driver·compiler 버전의 의미도 다릅니다.

bash
# On a host where the driver is already installed:
nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader
# Optional: nvcc reports the locally installed compiler, if present.
if command -v nvcc >/dev/null 2>&1; then nvcc --version; fi

선택한 조합의 현재 NVIDIA 호환성/platform 안내를 사용하세요. Drain한 test host에서 driver/kernel 변경을 검증합니다. 오래된 driver 설치나 live worker의 containerd 재시작은 무해한 사전 검사가 아닙니다.

Network·CIDR·MTU ​

Control plane의 kubelet·hybrid webhook 접근을 포함해 안정적인 private 양방향 연결이 필요합니다. Public API는 node→API 경로를 바꾸며 필요한 private 역방향 경로를 없애지 않습니다.

AWS의 100 Mbps/RTT ≤200ms는 일반 권장이지 엄격한 보편적 최소값이 아닙니다. 이전 10Gbps/5ms, packet loss 0.1%/0.01%, MTU 1500/9000은 미검증 계획 예시입니다. 실제 수요·encapsulation/path MTU를 시험하며 모든 NIC를 9000으로 바꾸지 마세요.

Remote node/Pod CIDR은 IPv4 RFC1918/CGNAT이며 서로·VPC·service network와 겹치면 안 됩니다. 각 remote 종류의 CIDR 최대 15개를 하나의 remote-node-network, 하나의 remote-pod-network wrapper에 넣습니다. Cluster 간 Pod routing 분리도 필요합니다.

검토한 network-plan.json을 저장합니다.

json
{
  "vpcCidrs": [
    "10.0.0.0/16"
  ],
  "serviceCidrs": [
    "10.100.0.0/16"
  ],
  "remoteNodeCidrs": [
    "10.80.0.0/16"
  ],
  "remotePodCidrs": [
    "10.85.0.0/16"
  ]
}
bash
: "${NETWORK_PLAN_JSON:?Set the reviewed local network plan JSON}"
export NETWORK_PLAN_JSON
python3 - <<'PY'
import ipaddress, itertools, json, os
from pathlib import Path
plan = json.loads(Path(os.environ["NETWORK_PLAN_JSON"]).read_text())
allowed = [ipaddress.ip_network(c) for c in ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10")]
groups = {}
for key in ("vpcCidrs", "serviceCidrs", "remoteNodeCidrs", "remotePodCidrs"):
    values = plan[key]
    if not isinstance(values, list) or not values:
        raise SystemExit(f"{key} must be a nonempty list for this example")
    groups[key] = [ipaddress.ip_network(v, strict=True) for v in values]
    if any(n.version != 4 for n in groups[key]):
        raise SystemExit("This Hybrid Nodes plan requires IPv4")
for key in ("remoteNodeCidrs", "remotePodCidrs"):
    if len(groups[key]) > 15 or any(not any(n.subnet_of(a) for a in allowed) for n in groups[key]):
        raise SystemExit("Remote CIDRs must use RFC1918/CGNAT ranges, at most 15 per kind")
flat = [(key, n) for key, networks in groups.items() for n in networks]
for (ka, a), (kb, other) in itertools.combinations(flat, 2):
    if a.overlaps(other):
        raise SystemExit(f"Overlapping CIDRs: {ka} {a}, {kb} {other}")
print("CIDR syntax/range/non-overlap checks passed; routing and reachability remain unverified")
PY

문법·범위·중첩 검사이지 reachability 검증은 아닙니다. 실제 TGW/VGW의 VPC return route와 on-prem return/per-node Pod route를 구성하세요.

Flow검토 사항
Remote node/Pod → API TCP443대상 CIDR의 추가 cluster SG ingress
Control-plane ENI → node TCP10250제한된 cluster egress·private route·on-prem host/firewall ingress
Control plane → webhook실제 Pod IP/port·route·firewall
Host/Pod → credential/image/DNS/timeProvider endpoint·registry·DNS·시각 동기화

TCP10250은 kubelet에서 API-server SG로 들어오는 ingress가 아닙니다. EKS 단독 API가 모든 remote custom rule을 만들지는 않으며 eksctl은 자신이 소유한 VPC 측 리소스를 자동화할 수 있습니다. 60을 불변 상한으로 보지 말고 실제 quota를 확인하세요.

AWS는 public-only/private-only를 권장합니다. 둘 다 켜면 VPC 밖 node가 public 주소를 해석하므로 경로·접근 규칙에 따라 join이 실패할 수 있습니다. API의 무조건적인 거부는 아닙니다. Private-only에는 private DNS·관리자 접근도 필요합니다.

Provider별 자격 증명 ​

대상 관리자 identity로 AWS 입력을 준비합니다. Activation 정보를 private directory에 저장하세요.

bash
set -euo pipefail
: "${EXPECTED_ACCOUNT_ID:?Set the intended account}"
: "${AWS_REGION:?Set the intended Region}"
: "${CLUSTER_NAME:?Set the intended cluster}"
check_account() {
  local actual
  actual=$(aws sts get-caller-identity --region "$AWS_REGION" --query Account --output text) || return
  test "$actual" = "$EXPECTED_ACCOUNT_ID" || { printf 'Account mismatch.\n' >&2; return 1; }
}
check_account
umask 077
export WORK_DIR
WORK_DIR=$(mktemp -d "$PWD/hybrid-preflight.XXXXXXXX")
export EXPECTED_ACCOUNT_ID AWS_REGION CLUSTER_NAME
printf 'Private preparation directory: %s\n' "$WORK_DIR"

SSM·Roles Anywhere 모두 AWS 연결이 필요합니다. 로컬 CA나 더 긴 cached credential은 offline control plane을 만들지 않습니다.

SSM Role과 Activation ​

Role에는 eks:DescribeCluster, ECR pull, SSM core/cleanup 권한이 필요합니다. AmazonEKSWorkerNodeMinimalPolicy만으로 DescribeCluster를 얻지 못합니다. Pod Identity의 eks-auth:AssumeRoleForPodIdentity는 별도 권한입니다.

예시 계정/리전/cluster 값을 일관되게 바꾸세요. 아래 list operation은 instance별 resource scope를 지원하지 않아 wildcard를 쓰며 대상 Region으로 제한합니다.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ssm.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "123456789012"
        },
        "ArnLike": {
          "aws:SourceArn": "arn:aws:ssm:ap-northeast-2:123456789012:*"
        }
      }
    }
  ]
}
json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "eks:DescribeCluster",
      "Resource": "arn:aws:eks:ap-northeast-2:123456789012:cluster/my-hybrid-cluster"
    },
    {
      "Effect": "Allow",
      "Action": "ssm:DescribeInstanceInformation",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": "ap-northeast-2"
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": "ssm:DeregisterManagedInstance",
      "Resource": "arn:aws:ssm:ap-northeast-2:123456789012:managed-instance/*",
      "Condition": {
        "StringEquals": {
          "ssm:resourceTag/EKSClusterARN": "arn:aws:eks:ap-northeast-2:123456789012:cluster/my-hybrid-cluster"
        }
      }
    }
  ]
}

검토한 AmazonEC2ContainerRegistryPullOnly·AmazonSSMManagedInstanceCore 또는 동등 권한을 연결합니다. Activation의 EKSClusterARN tag는 deregistration policy와 일치해야 합니다.

의도한 등록 한도·24시간 등록 만료를 준비합니다.

bash
: "${HYBRID_ROLE_NAME:?Use the reviewed SSM-trusting role name}"
: "${REGISTRATION_LIMIT:?Set a deliberate node registration limit}"
export HYBRID_ROLE_NAME REGISTRATION_LIMIT
python3 - <<'PY'
import json, os, re
from datetime import datetime, timedelta, timezone
from pathlib import Path
account, region, cluster = (os.environ[k] for k in ("EXPECTED_ACCOUNT_ID", "AWS_REGION", "CLUSTER_NAME"))
if not re.fullmatch(r"\d{12}", account) or not re.fullmatch(r"[a-z]{2}(?:-[a-z]+)+-\d", region):
    raise SystemExit("Invalid account/Region")
if region.startswith(("cn-", "us-gov-")):
    raise SystemExit("Hybrid Nodes is not available in this Region family")
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,99}", cluster):
    raise SystemExit("Invalid cluster name")
role = os.environ["HYBRID_ROLE_NAME"]
if not re.fullmatch(r"[\w+=,.@-]{1,64}", role, re.ASCII):
    raise SystemExit("Use the actual IAM role name, not a role ARN")
limit = int(os.environ["REGISTRATION_LIMIT"])
if not 1 <= limit <= 1000:
    raise SystemExit("RegistrationLimit must be 1..1000 per activation; review service quotas separately")
body = {"DefaultInstanceName": "eks-hybrid-node", "IamRole": role, "RegistrationLimit": limit,
        "Description": "Reviewed EKS hybrid activation",
        "Tags": [{"Key": "EKSClusterARN", "Value": f"arn:aws:eks:{region}:{account}:cluster/{cluster}"}],
        "ExpirationDate": (datetime.now(timezone.utc) + timedelta(hours=24)).isoformat()}
(Path(os.environ["WORK_DIR"]) / "activation-request.json").write_text(json.dumps(body, indent=2) + "\n")
PY
bash
set -euo pipefail
# Run only after the role/trust/tag policy and registration scope are prepared.
check_account
aws ssm create-activation --region "$AWS_REGION" \
  --cli-input-json "file://$WORK_DIR/activation-request.json" --output json \
  > "$WORK_DIR/activation-response.json"
chmod 600 "$WORK_DIR/activation-response.json"
# The response contains the secret ActivationCode. Do not print or commit it.

ActivationCode는 한 번 반환됩니다. ActivationId와 함께 안전하게 전달하세요. Activation 만료/삭제는 기존 managed instance의 deregistration이 아닙니다. CreateActivation RegistrationLimit은 activation별 1–1,000 범위를 유지합니다. 전체 fleet의 무료 구간 한도가 아니며, 현재 요금 조건은 자격 증명 provider 비교에서 설명합니다.

SSM node name은 mi-..., credential 수명은 고정 1시간입니다. Network 복구 후에도 refresh backoff로 재연결이 지연될 수 있습니다. Credential을 log에 출력하지 마세요.

Roles Anywhere Trust와 수명 ​

Provider에 맞는 role·trust anchor·profile을 사용합니다. Session name을 인증서 identity/nodeName과 연결하며 이전 PrincipalTag/RequestTag 비교만으로는 강제되지 않았습니다.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "rolesanywhere.amazonaws.com"
      },
      "Action": [
        "sts:TagSession",
        "sts:SetSourceIdentity"
      ],
      "Condition": {
        "ArnEquals": {
          "aws:SourceArn": "arn:aws:rolesanywhere:ap-northeast-2:123456789012:trust-anchor/11111111-2222-3333-4444-555555555555"
        }
      }
    },
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "rolesanywhere.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "ArnEquals": {
          "aws:SourceArn": "arn:aws:rolesanywhere:ap-northeast-2:123456789012:trust-anchor/11111111-2222-3333-4444-555555555555"
        },
        "StringEquals": {
          "sts:RoleSessionName": "${aws:PrincipalTag/x509Subject/CN}"
        }
      }
    }
  ]
}
json
{
  "name": "hybrid-node-profile",
  "roleArns": [
    "arn:aws:iam::123456789012:role/EKSHybridNodeRole"
  ],
  "enabled": true,
  "acceptRoleSessionName": true,
  "durationSeconds": 3600
}

Anchor ARN을 바꾸고 scoped DescribeCluster/ECR 권한과 acceptRoleSessionName=true를 구성합니다.

text
effectiveDuration = min(profileDuration, requestedDuration)
                    or profileDuration when the request omits duration
effectiveDuration <= role.MaxSessionDuration

Request/profile duration은 900–43,200초입니다. CreateSession은 같은 값도 허용합니다. Profile 3,600·override 없음·role maximum 3,600은 유효합니다. 이전 “더 커야 함”이라는 엄격한 표현은 부정확했습니다. 인증서 유효성/revocation·AWS 연결은 여전히 필요합니다.

Node별 Key와 인증서 ​

대상 새 node의 private directory에서 고유 key/CSR을 만듭니다. 예제는 단순 lowercase DNS label을 사용합니다. CSR을 승인된 issuer에 전달하고 CA signing key는 node에 두지 마세요.

bash
set -euo pipefail
umask 077
: "${WORK_DIR:?Set a private preparation directory on this node}"
: "${NODE_NAME:?Use a unique certificate CN / node name}"
export NODE_NAME
python3 - <<'PY'
import os, re
name = os.environ["NODE_NAME"]
if not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,61}[a-z0-9]", name):
    raise SystemExit("This example requires a lowercase DNS label of 2..63 characters")
PY
test ! -e "$WORK_DIR/node.key"
test ! -e "$WORK_DIR/node.csr"
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out "$WORK_DIR/node.key"
chmod 600 "$WORK_DIR/node.key"
openssl req -new -key "$WORK_DIR/node.key" -out "$WORK_DIR/node.csr" -subj "/CN=$NODE_NAME"
# Send the CSR to the approved PKI issuer; do not copy its CA private key to nodes.

발급 chain·승인된 CA·유효성/revocation·key 일치·CN을 확인하고 시각을 동기화합니다. 새 node에만 설치하세요.

bash
set -euo pipefail
: "${ISSUED_NODE_CERT:?Set the verified certificate/chain issued for this node}"
: "${WORK_DIR:?Set the private directory containing the node key}"
# New-node installation only; do not overwrite an existing identity.
sudo test ! -e /etc/iam/pki/server.key
sudo test ! -e /etc/iam/pki/server.pem
sudo install -d -m 0700 /etc/iam/pki
sudo install -m 0644 "$ISSUED_NODE_CERT" /etc/iam/pki/server.pem
sudo install -m 0600 "$WORK_DIR/node.key" /etc/iam/pki/server.key

EKS Kubernetes API CA 파일과 다릅니다. Node identity를 image에 복제하거나 기존 key를 조용히 덮어쓰면 안 됩니다.

CloudFormation 준비 ​

고정된 v1.0.20 template은 provisioning의 출발점입니다. SSM template은 role을 만들며 activation은 만들지 않습니다. 여러 cluster에 쓰기 전 넓은 DescribeCluster scope와 고정 export 이름을 검토하세요.

IRA의 CertAttributeTrustPolicy 값은 "CN"이 아닌 literal **${aws:PrincipalTag/x509Subject/CN}**입니다. Python cryptography helper는 PEM을 정확히 serialize하고 기본 CA 형태·현재 유효성을 확인하며 issuer 권한·revocation 검증은 아닙니다.

bash
: "${CA_PEM_FILE:?Set the approved CA certificate file, not a private key}"
: "${ROLE_NAME:?Set the new role name selected by the IaC owner}"
export CA_PEM_FILE ROLE_NAME
python3 - <<'PY'
import json, os, re
from pathlib import Path
from cryptography import x509
from datetime import datetime, timezone
raw = Path(os.environ["CA_PEM_FILE"]).read_bytes()
if b"PRIVATE KEY" in raw or raw.count(b"-----BEGIN CERTIFICATE-----") != 1:
    raise SystemExit("Provide one reviewed CA certificate")
cert = x509.load_pem_x509_certificate(raw)
if not cert.extensions.get_extension_for_class(x509.BasicConstraints).value.ca:
    raise SystemExit("Certificate is not a CA")
now = datetime.now(timezone.utc)
if not cert.not_valid_before.replace(tzinfo=timezone.utc) <= now <= cert.not_valid_after.replace(tzinfo=timezone.utc):
    raise SystemExit("CA certificate is not currently valid")
role = os.environ["ROLE_NAME"]
if not re.fullmatch(r"[\w+=,.@-]{1,64}", role, re.ASCII):
    raise SystemExit("Invalid IAM role name")
body = [
    {"ParameterKey": "RoleName", "ParameterValue": role},
    {"ParameterKey": "CertAttributeTrustPolicy", "ParameterValue": "${aws:PrincipalTag/x509Subject/CN}"},
    {"ParameterKey": "CABundleCert", "ParameterValue": raw.decode("ascii")}
]
(Path(os.environ["WORK_DIR"]) / "cfn-iamra-parameters.json").write_text(json.dumps(body, indent=2) + "\n")
PY

고정 template의 parameter key/AllowedValues를 대조하고 IaC owner가 policy scope·export 이름을 정한 뒤 change set을 검토합니다. 잘못된 PEM shorthand나 미검토 mutable-main template을 배포하지 마세요.

Cluster Access와 생성 계획 ​

Node IAM role에는 HYBRID_LINUX access entry를 권장합니다.

json
{
  "clusterName": "my-hybrid-cluster",
  "principalArn": "arn:aws:iam::123456789012:role/EKSHybridNodeRole",
  "type": "HYBRID_LINUX"
}

Mapping은 system:node:{{SessionName}}과 node-bootstrap group을 사용합니다. API/API_AND_CONFIG_MAP 인증·소유권을 확인합니다. 기존 mapping을 지우는 한-role aws-auth ConfigMap을 적용하지 마세요. Legacy mapping 변경은 통제된 이전이 필요합니다.

아래 모든 ID를 검토한 실제 리소스로 바꿉니다. Private-only endpoint·IPv4·1.36이 명시돼 있습니다. Bootstrap creator admin 권한은 통제된 lab용이며 production operator 접근은 의도적으로 선택하세요.

eksctl 0.229.0에서는 문서화된 SSM/IRA를 사용합니다. RoleARN을 제공한다면 provider 리소스도 별도로 준비합니다.

yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: my-hybrid-cluster
  region: ap-northeast-2
  version: '1.36'
iam:
  serviceRoleARN: arn:aws:iam::123456789012:role/EKSClusterRole
accessConfig:
  authenticationMode: API_AND_CONFIG_MAP
  bootstrapClusterCreatorAdminPermissions: true
kubernetesNetworkConfig:
  ipFamily: IPv4
  serviceIPv4CIDR: 10.100.0.0/16
vpc:
  id: vpc-0123456789abcdef0
  subnets:
    private:
      ap-northeast-2a:
        id: subnet-0123456789abcdef0
      ap-northeast-2c:
        id: subnet-0123456789abcdef1
  controlPlaneSecurityGroupIDs:
  - sg-0123456789abcdef0
  clusterEndpoints:
    privateAccess: true
    publicAccess: false
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

eksctl provisioning 시 --without-nodegroup과 명시적인 private kubeconfig 경로를 사용하세요. Schema 유효성은 IAM·route·gateway reachability 증거가 아닙니다. 예전 eksctl의 launch-only 문서는 오래됐으며 현재 EKS API는 기존 cluster의 hybrid remote-network 구성도 지원합니다.

대응하는 EKS CreateCluster 요청입니다.

json
{
  "name": "my-hybrid-cluster",
  "version": "1.36",
  "roleArn": "arn:aws:iam::123456789012:role/EKSClusterRole",
  "resourcesVpcConfig": {
    "subnetIds": [
      "subnet-0123456789abcdef0",
      "subnet-0123456789abcdef1"
    ],
    "securityGroupIds": [
      "sg-0123456789abcdef0"
    ],
    "endpointPrivateAccess": true,
    "endpointPublicAccess": false
  },
  "kubernetesNetworkConfig": {
    "ipFamily": "ipv4",
    "serviceIpv4Cidr": "10.100.0.0/16"
  },
  "accessConfig": {
    "authenticationMode": "API_AND_CONFIG_MAP",
    "bootstrapClusterCreatorAdminPermissions": true
  },
  "remoteNetworkConfig": {
    "remoteNodeNetworks": [
      {
        "cidrs": [
          "10.80.0.0/16"
        ]
      }
    ],
    "remotePodNetworks": [
      {
        "cidrs": [
          "10.85.0.0/16"
        ]
      }
    ]
  }
}

한 infrastructure owner를 선택하며 두 경로를 모두 실행하지 않습니다. 실제 cluster/endpoint identity·operator 권한·node access 확인 후 join합니다. 예시 AWS ID는 검증된 리소스가 아닙니다.

Add-on ​

아래 공식 hybrid 호환 기준은 모든 Kubernetes 버전의 설치 target이 아닙니다.

Add-onHybrid 호환 기준
kube-proxy/CoreDNS1.25.14-eksbuild.2 / 1.9.3-eksbuild.7
ADOT/CloudWatch Observability0.102.1-eksbuild.2 / 2.2.1-eksbuild.1
Pod Identity Agent일반 1.3.3-eksbuild.1, Bottlerocket 1.3.7-eksbuild.2; Bottlerocket OS 1.39.0 이상도 필요
Node monitoring/snapshot controller1.2.0-eksbuild.1 / 8.1.0-eksbuild.2
Private CA Connector/FSx CSI/Secrets Store provider1.6.0-eksbuild.1 / 1.7.0-eksbuild.1 / 2.1.1-eksbuild.1
Metrics Server/cert-manager0.7.2-eksbuild.1 / 1.17.2-eksbuild.1
Node Exporter/kube-state-metrics/External DNS1.9.1-eksbuild.2 / 2.15.0-eksbuild.4 / 0.19.0-eksbuild.1
bash
check_account
: "${ADDON_NAME:?Select one add-on to review}"
aws eks describe-addon-versions --region "$AWS_REGION" --addon-name "$ADDON_NAME" \
  --kubernetes-version 1.36 --output json > "$WORK_DIR/addon-catalog.json"
jq '[.addons[].addonVersions[] |
     {addonVersion,architecture,computeTypes,compatibilities}]' "$WORK_DIR/addon-catalog.json"

선택한 버전의 hybrid/OS/kernel 설정을 검토합니다. VPC CNI는 hybrid node를 관리하지 않으므로 호환 CNI·cloud-only agent placement를 구성합니다. Catalog 존재는 설치·Ready 증거가 아닙니다.

참고 자료 ​

< 목차 | 다음: 네트워크 구성 >