Ускорение создания образов контейнеров Kubernetes
в Tekton Pipelines

Рассказываем, как использовать возможности кэширования Kaniko для ускорения сборок в Tekton Pipelines.
Когда команда оценивает переход от постоянного CI-сервера (или CI-сервера с постоянными воркерами) к системе с использованием непостоянных воркеров, поддерживаемых Kubernetes-подами, таких как Tekton Pipelines, есть смысл ускорить сборку контейнерных образов с использованием кэшируемых слоев.

Использование кэшируемых слоев достаточно просто, если сборки всегда выполняются на одном и том же сервере, где слои могут храниться локально. Однако в среде, где сборки выполняются в непостоянных контейнерах на Kubernetes, это становится более сложной задачей.

Tekton Pipelines

Tekton Pipelines — это мощный и гибкий, cloud-native фреймворк для CI/CD систем. Он позволяет создавать, тестировать и развертывать приложения в облачных и локальных системах.


Tekton Pipelines предоставляет основные строительные блоки, в виде CRD Kubernetes, для создания CI/CD пайплайнов, задачи которых выполняются в виде подов, управляемых и оркестрируемых Kubernetes.


Основные блоки Tekton:


  • Step: это спецификация контейнера Kubernetes включает образ контейнера и всю необходимую информацию для его выполнения, например, команды или переменные окружения. Определяет шаг пайплайна, как "mvn package" или "docker build", и задается в пользовательском ресурсе Kubernetes Task.
  • Task: пользовательский ресурс Kubernetes, который определяет последовательность шагов (Steps), выполняемых последовательно в одном поде на одном узле Kubernetes.
  • Pipeline: группа задач (Tasks), которую можно настроить для выполнения параллельно или последовательно, она представляет ваш CI/CD пайплайн.
  • TaskRun: конкретный экземпляр задачи (Task), связанный с параметрами, относящимися к определенной среде или приложению (например, репозиторий git, контейнерный образ для сборки или среда для развертывания).
  • PipelineRun: конкретный экземпляр пайплайна (Pipeline), связывающий его с параметрами.

Ускорение сборки контейнерных образов в Tekton Pipelines

У сборки в виде контейнеров есть множество преимуществ: портативность, повторное использование и возможность подключить все возможности Kubernetes. Однако, поскольку эти контейнеры не являются постоянными, для оптимизации выполнения сборки через кэширование потребуется выполнение дополнительных действий по сравнению с тем, к чему вы могли привыкнуть на автономном CI-сервере.


В следующих разделах приведем примеры использования кэширования для ускорения сборки образов в пайплайнах Tekton с использованием Kaniko. Kaniko — это инструмент с открытым исходным кодом, который помогает создавать контейнерные образы из Dockerfile. Ему не требуется демон Docker для выполнения, что делает его идеальным для сборки образов в средах, где Docker демоны недоступны или не могут быть запущены, например, в кластерах Kubernetes.


Чтобы выполнить инструкции, вам понадобится:

  • кластер Kubernetes с установленными Tekton Pipelines
  • система управления исходным кодом (SCM) на основе git
  • реестр контейнеров с разрешениями на отправку артефактов

В этом примере используются кластер GKE на Google Cloud, репозиторий GitHub и Artifact Registry, но шаги воспроизводимы на любой стандартной платформе Kubernetes, SCM на основе git и реестре контейнеров с минимальными модификациями.


Пример кода и ресурсов доступен в этом репозитории: https://github.com/ggalloro/tekton-speed-builds


Чтобы следовать за примерами, вам нужно сделать форк этого репозитория в вашем личном аккаунте GitHub или перенести/скопировать его в вашу SCM, если она отличается.

Эксперимент по умолчанию

После того как вы скопировали репозиторий, клонируйте его локально и посмотрите его содержимое.


  • Папка sample-app содержит исходный код, файлы проекта Maven и Dockerfile для примера приложения Spring Boot на Java. Обратите внимание на Dockerfile: он использует многоступенчатую сборку с использованием базового образа maven для упаковки вашего кода и образа eclipse-temurin:19-jdk-alpine для выполнения приложения.
FROM maven as build
COPY mvnw .
COPY pom.xml .
COPY src src
RUN mvn package -Dmaven.test.skip=true

FROM eclipse-temurin:19-jdk-alpine
COPY --from=build /target/demo-0.0.1-SNAPSHOT.jar app.jar
CMD ["java", "-jar", "app.jar"]

