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

Требования

  • CI-система;

  • Kubernetes для запуска CI-задач с установленным Kubernetes Runner вашей CI-системы.

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

Настройте раннер вашей CI-системы так, чтобы создаваемые им Pod’ы имели следующую конфигурацию:

apiVersion: v1
kind: Pod
metadata:
  namespace: ci
  annotations:
    "container.apparmor.security.beta.kubernetes.io/build": unconfined
spec:
  containers:
  - volumeMounts:
    - name: werf-cache
      mountPath: /home/build/.werf
  volumes:
  - name: werf-cache
    emptyDir: {}
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000

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

Настройте раннер вашей CI-системы так, чтобы создаваемые им Pod’ы имели следующую конфигурацию:

apiVersion: v1
kind: Pod
metadata:
  namespace: ci
  annotations:
    "container.apparmor.security.beta.kubernetes.io/build": unconfined
spec:
  initContainers:
  - name: fix-volumes-permissions
    image: alpine
    command:
    - sh
    - -ec
    - |
      chown :$(id -g) /home/build/.werf
      chmod g+rwx /home/build/.werf
    securityContext:
      runAsUser: 0
      runAsNonRoot: false
    volumeMounts:
    - mountPath: /home/build/.werf
      name: werf-cache
  containers:
  - volumeMounts:
    - name: werf-cache
      mountPath: /home/build/.werf
  volumes:
  - name: werf-cache
    persistentVolumeClaim:
      claimName: ci-kubernetes-runner-werf-cache
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000

Создайте PVC:

kubectl create -f - <<EOF
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ci-kubernetes-runner-werf-cache
  namespace: ci
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
EOF

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

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

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

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

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

Теперь добавьте эту строку в Pod, создаваемый вашим раннером:

spec:
  serviceAccountName: ci-kubernetes-runner

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

Если Kubernetes Nodes, на которых вы будете запускать Runner 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: 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: 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.

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

Конфигурация CI/CD проекта

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

.helm
app
pseudo-ci-cd-config.yaml
werf.yaml
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}/`);
});

image: "registry.werf.io/werf/werf:2-stable"
image_pull_policy: always

environment_variables:
  WERF_REPO: registry.example.org/myrepo
  WERF_ENV: "${CI_ENVIRONMENT}"
  WERF_ENABLE_PROCESS_EXTERMINATOR: "1"

before_every_job:
  - werf cr login -u "${REGISTRY_USER:?}" -p "${REGISTRY_PASSWORD:?}" "${WERF_REPO:?}"

jobs:
  prod:
    commands:
      - werf converge
    environment: prod
    on: master
    how: manually

  images:cleanup:
    commands:
      - werf cleanup
    on: master
    how: daily

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

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

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