site logo

Marico's space

使用 Azure DevOps、ACR、Helm、AKS 和 PostgreSQL 构建生产级 CI/CD 流水线

服务器技术 2026-09-18 14:51:56 3

最近折腾了一套完整的 CI/CD 流水线,从 GitHub 代码托管开始,一路经过 Azure DevOps 构建、Docker 镜像推送、Helm 部署,最终跑在 AKS 上。踩了不少坑,这篇把整个过程和关键概念说清楚。

目标不是做个多复杂的应用,而是理解应用代码从源码到真实部署环境这个过程里,到底发生了什么。

最终要搞清楚的流程是这样的:

Developer | v
GitHub Repository | v
Azure DevOps | v
CI/CD Pipeline | +----------------------+
 | |
v v Build + Test Docker Build | v Azure Container Registry | v Helm | v AKS | v DEV | v Running Application

应用本身故意做得很简单:

  • 前端
  • 后端
  • PostgreSQL 数据库

学习的重点其实在基础设施这一块。

我想搞清楚这些东西:

  • Azure 资源组
  • Azure Container Registry(容器镜像仓库)
  • Azure Kubernetes Service
  • Azure 网络配置
  • 私有网络
  • PostgreSQL Flexible Server
  • 私有 DNS
  • Docker
  • Kubernetes 命名空间
  • Deployment
  • Service
  • ConfigMap
  • Secret
  • Helm
  • Azure DevOps
  • Azure 服务连接
  • Workload Identity Federation
  • Azure RBAC
  • Kubernetes RBAC
  • CI/CD
  • 问题排查

这次只做 DEV 环境,UAT、PROD 和审批门以后再加。

1. 最终架构

DEV 环境大概长这样:

 GitHub | v Azure DevOps | v Build + Test + Docker | v Azure Container Registry | v Helm | v Azure Kubernetes Service | +------------+------------+
 | |
v v frontend namespace backend namespace
 | |
v v Frontend Deployment Backend Deployment
 | |
v v LoadBalancer Service ClusterIP Service
 | |
v | Public IP / Browser | v PostgreSQL Flexible Server | v Private Network | v Private DNS Zone

一个重要的设计决策:PostgreSQL 没有装在 AKS 里面,而是用了 Azure Database for PostgreSQL Flexible Server。

也就是说 AKS 只负责跑应用负载,数据库服务由 Azure 来管。

2. 项目结构

项目仓库是:

k8app-azure-devops

仓库里包含前端、后端和 Kubernetes/Helm 配置。

简化后的结构:

k8app-azure-devops/
│
├── backend/
│ ├── app/
│ └── backend.dockerfile
│
├── frontend/
│ └── frontend.dockerfile
│
├── helm/
│ └── k8app/
│ ├── Chart.yaml
│ ├── values.yaml
│ └── templates/
│ ├── frontend-deployment.yaml
│ ├── frontend-service.yaml
│ ├── backend-deployment.yaml
│ ├── backend-service.yaml
│ ├── backend-configmap.yaml
│ └── backend-secret.yaml
│
└── azure-pipelines.yml

具体应用本身不重要,基础设施才是重点。

3. Azure 订阅和区域

用的订阅是 Azure for Students

区域选的是:

South Africa North

选这个区域是因为学生订阅对可用区域有限制,有的区域用不了。

创建资源之前最好先确认一下订阅允许哪些区域,可以从 Azure Portal 创建资源时查看,或者用命令:

az account list-locations -o table

4. 创建 Azure 资源组

第一个创建的资源是资源组:

Resource Group:
devops-aks-lab-rg-sa Region:
South Africa North

为什么要创建资源组?

资源组是 Azure 资源的逻辑容器。

把所有相关资源放在一起而不是分散在 Azure 各处,管理起来方便多了:

devops-aks-lab-rg-sa | +-- ACR +-- AKS +-- PostgreSQL +-- Networking resources

权限管理、资源清理都更简单。

Azure Portal 操作

进入:

Azure Portal ↓
Resource groups ↓
Create

然后选择:

Subscription:
Azure for Students Resource group:
devops-aks-lab-rg-sa Region:
South Africa North

