跳转至

Kubernetes 部署

SECoder 使用 Kubernetes 运行应用。项目将部署配置和源代码一起保存在 Git 仓库中:GitLab CI 使用 BuildKit 构建镜像,将镜像推送到项目的 Container Registry,再使用 Kustomize 和 kubectl 更新 Kubernetes 资源。

一次部署包含什么

一个可从浏览器访问的 Web 应用通常至少包含以下资源:

资源 作用
Deployment / StatefulSet 声明要运行的容器、镜像、环境变量、端口和资源限制
Pod Kubernetes 根据工作负载声明创建的容器实例
Service 为一组 Pod 提供稳定的集群内访问地址
HTTPRoute 把 *.t.secoder.net 的外部请求转发到 Service
Secret 保存 Registry 凭据等敏感配置
ConfigMap 保存非敏感配置
PersistentVolumeClaim 为需要持久化的数据申请存储

一般不直接创建或修改 Pod,而是修改 Deployment 或 StatefulSet,让 Kubernetes 维护所需的 Pod。

部署目录

推荐把资源文件集中在仓库的 deploy/ 目录:

1
2
3
4
5
deploy/
├── deployment.yaml
├── service.yaml
├── httproute.yaml
└── kustomization.yaml

Deployment

下面的示例部署一个监听 80 端口的前端应用。请统一替换资源名、镜像占位值和标签。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-2026-next-hw
  labels:
    app.kubernetes.io/name: app-2026-next-hw
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: app-2026-next-hw
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app.kubernetes.io/name: app-2026-next-hw
    spec:
      imagePullSecrets:
        - name: gitlab-registry-2026-next-hw
      containers:
        - name: next
          image: registry.example.invalid/2026-next-hw:latest
          env:
            - name: HOSTNAME
              value: 0.0.0.0
            - name: PORT
              value: "80"
          ports:
            - name: http
              containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 10
            periodSeconds: 10
          resources:
            requests:
              cpu: 32m
              memory: 256Mi
            limits:
              cpu: 125m
              memory: 1Gi

selector.matchLabels、Pod 标签和 Service selector 必须完全一致。应用也必须监听 0.0.0.0,不能只监听 127.0.0.1。

Service

Service 将请求转发到带有相同标签的 Pod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: v1
kind: Service
metadata:
  name: app-2026-next-hw
spec:
  selector:
    app.kubernetes.io/name: app-2026-next-hw
  ports:
    - name: http
      port: 80
      targetPort: http

同一命名空间中的其他应用可以通过 http://app-2026-next-hw 访问这个 Service。

HTTPRoute

HTTPRoute 为 Service 分配外部域名:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: route-2026-next-hw
spec:
  parentRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: traefik-gateway
      namespace: infra
  hostnames:
    - u-2026000000-2026-next-hw.t.secoder.net
  rules:
    - backendRefs:
        - name: app-2026-next-hw
          port: 80
          kind: Service
      matches:
        - path:
            type: PathPrefix
            value: /

学生应用的 hostname 必须以自己的命名空间开头。例如命名空间是 u-2026000000,域名可以是 u-2026000000-2026-next-hw.t.secoder.net。

Kustomization

kustomization.yaml 是整套资源的入口:

1
2
3
4
5
6
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
  - httproute.yaml

提交前可以在本地预览最终配置,这条命令不会修改集群:

1
kubectl kustomize deploy

更多可复用资源和补丁示例见 secoder-tmpl。

CI/CD 凭据

在 GitLab 项目的 Settings → CI/CD → Variables 中配置:

变量 值 保护方式
TOKEN SECoder 个人资料页面中的 Kubernetes API 令牌 Masked
NAMESPACE 个人命名空间,例如 u-2026000000 普通变量
GITLAB_REGISTRY_USER 当前项目 Deploy Token 的用户名 普通变量
GITLAB_DEPLOY_TOKEN 当前项目具有 read_registry 权限的 Deploy Token Masked

