ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

使用 AWS CDK 构建隔离子网中的 EKS 私有集群(Java 示例)完整实战指南

使用 AWS CDK 构建隔离子网中的 EKS 私有集群(Java 示例)完整实战指南

【免费下载链接】aws-cdk-examples

Example projects using the AWS CDK

项目地址:https://gitcode.com/gh_mirrors/aw/aws-cdk-examples
点击查看免费下载

本指南基于 aws-cdk-examples 仓库中的 java/eks/private-cluster 示例,深入讲解如何用 AWS CDK(Java 语言)在一座完全没有公网出口的 VPC 中搭建 EKS 私有集群:VPC 只包含隔离子网(Isolated Subnets),不创建 Internet Gateway、NAT Gateway 或任何互联网代理;集群通过一组 VPC Endpoints 私有访问 AWS 服务;管理员借助 AWS Systems Manager Session Manager 免公网登录 EC2 实例,并通过kubectl管理集群。读完本文,你将掌握私有集群的整体架构设计、全部 VPC Endpoint 清单与作用、完整的 Java 源码实现要点、部署与验证步骤,以及无互联网环境下安装 Docker、kubectl 等工具的可行方法。

适用场景

私有 EKS 集群(Private EKS Cluster)通常用于企业级部署或高合规监管环境,例如金融、医疗、政府等对数据出境、公网暴露有严格要求的场景。这类环境的核心诉求是:部署的工作负载不允许任何互联网连通性——Pod 不能访问公网,公网也无法访问集群。示例中所有 EC2 实例都不具备公网 IP,唯一的运维入口是通过 AWS Systems Manager(SSM)的私有通道进行会话管理。

整体架构与设计要点

从 EksPrivateClusterStack.java 的构造逻辑看,整个 Stack 由四个部分组成:

  1. VPC:仅含隔离子网,无公网出口;
  2. VPC Endpoints:一组接口端点(Interface Endpoint)+ 一个网关端点(Gateway Endpoint),保证集群在无公网环境下仍能调用 AWS API;
  3. EKS 集群:私有访问模式的托管集群,外加一个自动扩缩组(Auto Scaling Group)承载工作节点;
  4. kubectl 客户端实例:一个位于隔离子网内的 BastionHostLinux 实例,通过 SSM 访问,用于执行kubectl命令。

VPC:只创建隔离子网

createVpc() 方法给出了 VPC 的关键配置:

this.vpc = Vpc.Builder.create(this, "vpc") .ipAddresses(IpAddresses.cidr("10.0.0.0/16")) .maxAzs(2) .natGateways(0) // 不创建 NAT 网关 .subnetConfiguration( List.of( SubnetConfiguration.builder() .cidrMask(24) .name("isolated") .subnetType(SubnetType.PRIVATE_ISOLATED) // 仅隔离子网 .build())) .build();

要点说明:

  • CIDR:使用10.0.0.0/16作为 VPC 网段,cidrMask(24)将每个子网切分为/24;
  • maxAzs(2):在两个可用区(AZ)中创建子网,满足高可用要求,同时控制端点和集群成本;
  • natGateways(0):显式关闭 NAT 网关,配合不创建 Internet Gateway,使 VPC 完全与公网隔绝;
  • 子网类型:仅配置PRIVATE_ISOLATED一种子网类型。隔离子网既没有到 Internet Gateway 的路由,也没有 NAT 出口路由,实例因此完全无法访问公网。

测试类 EksPrivateClusterStackTest.java 中专门用两个断言来验证这一点:

@Test public void testNoInternetGateway() { final Template template = synthesizeTemplate(); template.resourceCountIs("AWS::EC2::InternetGateway", 0); } @Test public void testNoNatGateway() { final Template template = synthesizeTemplate(); template.resourceCountIs("AWS::EC2::NatGateway", 0); }

resourceCountIs(..., 0)断言合成出的 CloudFormation 模板中InternetGateway 与 NatGateway 的资源数量均为 0,从测试层面锁定了"无公网出口"这一核心设计。

EKS 集群:私有端点访问模式

createEksCluster() 方法配置了集群本体:

this.cluster = Cluster.Builder.create(this, "eks") .vpc(vpc) .version(KubernetesVersion.V1_34) // Kubernetes 1.34 .vpcSubnets( List.of(SubnetSelection.builder().subnetType(SubnetType.PRIVATE_ISOLATED).build())) .endpointAccess(EndpointAccess.PRIVATE) // 私有端点访问 .clusterName("eks-private") .kubectlLayer(new KubectlV34Layer(this, "KubectlLayer")) .defaultCapacity(0) // 默认节点池容量为 0 .mastersRole(clusterAdmin) // 集群管理员 IAM 角色 .placeClusterHandlerInVpc(true) // 集群自定义资源 Lambda 放入 VPC .clusterHandlerEnvironment(Map.of("AWS_STS_REGIONAL_ENDPOINTS", "regional")) .kubectlEnvironment(Map.of("AWS_STS_REGIONAL_ENDPOINTS", "regional")) .outputClusterName(true) .outputConfigCommand(true) .outputMastersRoleArn(true) .build();

关键配置的含义:

  • endpointAccess(EndpointAccess.PRIVATE):集群 API Server 只暴露私有端点(private endpoint),不开放公网端点。测试用例testEksClusterEndpointAccess断言了对应的resourcesVpcConfig属性为endpointPublicAccess: false、endpointPrivateAccess: true;
  • version(KubernetesVersion.V1_34):使用 Kubernetes 1.34,并配套cdk-lambda-layer-kubectl-v34依赖(见 pom.xml 中的kubectl.version属性)与KubectlV34Layer提供 kubectl Lambda Layer;
  • defaultCapacity(0):不启用 CDK 默认的托管节点组,而是由后续addNodes()手动添加基于 Auto Scaling Group 的工作节点;
  • mastersRole(clusterAdmin):将根账户角色的管理员角色加入aws-authConfigMap 的system:masters,该角色在 Stack 中以AccountRootPrincipal被创建;
  • placeClusterHandlerInVpc(true):这是私有集群能否成功部署的关键。CDK 在创建 EKS 集群时依赖一组自定义资源(Custom Resource)Lambda 函数来监控集群生命周期(如等待集群可用、配置 kubectl 等)。默认情况下这些 Lambda 部署在 AWS 托管的公共 VPC 中,无法访问隔离子网内的私有集群;设置为true后,集群处理器 Lambda 会被部署到本 VPC 的隔离子网中,配合 VPC Endpoints 才能调用 EKS API;
  • clusterHandlerEnvironment/kubectlEnvironment中的AWS_STS_REGIONAL_ENDPOINTS=regional:让 Lambda 内的 STS 调用使用区域化端点,因为隔离环境中没有到全局端点所在区域的网络路径;
  • 输出开关:outputClusterName、outputConfigCommand、outputMastersRoleArn为true,使cdk deploy完成后直接打印集群名、kubeconfig命令和管理员角色 ARN,便于后续操作。

工作节点:Auto Scaling Group

addNodes() 通过cluster.addAutoScalingGroupCapacity添加工作节点:

AutoScalingGroup asg = this.cluster.addAutoScalingGroupCapacity( "nodes", AutoScalingGroupCapacityOptions.builder() .instanceType(InstanceType.of(InstanceClass.T3, InstanceSize.MEDIUM)) // t3.medium .minCapacity(1) // 至少 1 个节点 .vpcSubnets( SubnetSelection.builder().subnetType(SubnetType.PRIVATE_ISOLATED).build()) .ssmSessionPermissions(true) // 允许 SSM Session Manager 会话 .machineImageType(null) .build()); asg.addToRolePolicy( PolicyStatement.Builder.create() .effect(Effect.ALLOW) .actions( List.of( "ecr:BatchCheckLayerAvailability", "ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer", "ecr:GetAuthorizationToken")) .resources(List.of("*")) .build());

要点:

  • 工作节点使用t3.medium类型、最小容量 1,部署在隔离子网内;
  • ssmSessionPermissions(true):为节点实例角色附加 SSM 相关权限,使工作节点同样支持 Session Manager 接入(排障时很有用);
  • 节点角色被显式授予 ECR 拉取镜像所需的最小权限集(ecr:BatchCheckLayerAvailability、ecr:BatchGetImage、ecr:GetDownloadUrlForLayer、ecr:GetAuthorizationToken),配合 ECR 接口端点即可从私有仓库拉取容器镜像。

kubectl 客户端实例:BastionHostLinux

createBastion() 创建了名为kubectl-client的堡垒机实例:

BastionHostLinux client = BastionHostLinux.Builder.create(this, "kubectl-client") .vpc(vpc) .instanceName("kubectl-client") .blockDevices( List.of( BlockDevice.builder() .deviceName("/dev/sdf") .volume( BlockDeviceVolume.ebs( 10, EbsDeviceOptions.builder().encrypted(true).build())) .build())) .subnetSelection( SubnetSelection.builder().subnetType(SubnetType.PRIVATE_ISOLATED).build()) .securityGroup(this.cluster.getClusterSecurityGroup()) // 复用集群安全组 .instanceType(InstanceType.of(InstanceClass.T3, InstanceSize.SMALL)) // t3.small .build();

后续的权限配置体现了私有集群的运维设计:

  • clusterAdmin.grantAssumeClientRole(client.getRole())(源码中为grantAssumeRole):允许客户端实例角色Assume 集群管理员角色,从而获得system:masters级别的管理权限;
  • 客户端实例角色被授予eks:DescribeCluster与eks:ListClusters,这是执行aws eks update-kubeconfig所需的权限;
  • 附加 AWS 托管策略AmazonEC2ContainerRegistryPullOnly(用于拉取镜像)与AmazonS3ReadOnlyAccess(用于从 S3 资产桶读取 kubectl 等工具二进制);
  • 10 GB 的根 EBS 卷设置为加密(encrypted(true)),符合高合规环境的安全基线。

VPC Endpoints 清单与作用

addEndpoints() 创建了全部端点。在控制台中,这些端点会出现在每一个隔离子网中,服务名以com.amazonaws.[region].为前缀,例如com.amazonaws.ap-southeast-1.s3。

网关端点(Gateway Endpoint)

服务端点名称用途
S3s3从 S3 桶下载资产、构件和工具(例如 kubectl 二进制、Amazon Linux 仓库中的软件包)

S3 使用网关端点而非接口端点:网关端点通过路由表实现、不产生数据处理费,且 Amazon Linux 的 yum 仓库托管在 S3 桶中,因此在隔离环境中仍可正常安装系统软件包。

接口端点(Interface Endpoint)

服务端点名称用途
ECRecr.api、ecr.dkr拉取私有仓库中的容器镜像,也可通过 ECR pull through cache 获取上游公共仓库镜像
EC2ec2创建工作节点与客户端实例时调用 EC2 API
EKSeks调用 EKS API 完成集群创建、查询等操作
Security Token Servicests获取临时安全凭证,配合 AssumeRole 流程
CloudWatchlogs、monitoring日志与监控数据的采集上报
Systems Managerec2messages、ssm、ssmmessages支撑 SSM Session Manager 会话(另见 AWS 文档中 Systems Manager 所需的端点清单)
Lambdalambda集群自定义资源处理器的依赖服务
Step Functionsstates、sync-statesCDK 的集群处理器(Custom Resource)在创建 EKS 集群时依赖的状态机服务

接口端点的实现与 README 中的列表一一对应,在源码中体现为addEndpoints()内的 13 个InterfaceVpcEndpointAwsService常量(ECR、ECR_DOCKER、CLOUDWATCH_MONITORING、CLOUDWATCH_LOGS、STS、SSM、SSM_MESSAGES、LAMBDA、EKS、EC2、EC2_MESSAGES、STEP_FUNCTIONS、STEP_FUNCTIONS_SYNC)。

端点的作用说明

  • S3 端点:用于下载存储在 S3 桶中的资产、构件和工具;
  • ECR 端点:用于拉取私有仓库的容器镜像,也可以通过 ECR pull through cache 拉取上游注册表中的公共镜像;
  • EC2 端点:创建工作节点和客户端实例时调用 EC2 服务;
  • EKS 端点:调用 EKS API 完成集群创建;
  • CloudWatch 端点:提供日志与监控能力;
  • Lambda 与 Step Functions 端点:CDK 的集群处理器(Custom Resource)在创建 EKS 集群时必须依赖这两个服务。

后续扩展与计费注意

  • 部署完成后,随着你在集群中添加需要调用其他 AWS 服务的应用,需要为这些服务逐一添加对应的 VPC Endpoint(完整的 Privatelink 支持清单见 AWS 官方文档);
  • 注意:VPC Endpoint 在每个可用区中保持开通状态时按小时计费,同时还会产生数据处理费,请参考 AWS PrivateLink 定价页面评估成本。

构建与部署

示例的 Java 入口类是 PrivateClusterCdkApp.java,它创建 ID 为EksPrivateCluster的 Stack 并调用app.synth()。构建依赖通过 pom.xml 管理,其中aws-cdk-lib版本区间为[2.262.0,3.0.0),编译目标为 Java 17;cdk.json 中配置的应用启动命令为mvn -e -q compile exec:java。

部署步骤如下:

  1. 进入示例根目录:

    cd java/eks/private-cluster
  2. 合成(synth)CloudFormation 模板:

    cdk synth
  3. 部署:

    cdk deploy

    整个部署大约需要25 分钟,其中包括 EKS 集群、VPC Endpoints 以及 CDK 用于监控集群生命周期所需的 Lambda 函数的创建。这些函数被部署在 VPC 内部,这正是集群配置中placeClusterHandlerInVpc设为true的效果(详见 EKS Construct Library)。

  4. 部署成功后,留意 CDK 输出的集群名称、kubeconfig 设置、kubectl 客户端实例 ID 以及集群 master IAM 角色,后续操作都会用到。

    示例输出:

    Outputs: EksPrivateCluster.eksClusterNameXXXXXXXX = eks-private EksPrivateCluster.eksConfigCommandXXXXXXXX = aws eks update-kubeconfig --name eks-private --region ap-southeast-1 --role-arn arn:aws:iam::111111111111:role/EksPrivateCluster-clusteradminxxxxxxxx-xxxxxxxxxxxx EksPrivateCluster.eksGetTokenCommandXXXXXXXX = aws eks get-token --cluster-name eks-private --region ap-southeast-1 --role-arn arn:aws:iam::111111111111:role/EksPrivateCluster-clusteradminxxxxxxxx-xxxxxxxxxxxx EksPrivateCluster.eksMastersRoleArnXXXXXXXX = arn:aws:iam::111111111111:role/EksPrivateCluster-clusteradminxxxxxxxx-xxxxxxxxxxxx EksPrivateCluster.kubectlclientBastionHostXXXXXXXX = i-000000000aaaaaaaa

    (输出中的XXXXXXXX为 CDK 自动生成的后缀,111111111111为示例账号 ID,实际部署时以真实输出为准。)

除了运行部署,你还可以用 Maven 运行单元测试来验证模板的合规性:

mvn test

EksPrivateClusterStackTest.java 覆盖了四项关键断言:集群名为eks-private且版本为1.34、私有端点访问开启且公网端点关闭、不含 Internet Gateway、不含 NAT Gateway。

通过 SSM Session Manager 访问 EC2 实例

  1. 登录 AWS 控制台,进入EC2 服务,找到 CDK 创建的名为kubectl-client的实例,点击Connect,选择Session Manager; 2.(可选)在 Session Manager Preferences 中配置默认 shell 为 bash,并设置 kubectl 别名与环境变量:

    /bin/bash alias k=kubectl export AWS_STS_REGIONAL_ENDPOINTS=regional cd $HOME

    其中AWS_STS_REGIONAL_ENDPOINTS=regional与集群配置中写入处理器环境的值保持一致,确保 STS 走区域化端点,在纯隔离网络下尤为重要。

在隔离环境中安装工具与软件包

Docker

Amazon Linux 的软件仓库托管在 Amazon S3 桶中,因此即使在隔离环境中,从这些仓库安装或更新软件包也能正常工作(本次部署的实例正是 Amazon Linux,且 VPC 中配置了 S3 网关端点)。以安装 Docker 为例:

sudo yum update -y sudo amazon-linux-extras install docker sudo service docker start

kubectl

对于kubectl这类不在系统仓库中的工具,需要先准备一个可从隔离环境访问的 S3 桶来存放二进制资产。你可以使用 AWS CloudShell 下载工具并上传到该资产桶,然后在kubectl-client实例中安装。CloudShell 侧的操作示例:

[cloudshell-user@ip-10-2-84-204 ~]$ curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.34.2/2025-11-13/bin/linux/amd64/kubectl [cloudshell-user@ip-10-2-84-204 ~]$ aws s3 cp kubectl s3://my-bucket/kubectl-1.34.2 upload: ./kubectl to s3://my-bucket/kubectl-1.34.2

之后在kubectl-client实例中通过aws s3 cp(依赖已附加的AmazonS3ReadOnlyAccess托管策略)取回该二进制,并按 EKS 官方指南完成kubectl安装。实例角色已经具备 S3 只读权限,因此无需额外配置。

使用 kubectl 访问 EKS 集群

SSM 登录kubectl-client实例后,使用部署时记录的输出信息更新 kubeconfig:

[ssm-user@ip-10-0-0-240 bin]$ aws eks update-kubeconfig --name eks-private --region ap-southeast-1 --role-arn arn:aws:iam::111111111111:role/EksPrivateCluster-clusteradminxxxxxxxx-xxxxxxxxxxxx Added new context arn:aws:eks:ap-southeast-1:111111111111:cluster/eks-private to /home/ssm-user/.kube/config

验证集群访问,查看节点与全部命名空间的 Pod:

[ssm-user@ip-10-0-0-240 ~]$ kubectl get nodes NAME STATUS ROLES AGE VERSION ip-10-0-0-60.ap-southeast-1.compute.internal Ready <none> 19h v1.34.2-eks-ecaa3a6 [ssm-user@ip-10-0-0-240 ~]$ kubectl get pods -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system aws-node-9sqcb 2/2 Running 2 (4h39m ago) 19h kube-system coredns-6787556b84-cg29g 1/1 Running 1 (4h39m ago) 19h kube-system coredns-6787556b84-q98ks 1/1 Running 1 (4h39m ago) 19h kube-system kube-proxy-m9ms4 1/1 Running 1 (4h39m ago) 19h

从输出可以看到:工作节点版本为v1.34.2-eks-ecaa3a6,aws-node、coredns、kube-proxy等系统组件均正常Running,说明私有集群的控制面、节点注册与容器网络在无公网环境下运转正常。

清理资源

不再需要时,在示例根目录执行:

cdk destroy

该命令会删除集群、VPC、VPC Endpoints、Lambda 函数及相关资源。需要留意的是,如果集群中创建了独立的负载均衡器或持久化存储卷(EBS/EFS),可能需要按依赖顺序额外处理。

实践提示小结

  • 务必开启placeClusterHandlerInVpc:否则 CDK 的集群处理器 Lambda 无法访问隔离子网内的私有集群,部署会失败;
  • 端点是按需扩展的:新增应用若依赖其他 AWS 服务(如 DynamoDB、Secrets Manager),需要补充对应的 VPC Endpoint,同时可在addEndpoints()的端点列表中直接追加;
  • IAM 权限建议收敛:源码注释明确指出,生产环境应限制对特定仓库与桶的访问(如将AmazonS3ReadOnlyAccess、ECR 权限收窄到具体资源),示例中使用托管策略是为了演示的便捷性;
  • 计费不可忽视:VPC Endpoint 按小时计费并叠加数据处理费,多可用区部署会放大成本,请结合架构高可用要求与预算权衡;
  • 模板合规性可自动化验证:通过 EksPrivateClusterStackTest.java 的断言(无 IGW、无 NAT、私有端点、集群版本)即可在 CI 中守住"纯隔离"这一架构底线。

【免费下载链接】aws-cdk-examples

Example projects using the AWS CDK

项目地址:https://gitcode.com/gh_mirrors/aw/aws-cdk-examples
点击查看免费下载
上一篇:Aptos MoveFlow 规范推断评估:state-label 修复后的语料库筛选(Screening)全流程解读
下一篇:深入解析 Makepad 内嵌 GIF 库 makepad-gif:编解码高层接口与源码级实战

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进