Папка tekton-assets содержит манифесты ресурсов для Tekton, используемые в этой статье:


  • pipeline.yaml — определяет пайплайн, выполняющий 2 задачи:
  1. git-clone.yaml — клонирует репозиторий в рабочее пространство Tekton.
  2. image-build.yaml — использует Kaniko для сборки контейнерного образа из исходного кода, клонированного в рабочее пространство, и отправляет его в целевой реестр контейнеров.
  • source-pvc.yaml — манифест для Persistent Volume Claim (PVC), который будем использовать как постоянное хранилище для рабочего пространства Tekton. Класс хранения специально не определён для обеспечения переносимости; по умолчанию будет использоваться то, что настроено в кластере. Вы можете изменить манифест для использования предпочтительного класса хранилища.
  • Папки kaniko-basecache, kaniko-cache и m2cache содержат примеры ресурсов, полезных для реализации различных опций кэширования.

Теперь создадим необходимые ресурсы Tekton и запустим пайплайн. Из локально клонированного репозитория примените ресурсы из папки tekton-assets к вашему кластеру:

kubectl apply -f tekton-assets

Это создаст PVC, задачи и пайплайн, описанные выше.


Теперь запустим пайплайн вручную. В зависимости от вашей конфигурации, возможно, вам потребуется указать учетную запись службы (Service Account) для аутентификации в реестре, используя параметр -s. Подробнее смотрите в разделе документации Tekton.


Введите следующую команду для запуска пайплайна.

tkn pipeline start pipeline-clone-and-build --workspace name=source,claimName=source-pvc --showlog

В ? Value for param git-url of type string? введите URL вашего репозитория git.


В ? Value for param image-name of type string? введите URL вашего образа, включая имя репозитория и имя образа.


Пример конфигурации:

➜  ~ tkn pipeline start pipeline-clone-and-build --workspace name=source,claimName=source-pvc --showlog
? Value for param `git-url` of type `string`? https://github.com/ggalloro/tekton-speed-builds
? Value for param `image-name` of type `string`? europe-west1-docker.pkg.dev/galloro-demos/tektoncd/javasample

Если выполнение пайплайна прошло успешно, последний шаг задачи image-build выведет URL вашего образа в консоль, и ваш образ будет отправлен в целевой реестр:

[image-build : write-url]
europe-west1-docker.pkg.dev/galloro-demos/tektoncd/javasample:457848d

Выполнение пайплайна создало 2 ресурса TaskRun для выполнения двух задач. Введите следующую команду, чтобы увидеть информацию о TaskRun:

tkn tr ls

Вы увидите что-то подобное:

NAME                                             STARTED          DURATION   STATUS
pipeline-clone-and-build-run-68hjg-image-build   4 minutes ago    50s        Succeeded
pipeline-clone-and-build-run-68hjg-git-clone     4 minutes ago    11s        Succeeded

Так как ваша сборка всегда выполняется в новом контейнере, если вы измените только одну строку в исходном коде, сборка будет загружать базовые образы и зависимости Maven, не используя кэш. Попробуем.


Измените текст на 25-й строке файла index.html в sample-app/src/main/resources/templates на «Hello, Slow Builder!».


Зафиксируйте изменения и отправьте их в удалённый репозиторий.

git add .
git commit -m "change to index.html"
git push

Запустите ваш пайплайн снова, следуя тем же инструкциям, которые были даны ранее.


После завершения выполнения пайплайна посмотрите информацию о TaskRuns снова:

NAME                                             STARTED          DURATION   STATUS
pipeline-clone-and-build-run-fnzxj-image-build   55 seconds ago   49s        Succeeded
pipeline-clone-and-build-run-fnzxj-git-clone     1 minute ago     12s        Succeeded
pipeline-clone-and-build-run-68hjg-image-build   4 minutes ago    50s        Succeeded
pipeline-clone-and-build-run-68hjg-git-clone     4 minutes ago    11s        Succeeded

Как видите, даже если вы измените только одну строку кода, сборка займет примерно столько же времени, как и до этого.


В следующих разделах мы рассмотрим возможности оптимизации скорости сборки с помощью кэширования.

Кэширование слоев в реестре контейнеров

Kaniko может кэшировать слои, создаваемые командами RUN и COPY в удалённом репозитории. Перед выполнением команды Kaniko проверяет кэш на наличие слоя. Если слой существует в кэше, Kaniko извлечёт его вместо выполнения команды. Если слоя нет в кэше, Kaniko выполнит команду, а затем отправит вновь созданный слой в кэш.


Это может помочь ускорить сборку в Tekton.


Чтобы использовать кэш Kaniko, необходимо добавить флаг --cache=true к Kaniko в задаче image-build, чтобы шаг build-and-push выглядел следующим образом:

- name: build-and-push
      workingDir: $(workspaces.source.path)/sample-app/
      image: gcr.io/kaniko-project/executor:latest
      args:
        - --dockerfile=$(params.DOCKERFILE)
        - --context=$(params.CONTEXT) # The user does not need to care the workspace and the source.
        - --destination=$(params.IMAGE):$(params.commit)
        - --digest-file=$(results.IMAGE_DIGEST.path)
        - --cache=true
      securityContext:
        runAsUser: 0