点击 Review + create,然后 Create。

Azure CLI 操作

也可以用命令行:

az login

然后:

az group create \ --name devops-aks-lab-rg-sa \ --location southafricanorth

验证:

az group show \ --name devops-aks-lab-rg-sa \ -o table

5. 创建 Azure Container Registry

下一个组件是 ACR:

ACR name:
dessydevopsacr

仓库登录服务器是:

dessydevopsacr.azurecr.io

为什么需要 ACR?

CI 流水线构建 Docker 镜像,这些镜像需要一个地方存放,Kubernetes 才能拉取部署。

ACR 就成了镜像的中央仓库:

Azure DevOps | | Docker image v
Azure Container Registry | | Docker image v
AKS

这个项目有两个镜像:

dessydevopsacr.azurecr.io/k8app-backend
dessydevopsacr.azurecr.io/k8app-frontend

创建步骤

进入 Azure Portal:

Azure Portal ↓
Create a resource ↓
Search:
Container Registry ↓
Create

在 Basics 页面配置:

Subscription:
Azure for Students Resource group:
devops-aks-lab-rg-sa Registry name:
dessydevopsacr Location:
South Africa North SKU:
Basic

这次禁用了 ACR 管理账号。

点击 Review + create,然后 Create。

CLI 创建

az acr create \ --resource-group devops-aks-lab-rg-sa \ --name dessydevopsacr \ --location southafricanorth \ --sku Basic \ --admin-enabled false

验证:

az acr show \ --name dessydevopsacr \ --resource-group devops-aks-lab-rg-sa \ --query "{name:name,loginServer:loginServer,sku:sku.name}" \ -o table

预期登录服务器:

dessydevopsacr.azurecr.io

为什么要禁用 ACR 管理账号?

ACR 可以通过管理账号提供静态用户名密码,但不想让 CI/CD 依赖这种长期有效的凭证。

Azure DevOps 用服务连接来认证,模型更干净:

Azure DevOps | v
Service Connection | v
Azure / ACR

6. 创建虚拟网络

因为 PostgreSQL 要用私有网络,所以在创建数据库之前就得考虑网络。

基本思路是不同负载用不同的网络区域:

Virtual Network
|
+-- AKS subnet
|
+-- PostgreSQL subnet

PostgreSQL 子网用的是:

postgres-subnet
10.0.2.0/24

/24 给数据库子网分配了一个固定的私有地址段。

把数据库单独放在一个子网里是因为它有不同的用途和安全边界,跟应用负载不一样。

IP 规划说明

项目确定用了:

postgres-subnet:
10.0.2.0/24

AKS 集群还用了:

Pod CIDR:
10.244.0.0/16 Service CIDR:
10.240.0.0/16

这些跟 Azure VNet 子网不是一回事。

复现项目时网络设计要注意不要创建重叠的 CIDR 段,比如:

VNet
|
+-- AKS subnet
|
+-- PostgreSQL subnet | +-- 10.0.2.0/24

规则就是:不要让两个不同子网包含相同地址。

7. 创建 PostgreSQL 子网

在 Azure Portal 里从虚拟网络创建子网:

Azure Portal ↓
Virtual networks ↓
Select your project VNet ↓
Subnets ↓
+ Subnet

创建:

Subnet name:
postgres-subnet Subnet address range:
10.0.2.0/24

保存即可,这样就给 PostgreSQL 预留了一个专用私有网段。

8. 为什么 PostgreSQL 要用私有模式

故意没有把 PostgreSQL 暴露到公网。

走的是:

AKS | | Private network v
PostgreSQL

而不是:

Internet | v
Public PostgreSQL endpoint

这更接近生产环境的架构设计,应用负载通过私有网络跟数据库通信。

9. 创建私有 DNS Zone

这个概念花了一段时间才理解。

光给 PostgreSQL 创建私有 IP 不够,应用通常用主机名连接数据库,比如:

dessy-k8app-postgres.postgres.database.azure.com

主机名需要解析到私有 IP 地址,这就是私有 DNS 的作用。

创建了:

private.postgres.database.azure.com

私有 DNS 做什么?

把 DNS 想象成电话本。

应用知道:

