昨天 (Day 21) 的 CI/CD script 中有一個步驟是把 image 推送到 ECR,針對這個步驟我們可以來聊一下 ECR 授權的機制。
目前常見有兩種主要的方案:
當前,我們採用的是第一種方式 —— User Access Key。這是因為我們的 GitLab Runner 是公司集團共用的資源,我們並不擁有其執行環境的完全控制權,因此只好將 AWS 憑證以環境變數的方式存放在 GitLab CI/CD 變數中。
但這樣長期來說存在著明顯的風險:
因此,我們最終決定 部署自己的 GitLab Runner,並且透過 Transit Gateway 打通網路,讓 Runner 可以直接透過 IAM Role 與 AWS 資源互動,徹底擺脫長期金鑰的依賴。
我們的需求其實很單純:
之所以要使用 Transit Gateway (TGW),並不是因為我們有太多 VPC,而是因為 GitLab VPC 是由另一個單位管理,而該單位必須跟許多外部單位串接,他們統一要求透過 TGW 來進行連線。換句話說,TGW 在這裡就是一個「必須遵守的規範」,我們要配合使用。
理解 TGW 的幾個核心元件,有助於看懂架構:
以下示意圖展示我們的情境:EKS VPC 與 GitLab VPC 之間透過 TGW 打通,其中 EKS VPC 內部就跑著我們的 Runner。
要注意上圖強調的是 TGW 端的設定,因此除了 TGW Route Table,VPC 自己的 Route Table 和 Security Group 也需要更新,流量才會順利通過。
建立獨立的 Transit Gateway
resource "aws_ec2_transit_gateway" "this" {
amazon_side_asn = XXXXX
tags = {
"Name" = "example-internal-tgw"
}
}
與集團的 Transit Gateway 進行 Peering。這段的 attachment 因為是由對方發起的請求,因此我們採用 console 點選的方式與對方對接。結果如下圖:
在 Transit Gateway Route Table 新增對內部網段的路由
resource "aws_ec2_transit_gateway_route" "to_internal" {
for_each = toset(var.corporate_cidrs)
destination_cidr_block = each.value
### 這邊會指向前一步建立的 attachment
### 因為是 console 建立的,所以這邊用 data 來撈
transit_gateway_attachment_id = data.aws_ec2_transit_gateway_peering_attachment.this.id
### 會指向前一步 attachment 預設的 route table,一樣用 data 撈回
transit_gateway_route_table_id = data.aws_ec2_transit_gateway_route_table.this.id
}
建立 VPC Attachment
resource "aws_ec2_transit_gateway_vpc_attachment" "internal" {
### 把 VPC attachment 貼到我們的 private subnet 上
subnet_ids = data.aws_subnets.internal_private_subnets.ids
transit_gateway_id = aws_ec2_transit_gateway.this.id
vpc_id = data.aws_vpc.internal.id
}
在 VPC Route Table 新增回程路由
resource "aws_route" "internal_private" {
for_each = toset(var.corporate_cidrs)
### 一樣是貼回第二步建立的 attachment 預設的 route table 中
route_table_id = data.aws_route_table.internal_private.id
destination_cidr_block = each.value
### 藉由我們建立的 TGW 來進行路由
transit_gateway_id = aws_ec2_transit_gateway.this.id
}
更新 Security Group
resource "aws_vpc_security_group_ingress_rule" "internal_node_sg" {
for_each = toset(var.corporate_cidrs)
description = "Allow internal VPC communication"
security_group_id = data.aws_security_group.internal_node_sg.id
cidr_ipv4 = each.value
ip_protocol = "tcp"
from_port = 0
to_port = 65535
}
進入 Argo CD Repo Server Pod
kubectl exec -it deploy/argocd-repo-server -n argocd -- sh
嘗試 git clone
內部 Repo
cd /helm-working-dir/
git clone [internal-repo]
如果能順利 clone,而不是 timeout,就表示 TGW 已經打通。
測試完成後,把 SSH Known Hosts 加入 Argo CD,並把 Repo 設定改為 gitlab.internal.com
。
在網路打通後,我們就能在 EKS 內部部署自有 Runner。我們採用的方式並不是單純安裝 Helm Release,而是延續前幾天的 ApplicationSet 架構,讓多架構 Runner 能自動生成對應的 Deployment。
透過 Git Generator,我們會讀取 internal/
目錄下的檔案,裡面定義了不同架構的 Runner(例如 arm.yaml
、x86.yaml
)。ApplicationSet 會依據這些檔案自動建立 Runner 的 Application。
在每個架構的 values 檔案裡,我們會設定:
例如 arm.yaml
的片段如下:
gitlab-runner:
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/gitlab-runner-arm
nodeSelector:
usage: runner-arm
tolerations:
- key: RunnerArmOnly
operator: Exists
serviceAccount:
name: gitlab-runner-arm
runners:
config: |
[[runners]]
[runners.kubernetes]
service_account = "gitlab-runner-arm"
helper_image = "registry.gitlab.com/gitlab-org/gitlab-runner/gitlab-runner-helper:arm64-${CI_RUNNER_REVISION}"
這樣,我們就能在同一個 ApplicationSet 中,同時管理 arm 與 x86 的 Runner,不需要手動維護多個 Helm Release,而是透過 Git 檔案驅動,實現多架構 Runner 的自動化管理。
完成 Runner 部署後,下一步就是讓它們透過 IAM Role for Service Account (IRSA) 與 ECR 溝通:
另外,在多帳號的情境下,我們也必須考慮權限邊界的設計:
sts:AssumeRole
權限,可以切換成對應帳號的 專案 Runner Role。這樣一來,就能避免 GitLab Runner Role 本身權限過大的問題,同時也把跨帳號的責任切分清楚。
今天,我們完成了 Transit Gateway 與 GitLab Runner 部署:
明天 (Day 23),我們會介紹 CRI / CNI / CSI 這三個 Kubernetes 基本介面,這些介面是理解 Kubernetes 底層運作的關鍵,也為後續 Day 24/25 的 K8s Networking,以及 Day 26-28 的 Service Mesh 與 Cilium 做好準備! 🚀