Подготовка инфраструктуры

Требования

  • GitLab;

  • Kubernetes для запуска GitLab Kubernetes Executor;

  • Argo CD.

Установка GitLab Runner

Следуйте официальным инструкциям для установки и регистрации GitLab Runner. Если вы собираетесь устанавливать ваш GitLab Runner в Kubernetes, то установите его в namespace gitlab-ci.

Базовая настройка GitLab Runner (без кеширования)

Добавьте следующее в конфигурационный файл GitLab Runner config.toml:

[[runners]]
  environment = ["FF_USE_ADVANCED_POD_SPEC_CONFIGURATION=true"]

  [runners.kubernetes]
    namespace = "gitlab-ci"

    [runners.kubernetes.pod_annotations]
      "container.apparmor.security.beta.kubernetes.io/build" = "unconfined"

    [runners.kubernetes.pod_security_context]
      run_as_non_root = true
      run_as_user = 1000
      run_as_group = 1000
      fs_group = 1000

    [[runners.kubernetes.volumes.empty_dir]]
      name = "gitlab-ci-kubernetes-executor-werf-cache"
      mount_path = "/home/build/.werf"

    [[runners.kubernetes.volumes.empty_dir]]
      name = "gitlab-ci-kubernetes-executor-builds-cache"
      mount_path = "/builds"

    [[runners.kubernetes.volumes.empty_dir]]
      name = "gitlab-ci-kubernetes-executor-helper-home"
      mount_path = "/home/helper"

    [[runners.kubernetes.pod_spec]]
      name = "fix helper HOME"
      patch = '''
        containers:
        - name: helper
          env:
          - name: HOME
            value: /home/helper
      '''
      patch_type = "strategic"

Базовая настройка Gitlab Runner (с кешированием в Persistent Volumes)

Добавьте следующее в конфигурационный файл GitLab Runner config.toml:

[[runners]]
  environment = ["FF_USE_ADVANCED_POD_SPEC_CONFIGURATION=true"]

  [runners.kubernetes]
    namespace = "gitlab-ci"

    [runners.kubernetes.pod_annotations]
      "container.apparmor.security.beta.kubernetes.io/build" = "unconfined"

    [runners.kubernetes.pod_security_context]
      run_as_non_root = true
      run_as_user = 1000
      run_as_group = 1000
      fs_group = 1000

    [[runners.kubernetes.volumes.pvc]]
      name = "gitlab-ci-kubernetes-executor-werf-cache"
      mount_path = "/home/build/.werf"

    [[runners.kubernetes.volumes.pvc]]
      name = "gitlab-ci-kubernetes-executor-builds-cache"
      mount_path = "/builds"

    [[runners.kubernetes.volumes.pvc]]
      name = "gitlab-ci-kubernetes-executor-helper-home"
      mount_path = "/home/helper"

    [[runners.kubernetes.pod_spec]]
      name = "fix helper HOME"
      patch = '''
        containers:
        - name: helper
          env:
          - name: HOME
            value: /home/helper
      '''
      patch_type = "strategic"

    [[runners.kubernetes.pod_spec]]
      name = "fix volumes permissions"
      patch = '''
        initContainers:
        - name: fix-volumes-permissions
          image: alpine
          command:
          - sh
          - -ec
          - |
            chown :$(id -g) /home/build/.werf /builds /home/helper
            chmod g+rwx /home/build/.werf /builds /home/helper
          securityContext:
            runAsUser: 0
            runAsNonRoot: false
          volumeMounts:
          - mountPath: /home/build/.werf
            name: gitlab-ci-kubernetes-executor-werf-cache
          - mountPath: /builds
            name: gitlab-ci-kubernetes-executor-builds-cache
          - mountPath: /home/helper
            name: gitlab-ci-kubernetes-executor-helper-home
      '''
      patch_type = "strategic"

Создайте PVC:

kubectl create -f - <<EOF
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gitlab-ci-kubernetes-executor-werf-cache
  namespace: gitlab-ci
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gitlab-ci-kubernetes-executor-builds-cache
  namespace: gitlab-ci
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 30Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gitlab-ci-kubernetes-executor-helper-home
  namespace: gitlab-ci
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 30Gi
EOF

Настройте доступ к Kubernetes из GitLab Executor Pod’ов

werf будет запускаться в GitLab Executor Pod’ах. Скорее всего вы будете деплоить с werf в тот же кластер, в котором запускаются GitLab Executor Pod’ы. Если так, то вам нужно настроить отдельные ServiceAccount и ClusterRoleBinding.

Создайте ServiceAccount и ClusterRoleBinding:

kubectl create -f - <<EOF
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitlab-ci-kubernetes-executor
  namespace: gitlab-ci
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: gitlab-ci-kubernetes-executor
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: gitlab-ci-kubernetes-executor
    namespace: gitlab-ci
EOF

Для большей безопасности подумайте над использованием более ограниченной в правах ClusterRole/Role и используйте её вместо cluster-admin ClusterRole выше.

Теперь добавьте эту строку в конфигурационный файл GitLab Runner config.toml:

[[runners]]
  [runners.kubernetes]
    service_account = "gitlab-ci-kubernetes-executor"

Установка Argo CD Image Updater

Установите Argo CD Image Updater с патчем “continuous deployment of OCI Helm chart type application”:

kubectl apply -n argocd -f https://raw.githubusercontent.com/werf/3p-argocd-image-updater/master/manifests/install.yaml

