feat: harbor — proxy cache зеркалирование + tag retention policy

Proxy cache (harbor_proxy_cache_enabled: true):
- Автоматически создаёт registry endpoints + proxy cache проекты для:
  docker.io, gcr.io, quay.io, ghcr.io, registry.k8s.io, mcr.microsoft.com, public.ecr.aws
- При pull образа через harbor.example.com/<registry>/<image> он кэшируется
- Реализовано через alpine:3.19 + curl + jq Kubernetes Job (вызывает Harbor REST API изнутри кластера)

Tag retention (harbor_retention_enabled: true, harbor_retention_max_tags: 3):
- Политика "latestPushedN=3" применяется ко ВСЕМ проектам (включая proxy cache)
- Пропускает проекты с уже существующей политикой (idempotent)
- Запуск: ежедневно в 03:00 UTC (cron schedule в Harbor)

Механизм: Job запускается после Helm install, достучивается до harbor-core по
internal service DNS, ждёт API готовности (40 попыток × 15 сек = 10 мин max).
This commit is contained in:
Sergey Antropoff
2026-04-25 11:54:43 +03:00
parent e1e84aeb86
commit 5dc0fbcd3a
3 changed files with 232 additions and 2 deletions

View File

@@ -38,6 +38,48 @@ harbor_redis_storage_size: "1Gi"
harbor_metrics_enabled: true
# ServiceMonitor создаётся только когда addon_prometheus_stack: true
# ── Proxy Cache (зеркалирование публичных registry) ───────────────────────────
# При pull образа через Harbor он автоматически кэшируется в Harbor.
# Использование: docker pull harbor.example.com/dockerhub/library/nginx:latest
# ^^^^^^^^^ имя проекта ниже
harbor_proxy_cache_enabled: true
harbor_proxy_cache_registries:
- name: "dockerhub"
description: "Docker Hub"
type: "docker-hub"
url: "https://hub.docker.com"
- name: "gcr"
description: "Google Container Registry"
type: "gcr"
url: "https://gcr.io"
- name: "quay"
description: "Quay.io"
type: "quay"
url: "https://quay.io"
- name: "ghcr"
description: "GitHub Container Registry"
type: "docker-registry"
url: "https://ghcr.io"
- name: "k8s-registry"
description: "Kubernetes Registry (registry.k8s.io)"
type: "docker-registry"
url: "https://registry.k8s.io"
- name: "mcr"
description: "Microsoft Container Registry"
type: "docker-registry"
url: "https://mcr.microsoft.com"
- name: "ecr-public"
description: "Amazon ECR Public Gallery"
type: "docker-registry"
url: "https://public.ecr.aws"
# ── Tag Retention Policy ──────────────────────────────────────────────────────
# Автоматически удалять образы если их больше N штук для одного репозитория.
# Политика применяется ко ВСЕМ проектам (включая proxy cache).
# Запуск: ежедневно в 03:00 UTC.
harbor_retention_enabled: true
harbor_retention_max_tags: 3 # хранить последних N тегов по дате push
harbor_resources:
requests:
cpu: 100m

View File

