![](https://habrastorage.org/getpro/habr/upload_files/4c2/443/b6a/4c2443b6aecbb50cf20d6e6cc273b504.jpg)
В этой статье мы рассмотрим, как более-менее прилично организовать процесс тестирования и публикации чартов, встреченные при этом подводные камни, а также рассмотрим пару великолепных инструментов, которые совершенно незаслуженно получили крайне мало внимания не только на Хабре, но и вообще в русскоязычном сегменте интернета.
Очень кстати, в недавно вышедшем релизе Gitlab 14.1, появился долгожданный функционал хранения Helm-чартов во встроенном Package Registry. Отлично, заодно и разберемся, как его использовать.
Используемые инструменты
KinD
Первый инструмент, о котором пойдет речь, буквально kubernetes-in-docker, позволяет запустить практически полноценный кластер локально на нодах-контейнерах. Под капотом использует kubeadm для настройки узлов и kustomize для слияния предоставленного конфига и сгенерированной внутри конфигурации. Полную документацию можно найти тут. Также по желанию можно установить не только ingress-nginx, манифесты которого для KinD можно найти в его репозитории, но и Ambassador или Contour.
![Что примечательно, схема с оригинального сайта не совсем верна, внутри контейнера не используется dockershim, а, в соответствие последним тенденциям, kubelet общается с containerd напрямую через сокет. Что примечательно, схема с оригинального сайта не совсем верна, внутри контейнера не используется dockershim, а, в соответствие последним тенденциям, kubelet общается с containerd напрямую через сокет.](https://habrastorage.org/getpro/habr/upload_files/438/59b/e9f/43859be9f484d1f716f49d1e31e5f7e0.png)
Еще одним моментом будет отсутствие поддержки сервисов типа LoadBalancer. Поэтому до развернутого приложения нужно ходить по INTERNAL-IP ноды, узнать который можно с помощью kubectl или docker inspect, например:
kubectl get no -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-test-control-plane Ready master 36h v1.19.11 172.18.1.2 <none> Ubuntu 21.04 5.4.0-80-generic containerd://1.5.2
k8s-test-worker Ready compute 36h v1.19.11 172.18.1.3 <none> Ubuntu 21.04 5.4.0-80-generic containerd://1.5.2
Запущенные контейнеры-ноды помечаются лейбами io.x-k8s.kind.cluster
и io.x-k8s.kind.role
, в дальнейшем нам это пригодится.
Сейчас и далее предполагается, что локально уже установлен Docker, описывать в этой статье инструкцию его установки сочту излишним. Аналогично поступим и с Helm.
Устанавливаем kind в систему:
curl -Lo ./kind "https://kind.sigs.k8s.io/dl/v0.11.1/kind-$(uname)-amd64"
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
Chart Testing
Второй инструмент - chart-testing, репо и документацию которого можно найти на GitHub. В паре с chart-releaser широко применяется для организации репо Helm-чартов в Github-pages. Оба имеют соответствующие экшены: chart-testing-action и chart-releaser-action.
Инструмент позволит упростить автоматизацию тестирования наших чартов, причем отберет из них только измененные по сравнению с master/main веткой origin. Будучи добавлен в pre-commit хук, не позволит забыть увеличить версию Helm-чарта. Что он нам поможет делать:
Прогон линта по нашим свеженаписанным Helm-чартам.
Валидацию Chart.yaml.
Развертывание Helm-чарта в отдельный сгенерированный неймспейс текущего активного кластера kubernetes (не обязательно KinD).
Очистка окружения за собой вне зависимости от успешности развертывания.
Понятно, что все вышеперечисленное можно сделать и с помощью Bash-полотна, python, костыля и пары велосипедов, но мы ведь не за этим сюда пришли, правда? Хотя Bash-полотно все-таки будет.
Установка:
sudo mkdir -p /tmp/ct /etc/ct
curl -sL "https://github.com/helm/chart-testing/releases/download/v3.4.0/chart-testing_3.4.0_linux_amd64.tar.gz" | tar -xvz -C /tmp/ct
sudo mv /tmp/ct/etc/chart_schema.yaml /etc/ct/chart_schema.yaml
sudo mv /tmp/ct/etc/lintconf.yaml /etc/ct/lintconf.yaml
sudo mv /tmp/ct/ct /usr/local/bin/ct
sudo chmod +x /usr/local/bin/ct
rm -rf /tmp/ct
На этом вводное рассмотрение инструментария заканчиваем и переходим к подготовке пайплайна.
Кейс первый: публичный репозиторий и DinD
Создаем и клонируем к себе git-репо, можно использовать и уже существующий проект, но для простоты повествования опишу процесс создания “с нуля”. В корне проекта создаем и переходим в директорию charts
. Создаем наш первый чарт:
mkdir charts
cd charts
helm create test-chart
В Chart.yaml нужно добавить keywords
и maintainers
:
keywords:
- example
maintainers:
- name: <Your Name>
email: <mail@example.com>
Сразу стоит уточнить, что проверка мейнтейнеров проводится по наличию пути https://<git-repo-domain>/<maintainer-name>
. Отключить её можно в следующем конфиге ct.yaml, добавив validate-maintainers: false
.
Зачастую необходимо производить тестирование чарта с какими-либо предопределенными параметрами. Чтобы их передать при раскатке с помощью ct, нужно создать директорию ci
в директории конкретного чарта и в ней разместить *-values.yaml
, например, ./charts/test-chart/ci/test-values.yaml
. Таких файлов может быть несколько, но не указывайте в них пересекающиеся значения, т.к. порядок применения или только некоторые из них выбрать нельзя.
Далее создадим файлы:
ct.yaml
- конфиг chart-testing, позволит минимизировать указание ключей при запуске.
ct.yaml
# See https://github.com/helm/chart-testing#configuration
remote: origin
target-branch: main
chart-dirs:
- charts
helm-extra-args: --timeout=120s
kind-cluster.yaml
- конфиг KinD. Кластер kubernetes будет разворачиваться внутри сервиса DinD (docker-in-docker), соответственно, описываем, что api-сервер должен слушать все интерфейсы, патч ClusterConfiguration, где добавляем хостнейм, с которым будем к нему подключаться, тип и количество нод.
Версия kubernetes выбирается через версию docker-имаджа KinD. Доступные варианты перечислены в release notes на GitHub.
Ноды добавляются просто повторением одной и той же строки, например, при указании двух строк - role: worker
будет создано две воркер-ноды. Таким же образом можно варьировать и количество мастер-нод.
kind-cluster.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
ipFamily: ipv4
apiServerAddress: "0.0.0.0"
kubeadmConfigPatchesJSON6902:
- group: kubeadm.k8s.io
version: v1beta2
kind: ClusterConfiguration
patch: |
- op: add
path: /apiServer/certSANs/-
value: docker
nodes:
- role: control-plane
- role: worker
.gitlab-ci.yml
- описание пайплайна. Документацию по синтаксису можно найти здесь. Публичный GitLab “из коробки” предоставляет облачные раннеры с Docker, нам же потребуется настроить запуск сервиса DinD рядом с контейнером, в котором будет бежать выполнение пайплайна. Также и в сами образы, используемые для запуска джоб, в процессе выполнения нужно будет добавить недостающие бинари и конфиги утилит. Конфиг исключительно для примера, никто не запрещает собрать свой образ со всем необходимым для этого пайплайна.
.gitlab-ci.yml
# Объявляем стадии пайплайна
stages:
- "lint"
- "chart-test"
- "chart-release"
variables:
# Выбираем версию kubectl, соответственно используемой версии kubernetes
CI_KUBECTL_VER: v1.19.0
# Объявляем версии Helm, chart-testing и KinD
CI_HELM_VER: v3.6.3
CI_CT_VER: v3.4.0
CI_KIND_VERSION: v0.11.1
# Часть пути, по которому мы в дальнейшем будем подключать Helm-репо
CI_HELM_CHANNEL: stable
# Название создаваемого кластера kubernetes
CI_KIND_CLUSTER_NAME: k8s-test
# Для удобства переписываем в переменные имаджи нод с дайджестами
# в соответствии с используемой версией KinD
CI_KIND_IMAGE_1_17: 'kindest/node:v1.17.17@sha256:66f1d0d91a88b8a001811e2f1054af60eef3b669a9a74f9b6db871f2f1eeed00'
CI_KIND_IMAGE_1_18: 'kindest/node:v1.18.19@sha256:7af1492e19b3192a79f606e43c35fb741e520d195f96399284515f077b3b622c'
CI_KIND_IMAGE_1_19: 'kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729'
CI_KIND_IMAGE_1_20: 'kindest/node:v1.20.7@sha256:cbeaf907fc78ac97ce7b625e4bf0de16e3ea725daf6b04f930bd14c67c671ff9'
CI_KIND_IMAGE_1_21: 'kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6'
# Указываем, какой образ будем использовать в этом пайплайне
CI_KIND_IMAGE: $CI_KIND_IMAGE_1_19
# Описываем небольшие сниппеты, которые легко переиспользовать в нескольких джобах,
# а также улучшают читабельность кода.
.check-and-unshallow: &check-and-unshallow
- git version
- |
if [ -f "$(git rev-parse --git-dir)/shallow" ]; then
echo "this is a shallow repository";
git fetch --unshallow --prune --prune-tags --verbose
else
echo "not a shallow repository";
git fetch --prune --prune-tags --verbose
fi
- git rev-parse --verify HEAD
- git rev-list HEAD --count
- git rev-list HEAD --count --first-parent
.get-kube-binaries: &get-kube-binaries
- apk add -U wget
- wget -O /usr/local/bin/kind "https://github.com/kubernetes-sigs/kind/releases/download/${CI_KIND_VERSION}/kind-linux-amd64"
- chmod +x /usr/local/bin/kind
- wget -O /usr/local/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/${CI_KUBECTL_VER}/bin/linux/amd64/kubectl"
- chmod +x /usr/local/bin/kubectl
.install-ct: &install-ct
- |
export CT_URL="https://github.com/helm/chart-testing/releases/download/${CI_CT_VER}"
export CT_TAR_FILE="chart-testing_${CI_CT_VER#v}_linux_amd64.tar.gz"
echo "install chart-testing ${CI_CT_VER} from \"${CT_URL}/${CT_TAR_FILE}\""
mkdir -p /tmp/ct /etc/ct
wget -O "/tmp/${CT_TAR_FILE}" "${CT_URL}/${CT_TAR_FILE}"
tar -xzvf "/tmp/${CT_TAR_FILE}" -C /tmp/ct
mv /tmp/ct/etc/chart_schema.yaml /etc/ct/chart_schema.yaml
mv /tmp/ct/etc/lintconf.yaml /etc/ct/lintconf.yaml
mv /tmp/ct/ct /usr/bin/ct
rm -rf /tmp/ct
ct version
.install-helm: &install-helm
- |
export HELM_URL="https://get.helm.sh"
export HELM_TAR_FILE="helm-${CI_HELM_VER}-linux-amd64.tar.gz"
echo "install HELM ${CI_HELM_VER} from \"${HELM_URL}/${HELM_TAR_FILE}\""
mkdir -p /tmp/helm
wget -O "/tmp/${HELM_TAR_FILE}" "${HELM_URL}/${HELM_TAR_FILE}"
tar -xzvf "/tmp/${HELM_TAR_FILE}" -C /tmp/helm
mv /tmp/helm/linux-amd64/helm /usr/bin/helm
rm -rf /tmp/helm
chmod +x /usr/bin/helm
helm version
# Добавляем Package Registry проекта с авторизацией через job-token
# и плагин для push в Helm-репо
.helm-add-project-as-repo: &helm-add-project-as-repo
- >-
helm repo add
--username gitlab-ci-token
--password "${CI_JOB_TOKEN}"
"${CI_PROJECT_NAME}"
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/${CI_HELM_CHANNEL}"
- helm plugin install https://github.com/chartmuseum/helm-push.git
- helm repo list
# Описываем сами джобы
# Линт. Тут просто берем родной имадж chart-testing, ничего дабавлять не требуется
chart-lint:
stage: lint
image: quay.io/helmpack/chart-testing:v3.4.0
tags:
- "docker"
script:
- *check-and-unshallow
- ct lint --config ct.yaml
only:
- pushes
except:
- master
- main
# Здесь добавляем все необходимые бинари, поднимаем KinD
# и разворачиваем в него тестируемые чарты
chart-test:
stage: chart-test
image: docker:20.10-git
variables:
# Use TLS https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#tls-enabled
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
# Сервис DinD нам не нужен в остальных джобах, поэтому укажем его здесь,
# чтобы не замедлять весь пайплайн
services:
- name: docker:20.10-dind
alias: docker
tags:
- "docker"
script:
- *check-and-unshallow
- apk add -U wget
# Добавляем недостающие бинари
- *get-kube-binaries
- *install-ct
- *install-helm
# разворачиваем KinD
- >-
kind create cluster
--name ${CI_KIND_CLUSTER_NAME}
--image ${CI_KIND_IMAGE}
--config=kind-cluster.yaml
--wait 5m
# Правим kubeconfig, чтобы указанный хост api-server соответствовал хосту DinD,
# который, в свою очередь, мы ранее указали в патче ClusterConfiguration
- sed -i -E -e 's/127\.0\.0\.1|0\.0\.0\.0/docker/g' "$HOME/.kube/config"
# Разворачиваем наши чарты
- ct install --config ct.yaml
after_script:
# не обязательно, т.к. сервис все равно будет погашен с завершением джобы
- kind delete cluster --name k8s-test
only:
- pushes
except:
- master
- main
# Упаковываем чарты и публикуем их в Package Registry
chart-release:
stage: chart-release
image: quay.io/helmpack/chart-testing:v3.4.0
tags:
- "docker"
script:
- *check-and-unshallow
- apk add jq yq
- *helm-add-project-as-repo
# используем доработанный скрипт из экшена chart-releaser
- >-
./gitlab-cr.sh
--charts-dir charts
--charts-repo-url "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/${CI_HELM_CHANNEL}"
--repo "${CI_PROJECT_NAME}"
only:
- master
- main
Зачем тут unshallow? Chart-testing просматривает историю гит для поиска измененных чартов, а GitLab по умолчанию делает shallow clone с глубиной 50 коммитов, что дает вероятность его падения, когда он пытается просмотреть больше. Это поведение можно настроить через clone, а не fetch или через git depth, на вкус и цвет, так сказать.
Канал Helm-repo лучше использовать один, например, stable (название на самом деле можно выбрать любое) или же минимальное их разнообразие. Дело в том, что в интерфейсе GitLab определить какой чарт из какого канала можно примерно никак и разнообразие нейминга в этом случае приведет только к увеличению хаоса. В целом работает аналогично Chartmuseum и Helm-repo в GitHub-pages. Получить и распарсить индекс можно по пути "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/${CI_HELM_CHANNEL}/index.yaml"
Также обращу внимание, что поиск изменений происходит при сравнении текущего HEAD и указанного в конфиге ct target-branch
в origin. Это еще один повод не пушить в мастер, т.к. в таком случае внутри пайплайна сравнивать будет не с чем.
Представленный пайплайн предполагает разработку в отдельной ветке, там же прогоняется линт и тестирование, затем мердж в мастер, где происходит упаковка и публикация чарта в Package Registry.
gitlab-cr.sh
Скрипт упаковки и публикации чарта мы подсмотрим в оригинальном GitHub экшене. К сожалению, chart-releaser с GitLab пока не работает, поэтому перепишем на использование helm для поиска версии чарта по Helm-repo, а затем упаковки и публикации, если текущей версии найдено не будет:
gitlab-cr.sh
#!/usr/bin/env bash
# Copyright The Helm Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
set -o pipefail
needy_tools=("jq" "yq" "helm")
show_help() {
cat << EOF
Usage: $(basename "$0") <options>
-h, --help Display help
-d, --charts-dir The charts directory (default: charts)
-u, --charts-repo-url The Gitlab helm package registry URL (default: "<CI_API_V4_URL>/projects/<CI_PROJECT_ID>/packages/helm/stable")
-r, --repo The repo name
EOF
}
main() {
local charts_dir=charts
local repo=
local charts_repo_url=
parse_command_line "$@"
for tool in "${needy_tools[@]}"; do
assert_tools "$tool"
done
local repo_root
repo_root=$(git rev-parse --show-toplevel)
pushd "$repo_root" > /dev/null
echo 'Looking up latest tag...'
local latest_tag
latest_tag=$(lookup_latest_tag)
echo "Discovering changed charts since '$latest_tag'..."
local changed_charts=()
readarray -t changed_charts <<< "$(lookup_changed_charts "$latest_tag")"
if [[ -n "${changed_charts[*]}" ]]; then
rm -rf .cr-release-packages
mkdir -p .cr-release-packages
rm -rf .cr-index
mkdir -p .cr-index
for chart in "${changed_charts[@]}"; do
if [[ -d "$chart" ]]; then
package_chart "$chart"
else
echo "Chart '$chart' no longer exists in repo. Skipping it..."
fi
done
release_charts "$repo"
else
echo "Nothing to do. No chart changes detected."
fi
popd > /dev/null
}
parse_command_line() {
while :; do
case "${1:-}" in
-h|--help)
show_help
exit
;;
-d|--charts-dir)
if [[ -n "${2:-}" ]]; then
charts_dir="$2"
shift
else
echo "ERROR: '-d|--charts-dir' cannot be empty." >&2
show_help
exit 1
fi
;;
-u|--charts-repo-url)
if [[ -n "${2:-}" ]]; then
charts_repo_url="$2"
shift
else
echo "ERROR: '-u|--charts-repo-url' cannot be empty." >&2
show_help
exit 1
fi
;;
-r|--repo)
if [[ -n "${2:-}" ]]; then
repo="$2"
shift
else
echo "ERROR: '--repo' cannot be empty." >&2
show_help
exit 1
fi
;;
*)
break
;;
esac
shift
done
if [[ -z "$repo" ]]; then
echo "ERROR: '-r|--repo' is required." >&2
show_help
exit 1
fi
if [[ -z "$charts_repo_url" ]]; then
charts_repo_url="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/stable"
fi
}
lookup_latest_tag() {
git fetch --tags > /dev/null 2>&1
if ! git describe --tags --abbrev=0 2> /dev/null; then
git rev-list --max-parents=0 --first-parent HEAD
fi
}
filter_charts() {
while read -r chart; do
[[ ! -d "$chart" ]] && continue
local file="$chart/Chart.yaml"
if [[ -f "$file" ]]; then
echo "$chart"
else
echo "WARNING: $file is missing, assuming that '$chart' is not a Helm chart. Skipping." 1>&2
fi
done
}
lookup_changed_charts() {
local commit="$1"
local changed_files
changed_files=$(git diff --find-renames --name-only "$commit" -- "$charts_dir")
local depth=$(( $(tr "/" "\n" <<< "$charts_dir" | sed '/^\(\.\)*$/d' | wc -l) + 1 ))
local fields="1-${depth}"
cut -d '/' -f "$fields" <<< "$changed_files" | uniq | filter_charts
}
lookup_chart_in_repo_by_version() {
local chart="$1"
local chart_version
chart_version="$(yq r "${chart}/Chart.yaml" 'version')"
local chart_name
chart_name="$(yq r "${chart}/Chart.yaml" 'name')"
helm search repo "${repo}/${chart_name}" --version "$chart_version" -o json | jq -r '.[].version'
}
package_chart() {
local chart="$1"
local chart_version_in_repo
chart_version_in_repo="$(lookup_chart_in_repo_by_version "$chart")"
local args=("$chart" --destination .cr-release-packages)
if [[ -z "$chart_version_in_repo" ]]; then
echo "Packaging chart '$chart'..."
helm package "${args[@]}"
else
echo "$chart with version $chart_version_in_repo already exist in repo. Skipping..."
fi
}
release_charts() {
local repo="$1"
echo 'Releasing charts...'
for f in .cr-release-packages/*.tgz
do
[ -e "$f" ] || break
helm push "$f" "$repo"
done
}
assert_tools() {
local tool="$1"
command -v "$tool" >/dev/null 2>&1 || {
echo "ERROR: ${tool} is not installed." >&2
exit 1
}
}
main "$@"
Готово. Коммитим, пушим в develop и наблюдаем, как пайплайн становится зеленым, после чего мерджим нашу ветку в master(main) и по завершению пайплайна получаем тарболл чарта в Package Registry.
![](https://habrastorage.org/getpro/habr/upload_files/874/fcc/fc8/874fccfc8bec2daf53f0db0320e5aff8.jpg)
Добавляем Helm-repo (подставьте свой ID проекта):
helm repo add habr-test https://gitlab.com/api/v4/projects/<project_ID>/packages/helm/stable
"habr-test" has been added to your repositories
Проверяем, что тестовый чарт опубликован:
helm search repo habr-test/test-chart
NAME CHART VERSION APP VERSION DESCRIPTION
habr-test/test-chart 0.1.0 1.16.0 A Helm chart for Kubernetes
Кейс второй: раннер с монтированием сокета Docker
Сразу оговорюсь, что монтирование сокета с хоста в контейнеры, создаваемые раннером не лучшее решение в плане безопасности, но пока что необходимо, например, если вы пользуетесь werf. В таком случае запустить DinD будет как минимум не самой тривиальной задачей, т.к. сокет монтируется и в контейнеры сервисов.
Тем не менее задача легко решаема, поскольку контейнеры KinD в этом случае будут подниматься в Docker хоста, где запущен раннер, а интерфейс docker0 доступен изнутри контейнера и практически всегда имеет адрес 172.17.0.1
. Как и в случае с сервисом DinD мы добавим этот адрес через патч ClusterConfiguration в kind-cluster.yaml
:
kubeadmConfigPatchesJSON6902:
- group: kubeadm.k8s.io
version: v1beta2
kind: ClusterConfiguration
patch: |
- op: add
path: /apiServer/certSANs/-
value: 172.17.0.1
И sed’ом по ходу выполнения пайплайна заменим 0.0.0.0
в кубконфиге на адрес интерфейса docker0: sed -i -E -e 's/0\.0\.0\.0/172\.17\.0\.1/g' "$HOME/.kube/config"
Кроме того, нужно позаботиться и об удалении контейнеров KinD в случае отмены задания GitLab, т.к. контейнер задания будет просто убит kill без выполнения after_script
. Подробнее с сутью проблемы можно ознакомиться в этом issue. В этом нам помогут лейбы, которые KinD ставит на запускаемые контейнеры нод. Сперва добавляем CI_JOB_ID в переменную названия кластера, затем, перед запуском кластера, создаем контейнер “мертвой руки”, который после некоторого таймаута будет завершать контейнеры с лейбой io.x-k8s.kind.cluster=job-${CI_JOB_ID}
.
Пример пайплайна для работы с docker0
# Объявляем стадии пайплайна
stages:
- "lint"
- "chart-test"
- "chart-release"
variables:
# Выбираем версию kubectl, соответственно используемой версии kubernetes
CI_KUBECTL_VER: v1.19.0
# Объявляем версии Helm, chart-testing и KinD
CI_HELM_VER: v3.6.3
CI_CT_VER: v3.4.0
CI_KIND_VERSION: v0.11.1
# Часть пути, по которому мы в дальнейшем будем подключать Helm-репо
CI_HELM_CHANNEL: stable
# Название создаваемого кластера kubernetes
CI_KIND_CLUSTER_NAME: job-${CI_JOB_ID}
# Название контейнера отложенного удаления кластера
CI_KIND_REAPER_NAME: job-${CI_JOB_ID}-reaper
# Для удобства переписываем в переменные имаджи нод с дайджестами
# в соответствии с используемой версией KinD
CI_KIND_IMAGE_1_17: 'kindest/node:v1.17.17@sha256:66f1d0d91a88b8a001811e2f1054af60eef3b669a9a74f9b6db871f2f1eeed00'
CI_KIND_IMAGE_1_18: 'kindest/node:v1.18.19@sha256:7af1492e19b3192a79f606e43c35fb741e520d195f96399284515f077b3b622c'
CI_KIND_IMAGE_1_19: 'kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729'
CI_KIND_IMAGE_1_20: 'kindest/node:v1.20.7@sha256:cbeaf907fc78ac97ce7b625e4bf0de16e3ea725daf6b04f930bd14c67c671ff9'
CI_KIND_IMAGE_1_21: 'kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6'
# Указываем, какой образ будем использовать в этом пайплайне
CI_KIND_IMAGE: $CI_KIND_IMAGE_1_19
# Описываем небольшие сниппеты, которые легко переиспользовать в нескольких джобах,
# а также улучшают читабельность кода.
.check-and-unshallow: &check-and-unshallow
- git version
- |
if [ -f "$(git rev-parse --git-dir)/shallow" ]; then
echo "this is a shallow repository";
git fetch --unshallow --prune --prune-tags --verbose
else
echo "not a shallow repository";
git fetch --prune --prune-tags --verbose
fi
- git rev-parse --verify HEAD
- git rev-list HEAD --count
- git rev-list HEAD --count --first-parent
.get-kube-binaries: &get-kube-binaries
- wget -nv -O /usr/local/bin/kind "https://github.com/kubernetes-sigs/kind/releases/download/${CI_KIND_VERSION}/kind-linux-amd64"
- chmod +x /usr/local/bin/kind
- wget -nv -O /usr/local/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/${CI_KUBECTL_VER}/bin/linux/amd64/kubectl"
- chmod +x /usr/local/bin/kubectl
.install-ct: &install-ct
- |
export CT_URL="https://github.com/helm/chart-testing/releases/download/${CI_CT_VER}"
export CT_TAR_FILE="chart-testing_${CI_CT_VER#v}_linux_amd64.tar.gz"
echo "install chart-testing ${CI_CT_VER} from \"${CT_URL}/${CT_TAR_FILE}\""
mkdir -p /tmp/ct /etc/ct
wget -nv -O "/tmp/${CT_TAR_FILE}" "${CT_URL}/${CT_TAR_FILE}"
tar -xzvf "/tmp/${CT_TAR_FILE}" -C /tmp/ct
mv /tmp/ct/etc/chart_schema.yaml /etc/ct/chart_schema.yaml
mv /tmp/ct/etc/lintconf.yaml /etc/ct/lintconf.yaml
mv /tmp/ct/ct /usr/bin/ct
rm -rf /tmp/ct
ct version
.install-helm: &install-helm
- |
export HELM_URL="https://get.helm.sh"
export HELM_TAR_FILE="helm-${CI_HELM_VER}-linux-amd64.tar.gz"
echo "install HELM ${CI_HELM_VER} from \"${HELM_URL}/${HELM_TAR_FILE}\""
mkdir -p /tmp/helm
wget -nv -O "/tmp/${HELM_TAR_FILE}" "${HELM_URL}/${HELM_TAR_FILE}"
tar -xzvf "/tmp/${HELM_TAR_FILE}" -C /tmp/helm
mv /tmp/helm/linux-amd64/helm /usr/bin/helm
rm -rf /tmp/helm
chmod +x /usr/bin/helm
helm version
# Добавляем Package Registry проекта с авторизацией через job-token
# и плагин для push в Helm-репо
.helm-add-project-as-repo: &helm-add-project-as-repo
- >-
helm repo add
--username gitlab-ci-token
--password "${CI_JOB_TOKEN}"
"${CI_PROJECT_NAME}"
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/${CI_HELM_CHANNEL}"
- helm plugin install https://github.com/chartmuseum/helm-push.git
- helm repo list
# Создаем контейнер отложенной очистки
.create-kind-reaper: &create-kind-reaper
- >-
docker run --rm -d --name ${CI_KIND_REAPER_NAME}
-e CI_KIND_CLUSTER_NAME=${CI_KIND_CLUSTER_NAME}
-e CI_REAPER_SLEEP=${CI_REAPER_SLEEP:-300}
-v /var/run/docker.sock:/var/run/docker.sock
docker:20.10
sh -c
'sleep ${CI_REAPER_SLEEP};
docker rm -fv $(docker ps -aq --filter label=io.x-k8s.kind.cluster=${CI_KIND_CLUSTER_NAME})'
# Описываем сами джобы
# Линт. Тут просто берем родной имадж chart-testing, ничего дабавлять не требуется
chart-lint:
stage: lint
image: quay.io/helmpack/chart-testing:v3.4.0
tags:
- "docker"
script:
- *check-and-unshallow
- ct lint --config ct.yaml
only:
- pushes
except:
- master
- main
# Здесь добавляем все необходимые бинари, поднимаем KinD
# и разворачиваем в него тестируемые чарты
chart-test:
stage: chart-test
image: docker:20.10-git
tags:
- "docker"
script:
- *check-and-unshallow
- apk add -U wget
# Добавляем недостающие бинари
- *get-kube-binaries
- *install-ct
- *install-helm
# запускаем рипер
- *create-kind-reaper
# разворачиваем KinD
- >-
kind create cluster
--name ${CI_KIND_CLUSTER_NAME}
--image ${CI_KIND_IMAGE}
--config=kind-cluster.yaml
--wait 5m
# Правим kubeconfig, чтобы указанный хост api-server соответствовал хосту docker0,
# адрес которого, в свою очередь, мы ранее указали в патче ClusterConfiguration
- sed -i -E -e 's/0\.0\.0\.0/172\.17\.0\.1/g' "$HOME/.kube/config"
# Разворачиваем наши чарты
- ct install --config ct.yaml
after_script:
# удаляем кластер KinD
- kind delete cluster --name ${CI_KIND_CLUSTER_NAME}
# удаляем контейнер отложенной очистки
- docker rm -fv ${CI_KIND_REAPER_NAME}
only:
- pushes
except:
- master
- main
# Упаковываем чарты и публикуем их в Package Registry
chart-release:
stage: chart-release
image: quay.io/helmpack/chart-testing:v3.4.0
tags:
- "docker"
script:
- *check-and-unshallow
- apk add jq yq
- *helm-add-project-as-repo
# используем доработанный скрипт из экшена chart-releaser
- >-
./gitlab-cr.sh
--charts-dir charts
--charts-repo-url "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/${CI_HELM_CHANNEL}"
--repo "${CI_PROJECT_NAME}"
only:
- master
- main
Для подключения к приватному Helm-репо на pull будет достаточно прав read_api
персонального токена, но намного правильнее использовать для этого deploy token с правами read_package_registry
. Аналогичный пример подключения Helm-репо с авторизацией токеном задания GitLab можно найти в сниппете "helm-add-project-as-repo".
Заключение
Статья получилась довольно насыщенной ссылками на документацию, но возможно, это облегчит путь тех, кто еще не слишком уверенно ориентируется в предметной области. Кроме того все материалы собраны в репо на GitHub.
Благодарю за внимание и надеюсь, что этот дебют получился достаточно интересным и поможет вам в автоматизации своего проекта или послужит отправной точкой в изучении новых инструментов.