← До програми курсу

Модуль 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