@@ -101,12 +101,59 @@
environment:
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
- name: Template Harbor configuration Job (proxy cache + retention)
ansible.builtin.template:
src: harbor-configure-job.yaml.j2
dest: /tmp/harbor-configure-job.yaml
mode: '0644'
when: harbor_proxy_cache_enabled | bool or harbor_retention_enabled | bool
- name: Delete previous Harbor configuration Job (если было)
ansible.builtin.command: >
k3s kubectl -n {{ harbor_namespace }} delete job harbor-configure --ignore-not-found=true
changed_when: false
when: harbor_proxy_cache_enabled | bool or harbor_retention_enabled | bool
- name: Apply Harbor configuration Job
ansible.builtin.command: k3s kubectl apply -f /tmp/harbor-configure-job.yaml
changed_when: true
when: harbor_proxy_cache_enabled | bool or harbor_retention_enabled | bool
- name: Wait for Harbor configuration Job to complete
ansible.builtin.command: >
k3s kubectl -n {{ harbor_namespace }}
wait job/harbor-configure
--for=condition=complete --timeout=600s
changed_when: false
when: harbor_proxy_cache_enabled | bool or harbor_retention_enabled | bool
- name: Show Harbor configuration Job logs
ansible.builtin.command: >
k3s kubectl -n {{ harbor_namespace }} logs
-l job-name=harbor-configure --tail=50
register: _harbor_configure_logs
changed_when: false
failed_when: false
when: harbor_proxy_cache_enabled | bool or harbor_retention_enabled | bool
- name: Harbor configuration output
ansible.builtin.debug:
msg: "{{ _harbor_configure_logs.stdout_lines }}"
when: harbor_proxy_cache_enabled | bool or harbor_retention_enabled | bool
- name: Show Harbor access info
ansible.builtin.debug:
msg:
- "Harbor установлен в namespace: {{ harbor_namespace }}"
- "URL: http{{ 's' if harbor_ingress_tls else '' }}://{{ harbor_ingress_host }}"
- "Логин: admin"
- "Пароль: {{ harbor_admin_password }}"
- "Логин: admin / Пароль: {{ harbor_admin_password }}"
- "Docker login: docker login {{ harbor_ingress_host }} -u admin"
- "БД: {{ harbor_database_type }}{{ ' (PostgreSQL ' + harbor_db_host + ')' if harbor_database_type == 'external' else '' }}"
- ""
- "Proxy cache pull (образ кэшируется в Harbor автоматически):"
- " docker pull {{ harbor_ingress_host }}/dockerhub/library/nginx:latest"
- " docker pull {{ harbor_ingress_host }}/gcr/google-containers/pause:3.9"
- " docker pull {{ harbor_ingress_host }}/ghcr/owner/image:tag"
- " docker pull {{ harbor_ingress_host }}/k8s-registry/kube-apiserver:v1.30.0"
- ""
- "Retention policy: хранить последние {{ harbor_retention_max_tags }} тега, запуск в 03:00 UTC"

View File