dessy-k8app-postgres.postgres.database.azure.com

但网络最终需要的是 IP 地址。

私有 DNS 提供映射:

Hostname | v
Private DNS | v
Private IP

这个项目里 PostgreSQL 私有 IP 解析到:

10.0.2.4

所以流程是:

AKS Pod | | asks DNS: | "Where is | dessy-k8app-postgres.postgres.database.azure.com?" | v
Private DNS Zone | v
10.0.2.4 | v
PostgreSQL

没有 DNS 解析的话,应用知道主机名但不知道往哪发网络流量。

10. 在 Azure Portal 创建私有 DNS Zone

进入:

Azure Portal ↓
Create a resource ↓
Search:
Private DNS zones ↓
Create

配置:

Resource group:
devops-aks-lab-rg-sa Name:
private.postgres.database.azure.com

点击 Review + create,然后 Create。

创建完之后还需要把 DNS zone 链接到 VNet:

Private DNS zone ↓
Virtual network links ↓
+ Add

选择应用/数据库用的 VNet,这样 VNet 里的资源就能用这个私有 DNS zone。

11. 创建 Azure Database for PostgreSQL Flexible Server

创建了:

PostgreSQL server:
dessy-k8app-postgres Region:
South Africa North PostgreSQL:
16 SKU:
Standard_B1ms Database:
app

主机名是:

dessy-k8app-postgres.postgres.database.azure.com

服务器用的是私有网络。

为什么要用 PostgreSQL Flexible Server?

没有在 Kubernetes 里面跑 PostgreSQL:

AKS | +-- PostgreSQL Pod

而是:

AKS | | private network v
Azure PostgreSQL Flexible Server

这样把应用计算和数据库基础设施分开了。

AKS 专注跑:

frontend
backend

数据库服务由 Azure 管理。

12. 在 Azure Portal 创建 PostgreSQL

进入:

Azure Portal ↓
Create a resource ↓
Search:
Azure Database for PostgreSQL Flexible Server ↓
Create

在 Basics 页面配置:

Subscription:
Azure for Students Resource group:
devops-aks-lab-rg-sa Server name:
dessy-k8app-postgres Region:
South Africa North PostgreSQL version:
16 Workload type:
Development / appropriate low-cost option Compute:
Standard_B1ms

配置管理员:

Administrator username:
k8appadmin Administrator password:
<your password>

密码不要公开。

13. 配置 PostgreSQL 网络

创建 PostgreSQL 时选择私有访问而不是公共访问。

数据库需要使用 VNet 和专用子网:

Virtual network:
<project VNet> Subnet:
postgres-subnet

私有 DNS 配置用:

private.postgres.database.azure.com

关键关系是:

PostgreSQL | v
postgres-subnet
10.0.2.0/24 | v
Private DNS
private.postgres.database.azure.com

Azure 就会建立私有网络连接。

14. 创建应用数据库

服务器创建完之后,创建数据库:

app

从 PostgreSQL 资源进入:

PostgreSQL Flexible Server ↓
Databases ↓
+ Add

创建:

Database name:
app

应用后续连接用:

Server:
dessy-k8app-postgres.postgres.database.azure.com Database:
app User:
k8appadmin

密码保密。

15. 从 AKS 验证 PostgreSQL 网络

创建完数据库还不够,更重要的问题是:

跑在 AKS 里的负载能不能真正连到 PostgreSQL?

从 Kubernetes 里面测试一下。

先检查 DNS 解析:

kubectl run psql-test \ --rm -it \ --image=postgres:16 \ --restart=Never \ -- \ getent hosts dessy-k8app-postgres.postgres.database.azure.com

主机名解析到了私有 PostgreSQL IP:

10.0.2.4

这证明私有 DNS 路径是通的。

16. 测试 PostgreSQL 连接

用临时 PostgreSQL 客户端 pod 测试:

kubectl run psql-test \ --rm -it \ --image=postgres:16 \ --restart=Never \ -- \ psql "host=dessy-k8app-postgres.postgres.database.azure.com port=5432 dbname=app user=k8appadmin sslmode=require"

提示输入密码时输入数据库密码。

通过 TLS 连接成功。

