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
|
harbor_metrics_enabled: true
|
||||||
# ServiceMonitor создаётся только когда addon_prometheus_stack: 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:
|
harbor_resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
|
|||||||
@@ -101,12 +101,59 @@
|
|||||||
environment:
|
environment:
|
||||||
KUBECONFIG: "{{ k3s_kubeconfig_path }}"
|
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
|
- name: Show Harbor access info
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg:
|
msg:
|
||||||
- "Harbor установлен в namespace: {{ harbor_namespace }}"
|
- "Harbor установлен в namespace: {{ harbor_namespace }}"
|
||||||
- "URL: http{{ 's' if harbor_ingress_tls else '' }}://{{ harbor_ingress_host }}"
|
- "URL: http{{ 's' if harbor_ingress_tls else '' }}://{{ harbor_ingress_host }}"
|
||||||
- "Логин: admin"
|
- "Логин: admin / Пароль: {{ harbor_admin_password }}"
|
||||||
- "Пароль: {{ harbor_admin_password }}"
|
|
||||||
- "Docker login: docker login {{ harbor_ingress_host }} -u admin"
|
- "Docker login: docker login {{ harbor_ingress_host }} -u admin"
|
||||||
- "БД: {{ harbor_database_type }}{{ ' (PostgreSQL ' + harbor_db_host + ')' if harbor_database_type == 'external' else '' }}"
|
- "БД: {{ 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