@@ -0,0 +1,141 @@
---
# ConfigMap со скриптом конфигурации Harbor
# Настраивает proxy-cache registry и tag retention policies через REST API
apiVersion: v1
kind: ConfigMap
metadata:
name: harbor-configure
namespace: {{ harbor_namespace }}
data:
configure.sh: |
#!/bin/sh
set -e
apk add -q curl jq
BASE="http://harbor.{{ harbor_namespace }}.svc.cluster.local"
AUTH="admin:{{ harbor_admin_password }}"
# ── Ждём готовности Harbor API ────────────────────────────────────────────
echo ">>> Ожидаю готовности Harbor API..."
for i in $(seq 1 40); do
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -u "$AUTH" \
"$BASE/api/v2.0/systeminfo" 2>/dev/null || echo "000")
[ "$STATUS" = "200" ] && echo "Harbor API готов" && break
echo "Попытка $i/40 (HTTP $STATUS), жду 15 сек..."
sleep 15
[ "$i" -eq 40 ] && echo "ОШИБКА: Harbor API недоступен" && exit 1
done
{% if harbor_proxy_cache_enabled %}
# ── Proxy Cache: создать registry endpoints и проекты ─────────────────────
echo ""
echo ">>> Настройка proxy-cache реестров..."
{% for reg in harbor_proxy_cache_registries %}
echo "--- {{ reg.description }} ({{ reg.name }}) ---"
# Создать registry endpoint (409 = уже существует — ок)
curl -sf -X POST -u "$AUTH" -H "Content-Type: application/json" \
-d '{"name":"{{ reg.name }}","type":"{{ reg.type }}","url":"{{ reg.url }}","description":"{{ reg.description }}","insecure":false}' \
"$BASE/api/v2.0/registries" \
-o /dev/null -w " Endpoint: HTTP %{http_code}\n" || true
# Получить ID endpoint-а
REG_ID=$(curl -sf -u "$AUTH" \
"$BASE/api/v2.0/registries?name={{ reg.name }}" | jq -e '.[0].id' 2>/dev/null || echo "")
if [ -z "$REG_ID" ] || [ "$REG_ID" = "null" ]; then
echo " ПРЕДУПРЕЖДЕНИЕ: не удалось получить ID для {{ reg.name }}, пропускаю"
else
# Создать proxy cache проект (409 = уже существует — ок)
curl -sf -X POST -u "$AUTH" -H "Content-Type: application/json" \
-d "{\"project_name\":\"{{ reg.name }}\",\"public\":true,\"registry_id\":$REG_ID,\"metadata\":{\"public\":\"true\"}}" \
"$BASE/api/v2.0/projects" \
-o /dev/null -w " Проект: HTTP %{http_code}\n" || true
fi
{% endfor %}
echo ">>> Proxy-cache реестры настроены"
echo " Примеры pull через Harbor:"
{% for reg in harbor_proxy_cache_registries %}
echo " {{ harbor_ingress_host }}/{{ reg.name }}/<image>:<tag>"
{% endfor %}
{% endif %}
{% if harbor_retention_enabled %}
# ── Tag Retention: применить ко всем проектам ─────────────────────────────
echo ""
echo ">>> Настройка retention policy (max {{ harbor_retention_max_tags }} тегов на репозиторий)..."
# Получить все проекты и сохранить в файл (избегаем subshell через pipe)
curl -sf -u "$AUTH" "$BASE/api/v2.0/projects?page_size=100" \
| jq -c '.[]' > /tmp/projects.json
while IFS= read -r project; do
PROJ_ID=$(echo "$project" | jq '.id')
PROJ_NAME=$(echo "$project" | jq -r '.name')
# Пропустить если retention уже настроен
EXISTING_RET=$(echo "$project" | jq -r '.metadata.retention_id // empty')
if [ -n "$EXISTING_RET" ]; then
echo " $PROJ_NAME: retention уже настроен (id: $EXISTING_RET), пропускаю"
continue
fi
# Создать retention policy для проекта
RET_RESP=$(curl -sf -X POST -u "$AUTH" -H "Content-Type: application/json" \
-d "{
\"algorithm\": \"or\",
\"rules\": [{
\"disabled\": false,
\"action\": \"retain\",
\"template\": \"latestPushedN\",
\"params\": {\"latestPushedN\": {{ harbor_retention_max_tags }}},
\"tag_selectors\": [{\"kind\": \"doublestar\", \"decoration\": \"matches\", \"pattern\": \"**\"}],
\"scope_selectors\": {
\"repository\": [{\"kind\": \"doublestar\", \"decoration\": \"repoMatches\", \"pattern\": \"**\"}]
}
}],
\"trigger\": {\"kind\": \"Schedule\", \"settings\": {\"cron\": \"0 0 3 * * *\"}},
\"scope\": {\"level\": \"project\", \"ref\": $PROJ_ID}
}" \
"$BASE/api/v2.0/retentions" 2>/dev/null || echo "{}")
RET_ID=$(echo "$RET_RESP" | jq '.id // empty' 2>/dev/null || echo "")
if [ -n "$RET_ID" ] && [ "$RET_ID" != "null" ]; then
echo " $PROJ_NAME (id: $PROJ_ID): retention policy #$RET_ID установлена"
else
echo " $PROJ_NAME: не удалось создать retention (возможно уже настроено)"
fi
done < /tmp/projects.json
echo ">>> Retention policies настроены (запуск: ежедневно в 03:00 UTC)"
{% endif %}
echo ""
echo ">>> Конфигурация Harbor завершена!"
---
apiVersion: batch/v1
kind: Job
metadata:
name: harbor-configure
namespace: {{ harbor_namespace }}
spec:
ttlSecondsAfterFinished: 600
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: configure
image: alpine:3.19
command: ["/bin/sh", "/scripts/configure.sh"]
volumeMounts:
- name: scripts
mountPath: /scripts
volumes:
- name: scripts
configMap:
name: harbor-configure
defaultMode: 0755