Вы можете применить изменённый image-build-cache.yaml из папки kaniko-cache, чтобы обновить задачу image-build:

kubectl apply -f kaniko-cache/image-build-cache.yaml

Чтобы использовать возможности кэширования слоев Kaniko, также следует правильно настроить ваш Dockerfile, переместив инструкции, которые, скорее всего, не изменятся между сборками, наверх, а те, которые изменятся, вниз.


Отредактируйте Dockerfile в директории sample-app, чтобы разделить скачивание зависимостей Maven и сборку кода. Это позволит кэшировать зависимости в отдельном слое, чтобы любые изменения в коде не вызывали повторную загрузку зависимостей. Добавьте строку RUN mvn dependency

в ваш Dockerfile, чтобы он выглядел так:

FROM maven as build
COPY mvnw .
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src src
RUN mvn package -Dmaven.test.skip=true


FROM eclipse-temurin:19-jdk-alpine
COPY --from=build /target/demo-0.0.1-SNAPSHOT.jar app.jar
CMD ["java", "-jar", "app.jar"]

После изменения Dockerfile вам нужно зафиксировать и отправить изменения, чтобы ваша сборка использовала их:

git add .
git commit -m "update to Dockerfile"
git push

Теперь снова запустите ваш пайплайн, как вы это делали ранее. Результат первого выполнения должен быть похож на предыдущий, и все зависимости будут загружены.


Посмотрите информацию о TaskRun. Продолжительность сборки должна быть примерно такой же, как и раньше (хотя она может быть немного длиннее из-за загрузки слоев в реестр):

NAME                                             STARTED          DURATION   STATUS
pipeline-clone-and-build-run-gsgwx-image-build   1 minute ago     58s        Succeeded
pipeline-clone-and-build-run-gsgwx-git-clone     1 minute ago     17s        Succeeded
pipeline-clone-and-build-run-fnzxj-image-build   55 seconds ago   49s        Succeeded
pipeline-clone-and-build-run-fnzxj-git-clone     1 minute ago     12s        Succeeded
pipeline-clone-and-build-run-68hjg-image-build   4 minutes ago    50s        Succeeded
pipeline-clone-and-build-run-68hjg-git-clone     4 minutes ago    11s        Succeeded

Слои образа были загружены в ваш реестр, как показано на картинке ниже для Artifact Registry (папка javasample/cache):

Теперь обновим код (используйте «Hello, Fast Builder!» в этот раз), зафиксируйте изменения и снова запустите пайплайн, следуя инструкциям из предыдущего раздела. В журнале вывода вы должны заметить, что основная часть зависимостей не будет загружаться заново, а будет использован кэшированный слой:

Выполнение задачи image-build TaskRun должно быть быстрее по сравнению с предыдущим выполнением (42 секунды против 58 секунд в этом примере):

NAME                                             STARTED         DURATION   STATUS
pipeline-clone-and-build-run-5s87p-image-build   1 minute ago     42s        Succeeded
pipeline-clone-and-build-run-5s87p-git-clone     1 minute ago     17s        Succeeded
pipeline-clone-and-build-run-gsgwx-image-build   7 minutes ago    58s        Succeeded
pipeline-clone-and-build-run-gsgwx-git-clone     7 minutes ago    17s        Succeeded
pipeline-clone-and-build-run-fnzxj-image-build   18 minutes ago   49s        Succeeded
pipeline-clone-and-build-run-fnzxj-git-clone     18 minutes ago   12s        Succeeded
pipeline-clone-and-build-run-68hjg-image-build   21 minutes ago   50s        Succeeded
pipeline-clone-and-build-run-68hjg-git-clone     22 minutes ago   11s        Succeeded

Вы увидели, как включение кэширования слоев в реестре контейнеров может ускорить вашу сборку в Tekton. Эффективный прирост скорости будет зависеть от различных факторов, таких как конфигурация проекта и подключение к вашему реестру.

Кэширование базовых образов на постоянном диске

Помимо кэширования слоев в реестре контейнеров, Kaniko может использовать постоянный том для хранения кэшированных базовых образов. По умолчанию, Kaniko будет искать кэшированные базовые образы в папке /cache, но вы можете изменить это с помощью флага --cache-dir.


Постоянный том должен быть заполнен до использования. Kaniko предоставляет образ для этого: gcr.io/kaniko-project/warmer.


Добавим монтирование тома, сопоставляющего путь /cache с PVC под названием basecache-pvc, в задачу build-image:

- name: build-and-push
      workingDir: $(workspaces.source.path)/sample-app/
      image: gcr.io/kaniko-project/executor:latest
      args:
        - --dockerfile=$(params.DOCKERFILE)
        - --context=$(params.CONTEXT) # The user does not need to care the workspace and the source.
        - --destination=$(params.IMAGE):$(params.commit)
        - --digest-file=$(results.IMAGE_DIGEST.path)
        - --cache=true
      # kaniko assumes it is running as root, which means this example fails on platforms
      # that default to run containers as random uid (like OpenShift). Adding this securityContext
      # makes it explicit that it needs to run as root.
      securityContext:
        runAsUser: 0
      volumeMounts:
        - name: basecache
          mountPath: /cache
    - name: write-url
      image: docker.io/library/bash:5.1.4@sha256:b208215a4655538be652b2769d82e576bc4d0a2bb132144c060efc5be8c3f5d6
      script: |
        set -e
        image="$(params.IMAGE):$(params.commit)"
        echo -n "${image}" | tee "$(results.IMAGE_URL.path)"
  volumes:
    - name: basecache
      persistentVolumeClaim:
        claimName: basecache-pvc

Вы можете применить изменённый image-build-basecache.yaml из папки kaniko-basecache, чтобы обновить задачу image-build.

kubectl apply -f kaniko-basecache/image-build-basecache.yaml

Теперь создадим basecache-pvc PVC:

kubectl apply -f kaniko-basecache/pvc-basecache.yaml

Затем запустим pod kaniko-warmer, чтобы заполнить диск:

kubectl apply -f kaniko-basecache/kaniko-warmer.yaml

Проверьте логи pod'а kaniko-warmer:

kubectl logs -f kaniko-warmer

Пока вы не увидите вывод с подтверждением того, что 2 базовых образа были загружены.

INFO[0000] Retrieving image manifest maven              
INFO[0000] Retrieving image maven from registry index.docker.io 
INFO[0004] Retrieving image manifest eclipse-temurin:19-jdk-alpine 
INFO[0004] Retrieving image eclipse-temurin:19-jdk-alpine from registry index.docker.io

Когда это будет сделано, вы можете снова внести изменения в свой код («Hello, Base Builder!»), зафиксировать его и запустить ваш пайплайн снова, как это было сделано в предыдущих тестах.


Эта операция будет использовать кэшированные слои, как и предыдущее, а также базовые образы из вашего постоянного тома basecache-pvc, что видно в выводе:

И в этом случае улучшение скорости будет зависеть от различных факторов, таких как размер базовых образов, производительность хранилища и подключение к вашему реестру.

Кэширование зависимостей на постоянном диске

Другой способ ускорить сборки — кэшировать зависимости локально. В нашем примере проекта на Maven мы можем использовать тот факт, что по умолчанию Maven кэширует модули зависимостей в директорию $HOME/.m2. Это зависит от используемого языка и инструментов сборки.


В данном примере мы добавим еще один постоянный том к задаче build-image и сопоставим его с папкой /root/.m2, чтобы сохранить кэш Maven между сборками.


Применим изменённый image-build-m2cache.yaml из папки m2cache для обновления задачи image-build:

kubectl apply -f m2cache/image-build-m2cache.yaml

Теперь создадим PVC m2cache-pvc:

kubectl apply -f m2cache/pvc-m2cache.yaml

Так как нам больше не нужно кэшировать зависимости Maven в слое образа, удалим строку RUN mvn dependency из нашего Dockerfile и вернём его к исходной структуре. Зафиксируйте и отправьте ваши изменения.


Теперь снова запустите ваш пайплайн, как делали это ранее. Maven снова загрузит ваши зависимости, когда будет запущен mvn package, поскольку ранее кэшированный слой больше не упоминается в вашем Dockerfile. Продолжительность выполнения должна быть похожа на наш первоначальный тест.


Снова внесите изменения в ваш код («Hello, Very Fast Builder»), зафиксируйте их и снова запустите пайплайн, как это было сделано в предыдущих тестах.


Этот запуск будет использовать кэшированные слои, как и предыдущий, а также базовые образы из вашего постоянного тома basecache-pvc и кэшированные зависимости.


В списке TaskRun вы должны увидеть улучшение скорости по сравнению с первым запуском.


Готово!

NAME                                             STARTED          DURATION   STATUS
pipeline-clone-and-build-run-lk8x2-image-build   1 minute ago     46s        Succeeded
pipeline-clone-and-build-run-lk8x2-git-clone     1 minute ago     14s        Succeeded
pipeline-clone-and-build-run-w7sd5-image-build   3 minutes ago    59s        Succeeded
pipeline-clone-and-build-run-w7sd5-git-clone     3 minutes ago    12s        Succeeded

Получите бесплатную консультацию по DevOps-поддержке


Узнайте об услуге devops support и закажите звонок у наших менеджеров. Поможем с любой задачей


Узнать больше