这证明这条路是通的:

AKS Pod | v
Private DNS | v
Private IP | v
PostgreSQL Flexible Server

这是很重要的测试,把数据库/网络问题和应用问题分开了。

17. 创建 AKS 集群

下一个主要组件是 Azure Kubernetes Service。

创建了:

AKS:
dessy-aks-cluster Resource Group:
devops-aks-lab-rg-sa Region:
South Africa North

集群用了:

Azure CNI Overlay Pod CIDR:
10.244.0.0/16 Service CIDR:
10.240.0.0/16 Load Balancer:
Standard

还开启了 Microsoft Entra 集成和 Kubernetes RBAC。

18. 为什么用 Azure CNI Overlay?

AKS 网络有不同配置方式,这次用了 Azure CNI Overlay。

一个重要区别是这些 CIDR:

10.244.0.0/16
10.240.0.0/16

是 Kubernetes 网络范围,跟 Azure VNet 子网范围不是一回事。

看到 10.244.x.x 这种 IP 很容易误以为是 Azure VNet IP,其实不是。

简化模型是:

Azure VNet | +-- Azure subnet | +-- AKS nodes | +-- Kubernetes pod networking | +-- Kubernetes service networking

19. 在 Azure Portal 创建 AKS

进入:

Azure Portal ↓
Create a resource ↓
Search:
Kubernetes Service ↓
Create

在 Basics 页面配置:

Subscription:
Azure for Students Resource group:
devops-aks-lab-rg-sa Cluster preset:
appropriate development option Kubernetes cluster name:
dessy-aks-cluster Region:
South Africa North

节点池配一个节点用于学习环境。

20. 配置 AKS 身份

身份/安全配置用:

Managed identity

而不是手动管理长期凭证,这样 Azure 资源可以用托管身份来工作。

21. 配置 Microsoft Entra ID 和 Kubernetes RBAC

开启了 Microsoft Entra 集成。

集群用 Microsoft Entra ID 做身份,Kubernetes RBAC 做授权。

认证和授权不是一回事:

Microsoft Entra ID | | Who are you? v
Authentication | v
Kubernetes RBAC | | What are you allowed to do? v
Authorization

22. 配置 AKS 网络

集群网络配置:

Network plugin:
Azure CNI Network plugin mode:
Overlay Pod CIDR:
10.244.0.0/16 Service CIDR:
10.240.0.0/16

用了 Standard 负载均衡器。

这很重要,因为前端 Kubernetes Service 后面会配置成:

type: LoadBalancer

Azure 就会给那个 Service 配一个 Azure 负载均衡器/公网 IP。

23. 开启 OIDC 和 Workload Identity

开启了:

OIDC issuer
Workload identity

这些功能对 Kubernetes 负载和 Azure 服务之间的现代身份认证很有用,也能避免在应用里放长期 Azure 凭证。

24. 连接 kubectl 到 AKS

集群创建完之后获取凭证:

az aks get-credentials \ --resource-group devops-aks-lab-rg-sa \ --name dessy-aks-cluster \ --overwrite-existing

然后:

kubectl get nodes

预期节点状态:

Ready

这确认了本地机器可以跟集群通信。

25. AKS 身份和 ACR

这个项目涉及多个身份,这是最重要的学习点之一。

AKS 集群有个 kubelet 托管身份,用命令查看:

az aks show \ --resource-group devops-aks-lab-rg-sa \ --name dessy-aks-cluster \ --query identityProfile.kubeletidentity.clientId \ -o tsv

kubelet 身份需要权限才能从 ACR 拉镜像。

需要的权限是:

AcrPull

流程是:

AKS Kubelet Identity | | AcrPull v
Azure Container Registry | v
Docker Image

26. 给 AKS 分配 AcrPull 权限

在 Azure Portal 进入:

Container Registry ↓
dessydevopsacr ↓
Access control (IAM) ↓
Role assignments

添加角色分配,选择:

Role:
AcrPull

分配给 AKS kubelet 托管身份。

之后:

AKS | | AcrPull v
ACR | v
k8app-backend image
k8app-frontend image

重要区别是:

Azure DevOps 推送镜像,AKS 拉取镜像。

