← К программе курса

Модуль 17 / 19

GitLab CI/CD → Kubernetes

KUBERNETES

Содержание
01

1. Цели модуля

Вы научитесь проектировать безопасный GitLab pipeline для Kubernetes: один раз собирать образ, проверять и подписывать immutable digest, развёртывать через GitLab Agent и подтверждать результат после deploy.

  • pipeline graph и artifacts
  • immutable image digest
  • SBOM, vulnerability scan и Cosign
  • GitLab Agent и KUBE_CONTEXT
  • protected environments и rollback
02

2. Теория: pipeline как цепочка доверия

Pipeline должен продвигать один и тот же artifact между средами, а не пересобирать его. Commit SHA удобен как tag, но deploy фиксирует digest вида repository@sha256:…, который невозможно незаметно переназначить.

Stages выражают порядок, needs — зависимости данных, artifacts передают отчёты, dotenv — вычисленный IMAGE_REF. Cache ускоряет повторные загрузки, но не является доверенным build artifact.

03

3. Теория: supply-chain security

Минимальная цепочка: воспроизводимая сборка → SBOM → vulnerability policy → подпись digest → проверка identity подписанта → admission policy в кластере. Scan не доказывает отсутствие уязвимостей, а подпись не доказывает безопасность содержимого; это разные утверждения.

GitLab.com может выдавать OIDC ID token с audience sigstore для keyless Cosign. Короткоживущий ключ и certificate связывают подпись с pipeline identity. Self-managed GitLab требует собственной доверенной Sigstore infrastructure или управляемых ключей.

04

4. Теория: GitLab Agent и границы доступа

GitLab Agent устанавливает outbound-соединение с KAS, поэтому runner не требует прямого inbound-доступа к API Server. Авторизованные CI jobs получают kubeconfig через KUBECONFIG и выбирают context формата project/path:agent-name.

Отдельный Agent нужен для каждого кластера. Используйте environment-scoped KUBE_CONTEXT, отдельные ServiceAccounts и namespaced RBAC; не храните cluster-admin kubeconfig в CI variables. Разрешение проекта в agent config само по себе не заменяет Kubernetes RBAC.

05

5. Теория: push deploy или GitOps

Прямой CI deploy является push-based: job получает право менять кластер. Он уместен для staging, миграции и специальных pipeline-driven процедур, но GitLab рекомендует GitOps для production из-за более сильной модели безопасности.

При GitOps pipeline строит, проверяет и публикует artifact, затем изменяет декларативный desired state через merge request. In-cluster controller выполняет pull и reconciliation; production credentials не выдаются build runner.

06

6. Методические указания

  • Защитите default branch, tags, CI configuration и production environment
  • Используйте deployment approvals и manual promotion для production
  • resource_group сериализует deploy одного environment
  • Разделяйте build и deployment projects при строгой модели доступа
  • Используйте masked/protected/file variables и внешнее secret storage
  • Фиксируйте CI images по digest в production
  • Не печатайте tokens, kubeconfig и registry password
  • Храните security reports и deployment evidence с разумным retention
  • Rollback проверяйте тем же smoke test, что и deploy
07

7. Лабораторная работа: доступ через Agent

После регистрации Agent сохраните конфигурацию как .gitlab/agents/staging/config.yaml, заменив project id на реальный путь. В GitLab создайте environment-scoped protected variable KUBE_CONTEXT со значением пути context вашего Agent.

terminal
ci_access:
  projects:
    - id: platform/course-web

Для namespace course-staging выдайте CI identity только get/list/watch и необходимые изменения Deployment/Service. Проверьте разрешения через kubectl auth can-i до первого deploy.

08

8. Лабораторная работа: manifest с immutable image

Сохраните manifest как k8s/deployment.yaml. Pipeline заменяет ровно маркер IMAGE_REFERENCE на digest из build job; в Git хранится шаблон без mutable tag.

terminal
apiVersion: apps/v1
kind: Deployment
metadata:
  name: course-web
  namespace: course-staging
spec:
  replicas: 2
  selector:
    matchLabels: {app: course-web}
  template:
    metadata:
      labels:
        app: course-web
    spec:
      containers:
        - name: web
          image: IMAGE_REFERENCE
          ports: [{name: http, containerPort: 8080}]
          readinessProbe:
            httpGet: {path: /healthz, port: http}
          resources:
            requests: {cpu: 50m, memory: 64Mi}
            limits: {memory: 128Mi}
09

9. Лабораторная работа: build, scan, sign и deploy

Сохраните пример как .gitlab-ci.yml и адаптируйте health path к приложению. Он рассчитан на GitLab.com для public Sigstore и на Docker runner, которому разрешён dind. Для production зафиксируйте CI images по проверенным digests.

terminal
stages: [validate, build, scan, sign, deploy, verify]

variables:
  IMAGE_TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
  DOCKER_TLS_CERTDIR: "/certs"

validate:
  stage: validate
  image: alpine/helm:4.2.4
  script:
    - helm lint ./chart -f ./chart/values-staging.yaml
    - helm template course-web ./chart -f ./chart/values-staging.yaml > rendered.yaml
  artifacts:
    paths: [rendered.yaml]