CI_REGISTRY、CI_REGISTRY_IMAGE、CI_REGISTRY_USER、CI_REGISTRY_PASSWORD 和 CI_COMMIT_SHA 由 GitLab 自动提供,用于在当前 CI 作业中构建并推送镜像。

Deploy Token 的创建和轮换步骤见 CI/CD 小作业。

GitLab CI/CD

以下配置包含镜像构建和部署两个阶段。为避免 Runner 获取外部模板失败,请把模板直接放进项目的 .gitlab-ci.yml,不要使用远程 include:。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
stages:
  - build
  - deploy

.buildkit:
  image:
    name: moby/buildkit:rootless
    entrypoint: [""]
  variables:
    BUILDKITD_CONFIG: /tmp/buildkitd.toml
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox --config /tmp/buildkitd.toml
    BUILDKIT_CACHE_REF: $CI_REGISTRY_IMAGE:buildcache
  before_script:
    - mkdir -p ~/.docker
    - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
    - |
      cat > "$BUILDKITD_CONFIG" <<'EOF'
      [registry."docker.io"]
        insecure = true
      EOF
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --import-cache type=registry,ref="${BUILDKIT_CACHE_REF}" \
        --export-cache type=registry,ref="${BUILDKIT_CACHE_REF}",mode=max \
        --output type=image,name="${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}",push=true

.kustomize:
  image:
    name: alpine/k8s:1.35.1
    entrypoint: [""]
  variables:
    KUSTOMIZE_PATH: deploy
  script:
    - |
      set -eu
      : "${TOKEN:?TOKEN is required}"
      : "${NAMESPACE:?NAMESPACE is required}"
      export KUBECTL_APPLYSET=true
      kubectl --token="${TOKEN}" -n "${NAMESPACE}" \
        apply -k "${KUSTOMIZE_PATH}" \
        --applyset="gitops-ci-${CI_PROJECT_PATH_SLUG}" \
        --prune

build-image:
  extends: .buildkit
  stage: build
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

deploy:
  extends: .kustomize
  stage: deploy
  needs:
    - build-image
  before_script:
    - |
      set -eu
      : "${GITLAB_REGISTRY_USER:?GITLAB_REGISTRY_USER is required}"
      : "${GITLAB_DEPLOY_TOKEN:?GITLAB_DEPLOY_TOKEN is required}"
      kubectl --token="${TOKEN}" -n "${NAMESPACE}" \
        create secret docker-registry gitlab-registry-2026-next-hw \
        --docker-server="${CI_REGISTRY}" \
        --docker-username="${GITLAB_REGISTRY_USER}" \
        --docker-password="${GITLAB_DEPLOY_TOKEN}" \
        --dry-run=client -o yaml | \
        kubectl --token="${TOKEN}" -n "${NAMESPACE}" apply -f -
    - sed -i "s|registry.example.invalid/2026-next-hw:latest|${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}|" deploy/deployment.yaml
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

后端项目使用同一结构,只需替换资源名、容器启动配置、健康检查和对外域名。

ApplySet 会清理资源

--prune 会删除同一 ApplySet 上一次部署过、但本次 Kustomize 输出中已经不存在的资源。不要让两个项目使用相同的 ApplySet 名称,也不要用一个项目管理另一个项目的资源。

kubectl apply 不是事务操作。如果某个资源校验失败,排在它前面的资源可能已经更新。应先检查流水线日志和命名空间中的实际资源,再修正配置并重新运行流水线。

验证部署

流水线成功后,依次验证工作负载、Service 和 HTTPRoute:

1
2
3
4
kubectl get deployment,pod,service,httproute
kubectl rollout status deployment/app-2026-next-hw
kubectl get httproute route-2026-next-hw -o yaml
curl -I https://u-2026000000-2026-next-hw.t.secoder.net

若部署失败,按 kubectl 使用指南检查事件、容器日志、Service selector 和 HTTPRoute 状态。

作者: Ajax Dong (86.76%), abmfy (13.24%)