这是两个不同操作,可以用两个不同身份。

27. 理解三层身份概念

这个项目涉及三个不同概念。

OIDC / Workload Identity

回答:

Who are you?

对 Azure DevOps:

Azure DevOps | v
OIDC / Workload Identity Federation | v
Microsoft Entra ID

Azure RBAC

回答:

What can you do in Azure?

比如:

Azure DevOps identity | v
Contributor | v
Resource Group

Kubernetes RBAC

回答:

What can you do inside Kubernetes?

比如:

Azure DevOps identity | v
Kubernetes RoleBinding | v
edit | +-- frontend namespace | +-- backend namespace

简化模型:

OIDC ↓
Who are you? Azure RBAC ↓
What can you do in Azure? Kubernetes RBAC ↓
What can you do in Kubernetes?

28. 创建 Azure DevOps 项目

创建了 Azure DevOps 项目:

DessyTest-1

进入:

Azure DevOps ↓
New Project

创建:

Project name:
DessyTest-1

GitHub 仓库还是作为源码仓库,Azure DevOps 只负责 CI/CD 流水线。

关系是:

GitHub | v
Azure DevOps | v
Pipeline

29. 连接 GitHub 到 Azure DevOps

在 Azure DevOps 里面配置 GitHub 连接:

Azure DevOps ↓
Project Settings ↓
Service connections

创建 GitHub 连接并授权仓库,目的是让 Azure DevOps 能拉取仓库源码。

30. 创建 ACR 服务连接

Azure DevOps 需要认证到 ACR:

Azure DevOps ↓
Project Settings ↓
Service connections ↓
New service connection

选择:

Docker Registry

然后配置为 Azure Container Registry,创建服务连接:

dessydevopsacr

这个连接被流水线的 Docker 任务使用。

概念:

Azure DevOps | v
ACR Service Connection | v
Azure Container Registry

31. 创建 AKS Azure 服务连接

部署阶段需要 Azure 访问权限来获取 AKS 凭证,创建了:

dessy-aks-deploy

进入:

Azure DevOps ↓
Project Settings ↓
Service connections ↓
New service connection

选择 Azure Resource Manager 连接类型,用:

Workload Identity Federation

而不是长期客户端密码,这样得到:

Azure DevOps | v
OIDC / Workload Identity Federation | v
Microsoft Entra ID | v
Azure

32. 给 Azure DevOps 身份分配 Azure 权限

部署服务连接用的服务主体需要对 Azure 资源有操作权限。

学习环境里分配了资源组级别的:

Contributor

给:

devops-aks-lab-rg-sa

在 Azure Portal:

Resource Group ↓
devops-aks-lab-rg-sa ↓
Access control (IAM) ↓
Add role assignment

选择 Contributor,然后选择 dessy-aks-deploy 用的服务主体。

生产环境应该用更严格的权限。

33. Kubernetes RBAC 配置

光有 Azure 权限不够,流水线能认证到 Azure 但 Kubernetes 还是拒绝了某些操作。

这教会了一个重要教训:

Azure 认证和 Kubernetes 授权是两个独立的问题。

Azure DevOps 服务主体是:

<AZURE_DEVOPS_SERVICE_PRINCIPAL_OBJECT_ID>

frontend 命名空间创建了 RoleBinding,在 backend 也创建了。

绑定给这个身份 Kubernetes edit ClusterRole。

示例:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: name: azure-devops-deployer namespace: backend
subjects: - kind: User name: <AZURE_DEVOPS_SERVICE_PRINCIPAL_OBJECT_ID> apiGroup: rbac.authorization.k8s.io
roleRef: kind: ClusterRole name: edit apiGroup: rbac.authorization.k8s.io

前端绑定一样,只是 namespace: frontend

34. 测试 Kubernetes RBAC

测试 Azure DevOps 身份是否能创建 Deployment:

kubectl auth can-i create deployments \ --as=<AZURE_DEVOPS_SERVICE_PRINCIPAL_OBJECT_ID> \ -n backend
kubectl auth can-i create deployments \ --as=<AZURE_DEVOPS_SERVICE_PRINCIPAL_OBJECT