Разрешите использование FUSE (для Kubernetes Nodes с ядром Linux старее, чем 5.13)

Если Kubernetes Nodes, на которых вы будете запускать Kubernetes Executor Pods, имеют версию ядра Linux старее 5.13, то вам нужно разрешить использование FUSE:

kubectl create -f - <<EOF
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fuse-device-plugin
  namespace: kube-system
spec:
  selector:
    matchLabels:
      name: fuse-device-plugin
  template:
    metadata:
      labels:
        name: fuse-device-plugin
    spec:
      hostNetwork: true
      containers:
      - image: soolaugust/fuse-device-plugin:v1.0
        name: fuse-device-plugin-ctr
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
        volumeMounts:
          - name: device-plugin
            mountPath: /var/lib/kubelet/device-plugins
      volumes:
        - name: device-plugin
          hostPath:
            path: /var/lib/kubelet/device-plugins
---
apiVersion: v1
kind: LimitRange
metadata:
  name: enable-fuse
  namespace: gitlab-ci
spec:
  limits:
  - type: "Container"
    default:
      github.com/fuse: 1
EOF

Настройка Kubernetes для мульти-платформенных сборок (опционально)

Данный шаг требуется только для сборки образов для платформ, отличных от платформы системы, где запущен werf.

Активируйте эмуляторы для ваших Kubernetes Nodes, используя qemu-user-static:

kubectl create -f - <<EOF
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: qemu-user-static
  namespace: gitlab-ci
  labels:
    app: qemu-user-static
spec:
  selector:
    matchLabels:
      name: qemu-user-static
  template:
    metadata:
      labels:
        name: qemu-user-static
    spec:
      initContainers:
        - name: qemu-user-static
          image: multiarch/qemu-user-static
          args: ["--reset", "-p", "yes"]
          securityContext:
            privileged: true
      containers:
        - name: pause
          image: gcr.io/google_containers/pause
          resources:
            limits:
              cpu: 50m
              memory: 50Mi
            requests:
              cpu: 50m
              memory: 50Mi
EOF

Конфигурация container registry

Включите сборщик мусора вашего container registry.

Настройка проекта

Настройка приложения Argo CD

  1. В целевом кластере примените следующий Application CRD, чтобы развернуть бандл из container registry:
kubectl create -f - <<EOF
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  annotations:
    argocd-image-updater.argoproj.io/chart-version: ~ 1.0
    argocd-image-updater.argoproj.io/pull-secret: pullsecret:myproject-production/myproject-regcred
  name: myproject
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: myproject-production
    server: https://kubernetes.default.svc
  project: default
  source:
    chart: myproject
    repoURL: registry.mycompany.org/myproject
    targetRevision: 1.0.0
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
EOF

Значение argocd-image-updater.argoproj.io/chart-version="~ 1.0" означает, что оператор должен автоматически развернуть чарт с последней версией патча в диапазоне SEMVER 1.0.*.

  1. Создайте Secret для доступа к container registry проекта:
kubectl create -f - <<EOF
---
apiVersion: v1
kind: Secret
metadata:
  name: myproject-regcred
  namespace: myproject-production
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: BASE64_DOCKER_CONFIG_JSON
EOF

Настройка GitLab-проекта

  • Создайте и сохраните токен для очистки ненужных образов из container registry. Используйте следующие параметры:

    • Token name: werf-images-cleanup;

    • Role: developer;

    • Scopes: api.

  • Добавьте следующие переменные в переменные проекта:

    • Токен доступа для очистки ненужных образов:

      • Key: WERF_IMAGES_CLEANUP_PASSWORD;

      • Value: <"werf-images-cleanup" токен, который вы сохранили ранее>;

      • Protect variable: yes;

      • Mask variable: yes.

  • Добавьте ночную задачу по расписанию для очистки ненужных образов в container registry, установив ветку main/master в качестве целевой (Target branch).

Настройка CI/CD проекта

Вот как может выглядеть репозиторий, использующий werf для сборки и развертывания:

.helm
app
.gitlab-ci.yml
werf.yaml
stages:
  - release
  - cleanup

default:
  image:
    name: registry.werf.io/werf/werf:2-stable
    pull_policy: always
  before_script:
    - source $(werf ci-env gitlab --as-file)
  tags: ["<GitLab Runner tag>"]

Build and publish release:
  stage: release
  script:
    - werf bundle publish --tag "1.0.${CI_PIPELINE_ID}"
  only:
    - main
  except:
    - schedules

Cleanup registry:
  stage: cleanup
  script:
    - werf cr login $WERF_REPO
    - werf cleanup
  only:
    - schedules

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
      - name: app
        image: {{ .Values.werf.image.app }}

apiVersion: v1
kind: Service
metadata:
  name: app
spec:
  selector:
    app: app
  ports:
  - name: app
    port: 80

FROM node

WORKDIR /app
COPY . .
RUN npm ci

CMD ["node", "server.js"]

{
  "name": "app",
  "version": "1.0.0",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {
    "": {
      "name": "app",
      "version": "1.0.0"
    }
  }
}

{
  "name": "app",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  }
}

const http = require('http');

const hostname = '127.0.0.1';
const port = 80;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

configVersion: 1
project: myproject
---
image: app
dockerfile: Dockerfile
context: ./app

Дополнительно:

  • Добавьте параметры авторизации для werf cleanup в container registry, следуя инструкции.