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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
141
addons/harbor/role/templates/harbor-configure-job.yaml.j2
Normal file
141
addons/harbor/role/templates/harbor-configure-job.yaml.j2
Normal 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
|
||||
Reference in New Issue
Block a user