build:
  stage: build
  image: docker:28.4.0-cli
  services:
    - name: docker:28.4.0-dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - docker buildx build --pull --push --tag "$IMAGE_TAG" .
    - IMAGE_DIGEST="$(docker buildx imagetools inspect "$IMAGE_TAG" --format '{{.Manifest.Digest}}')"
    - test -n "$IMAGE_DIGEST"
    - echo "IMAGE_REF=$CI_REGISTRY_IMAGE@$IMAGE_DIGEST" > build.env
  artifacts:
    reports:
      dotenv: build.env

scan:
  stage: scan
  image:
    name: aquasec/trivy:0.66.0
    entrypoint: [""]
  script:
    - trivy image --username "$CI_REGISTRY_USER" --password "$CI_REGISTRY_PASSWORD" --format cyclonedx --output gl-sbom-report.cdx.json "$IMAGE_REF"
    - trivy image --username "$CI_REGISTRY_USER" --password "$CI_REGISTRY_PASSWORD" --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed "$IMAGE_REF"
  artifacts:
    when: always
    paths: [gl-sbom-report.cdx.json]
    reports:
      cyclonedx: gl-sbom-report.cdx.json

sign:
  stage: sign
  image:
    name: ghcr.io/sigstore/cosign/cosign:v3.1.2
    entrypoint: [""]
  id_tokens:
    SIGSTORE_ID_TOKEN:
      aud: sigstore
  variables:
    COSIGN_YES: "true"
  script:
    - cosign login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
    - cosign sign "$IMAGE_REF"
    - cosign verify "$IMAGE_REF" --certificate-identity-regexp "^https://gitlab.com/$CI_PROJECT_PATH//.gitlab-ci.yml@refs/heads/$CI_DEFAULT_BRANCH$" --certificate-oidc-issuer "https://gitlab.com"

deploy_staging:
  stage: deploy
  image:
    name: registry.k8s.io/kubectl:v1.37.0
    entrypoint: [""]
  resource_group: course-staging
  environment:
    name: staging
  script:
    - kubectl config use-context "$KUBE_CONTEXT"
    - kubectl create namespace course-staging --dry-run=client -o yaml | kubectl apply -f -
    - sed "s|IMAGE_REFERENCE|$IMAGE_REF|" k8s/deployment.yaml > deployment.rendered.yaml
    - kubectl apply --dry-run=server -f deployment.rendered.yaml
    - kubectl apply --server-side --field-manager=gitlab-ci -f deployment.rendered.yaml
    - kubectl rollout status deployment/course-web -n course-staging --timeout=180s
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

verify_staging:
  stage: verify
  image:
    name: registry.k8s.io/kubectl:v1.37.0
    entrypoint: [""]
  environment:
    name: staging
  script:
    - kubectl config use-context "$KUBE_CONTEXT"
    - kubectl get deployment,pod -n course-staging -o wide
    - kubectl get deployment course-web -n course-staging -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
    - kubectl wait deployment/course-web -n course-staging --for=condition=Available --timeout=60s
  needs: [deploy_staging]

В Settings → CI/CD защитите environment production/staging по своей модели, задайте allowed deployers и approvals. Критерий: scan создаёт CycloneDX SBOM, signature проверена, в Deployment записан тот же IMAGE_REF, rollout и verify успешны.

10

10. Диагностика: deploy job зелёный, rollout красный

Сначала различите ошибку доступа Agent, admission rejection, apply conflict и отказ workload. Соберите контекст и состояние до rollback.

terminal
kubectl config current-context
kubectl auth can-i patch deployments -n course-staging
kubectl get deployment course-web -n course-staging -o yaml
kubectl rollout status deployment/course-web -n course-staging --timeout=60s
kubectl get replicaset,pod -n course-staging -o wide
kubectl events -n course-staging --types=Warning
kubectl logs -n course-staging -l app=course-web --all-containers --tail=100 --prefix
kubectl rollout history deployment/course-web -n course-staging
kubectl rollout undo deployment/course-web -n course-staging
kubectl rollout status deployment/course-web -n course-staging --timeout=180s

Автоматический rollback уместен только при надёжном health signal. При database migration старый image может быть несовместим со схемой: используйте expand/contract и отдельный tested rollback plan.

11

11. Контроль и шпаргалка

  • Почему нельзя пересобирать image для production?
  • Чем tag отличается от digest?
  • Что доказывают SBOM, scan и signature?
  • Зачем environment-scoped KUBE_CONTEXT?
  • Что сериализует resource_group?
  • Почему GitOps безопаснее push-based production deploy?
  • Когда rollout undo может ухудшить инцидент?

Модуль освоен, если pipeline переносит один digest через проверки, использует Agent без cluster-admin credentials, сохраняет evidence и имеет проверяемый promotion/rollback процесс.

PDF для работы офлайн

Скачайте оформленную версию модуля для чтения без подключения к сети.

Скачать PDFPDF