## 🏗️ 新增功能 - **shared/**: 完整的基础组件部署方案 - docker-compose.yml: 统一编排所有基础服务 - .env.example: 标准化环境变量配置 - prometheus/prometheus.yml: 监控配置 - bin/up.sh & down.sh: 一键部署脚本 - README.md: 完整的使用文档 ## 🛠️ 核心服务 - **数据库层**: PostgreSQL 16 + MySQL 8 (统一密码: 123456) - **缓存层**: Redis 7 (密码: 123456) - **代码管理**: GitLab CE + Gitea (双代码仓库) - **镜像仓库**: Harbor (Docker Registry) - **监控层**: Prometheus + Grafana + cAdvisor - **网关反代**: Traefik v3.1 ## 🔧 技术优化 - 所有端口统一使用 2xx80 系列,避免端口冲突 - 统一网络架构 (core network),支持多项目接入 - 完善的健康检查和服务依赖配置 - 支持 ARM64 架构 (MySQL 8 兼容性修复) ## 📚 文档更新 - SYSTEM-CONFIG.md: 系统配置文档完善 - harbor/README.md: Harbor 模块使用说明 - docs/: 完整的部署和运维文档 ## 🚀 部署说明 所有服务已成功启动并通过验证: - GitLab: http://localhost:28082 (root/SecurePass2024!) - Gitea: http://localhost:23000 - Harbor: http://localhost:28081 - Grafana: http://localhost:23001 (admin/123456) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
698 lines
22 KiB
Bash
Executable File
698 lines
22 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Harbor Kubernetes 镜像预下载和推送脚本
|
|
# 自动下载常用的 Kubernetes 相关镜像并推送到 Harbor 仓库
|
|
|
|
set -euo pipefail
|
|
|
|
# 配置变量
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
HARBOR_DOMAIN="${HARBOR_DOMAIN:-192.168.1.83}"
|
|
HARBOR_PORT="${HARBOR_PORT:-8080}"
|
|
HARBOR_HTTPS_PORT="${HARBOR_HTTPS_PORT:-8443}"
|
|
HARBOR_USER="${HARBOR_USER:-admin}"
|
|
HARBOR_PASSWORD="${HARBOR_PASSWORD:-admin}"
|
|
HARBOR_PROJECT="${HARBOR_PROJECT:-kubernetes}"
|
|
HARBOR_USE_HTTPS="${HARBOR_USE_HTTPS:-true}"
|
|
|
|
LOG_FILE="${LOG_FILE:-~/ops/logs/harbor-k8s-images.log}"
|
|
IMAGES_CONFIG_FILE="${IMAGES_CONFIG_FILE:-${SCRIPT_DIR}/k8s-images.list}"
|
|
|
|
# 颜色定义
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
PURPLE='\033[0;35m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# 日志函数
|
|
log() {
|
|
echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}"
|
|
}
|
|
|
|
info() {
|
|
log "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
success() {
|
|
log "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
warning() {
|
|
log "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
error() {
|
|
log "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
progress() {
|
|
log "${PURPLE}[PROGRESS]${NC} $1"
|
|
}
|
|
|
|
# 检查必要的依赖
|
|
check_dependencies() {
|
|
local deps=("docker" "curl" "jq")
|
|
for dep in "${deps[@]}"; do
|
|
if ! command -v "$dep" &> /dev/null; then
|
|
error "$dep 未安装,请先安装 $dep"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# 检查 Docker 是否运行
|
|
if ! docker info &> /dev/null; then
|
|
error "Docker 未运行,请启动 Docker Desktop"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# 创建必要的目录
|
|
ensure_directories() {
|
|
mkdir -p "$(dirname "${LOG_FILE}")"
|
|
mkdir -p "$(dirname "${IMAGES_CONFIG_FILE}")"
|
|
}
|
|
|
|
# 检查 Harbor 连接
|
|
check_harbor_connection() {
|
|
info "检查 Harbor 连接..."
|
|
|
|
local harbor_url
|
|
local curl_opts=""
|
|
|
|
if [[ "$HARBOR_USE_HTTPS" == "true" ]]; then
|
|
harbor_url="https://${HARBOR_DOMAIN}:${HARBOR_HTTPS_PORT}"
|
|
curl_opts="-k" # 忽略证书验证
|
|
else
|
|
harbor_url="http://${HARBOR_DOMAIN}:${HARBOR_PORT}"
|
|
fi
|
|
|
|
# 检查 Harbor 服务是否可用
|
|
if ! curl -s -f $curl_opts "${harbor_url}/api/v2.0/health" > /dev/null 2>&1; then
|
|
error "无法连接到 Harbor 服务: ${harbor_url}"
|
|
error "请确保 Harbor 服务正在运行"
|
|
return 1
|
|
fi
|
|
|
|
success "Harbor 连接正常: ${harbor_url}"
|
|
return 0
|
|
}
|
|
|
|
# Harbor 登录
|
|
harbor_login() {
|
|
info "登录 Harbor 仓库..."
|
|
|
|
local harbor_port
|
|
|
|
if [[ "$HARBOR_USE_HTTPS" == "true" ]]; then
|
|
harbor_port="${HARBOR_HTTPS_PORT}"
|
|
else
|
|
harbor_port="${HARBOR_PORT}"
|
|
fi
|
|
|
|
# 尝试登录 Harbor
|
|
if echo "${HARBOR_PASSWORD}" | docker login "${HARBOR_DOMAIN}:${harbor_port}" -u "${HARBOR_USER}" --password-stdin 2>/dev/null; then
|
|
success "Harbor 登录成功"
|
|
return 0
|
|
else
|
|
# 如果直接登录失败,提供解决方案
|
|
error "Harbor 登录失败"
|
|
info "可能的解决方案:"
|
|
info "1. 对于 HTTPS + 自签名证书,尝试以下命令:"
|
|
info " mkdir -p ~/.docker && echo '{\"insecure-registries\":[\"${HARBOR_DOMAIN}:${harbor_port}\"]}' > ~/.docker/daemon.json"
|
|
info " 然后重启 OrbStack"
|
|
info ""
|
|
info "2. 或者使用 HTTP 方式(如果 Harbor 支持):"
|
|
info " HARBOR_USE_HTTPS=false ./harbor-k8s-images.sh sync"
|
|
info ""
|
|
info "3. 检查 Harbor Web 界面确认用户名和密码是否正确"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# 创建 Harbor 项目(如果不存在)
|
|
ensure_harbor_project() {
|
|
info "确保 Harbor 项目 '${HARBOR_PROJECT}' 存在..."
|
|
|
|
local harbor_url
|
|
local curl_opts=""
|
|
|
|
if [[ "$HARBOR_USE_HTTPS" == "true" ]]; then
|
|
harbor_url="https://${HARBOR_DOMAIN}:${HARBOR_HTTPS_PORT}"
|
|
curl_opts="-k"
|
|
else
|
|
harbor_url="http://${HARBOR_DOMAIN}:${HARBOR_PORT}"
|
|
fi
|
|
|
|
local project_url="${harbor_url}/api/v2.0/projects"
|
|
|
|
# 检查项目是否存在
|
|
if curl -s $curl_opts -u "${HARBOR_USER}:${HARBOR_PASSWORD}" "${project_url}?name=${HARBOR_PROJECT}" | jq -e '.[] | select(.name == "'"${HARBOR_PROJECT}"'")' > /dev/null 2>&1; then
|
|
success "项目 '${HARBOR_PROJECT}' 已存在"
|
|
return 0
|
|
fi
|
|
|
|
# 创建项目
|
|
info "创建项目 '${HARBOR_PROJECT}'..."
|
|
local create_data="{\"project_name\":\"${HARBOR_PROJECT}\",\"public\":true}"
|
|
|
|
if curl -s $curl_opts -u "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "${create_data}" \
|
|
"${project_url}" > /dev/null 2>&1; then
|
|
success "项目 '${HARBOR_PROJECT}' 创建成功"
|
|
return 0
|
|
else
|
|
error "项目 '${HARBOR_PROJECT}' 创建失败"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# 创建默认镜像配置文件
|
|
create_default_images_config() {
|
|
if [[ -f "${IMAGES_CONFIG_FILE}" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
info "创建默认镜像配置文件: ${IMAGES_CONFIG_FILE}"
|
|
|
|
cat > "${IMAGES_CONFIG_FILE}" << 'EOF'
|
|
# Kubernetes 镜像列表配置
|
|
# 格式: <镜像名称>:<标签> <目标仓库别名> <描述>
|
|
# 别名用于在 Harbor 中组织镜像,可以使用 / 分层
|
|
|
|
# 核心组件
|
|
k8s.gcr.io/kube-apiserver:v1.28.0 kube-apiserver Kubernetes API Server
|
|
k8s.gcr.io/kube-controller-manager:v1.28.0 kube-controller-manager Kubernetes Controller Manager
|
|
k8s.gcr.io/kube-scheduler:v1.28.0 kube-scheduler Kubernetes Scheduler
|
|
k8s.gcr.io/kube-proxy:v1.28.0 kube-proxy Kubernetes Proxy
|
|
k8s.gcr.io/etcd:3.5.9-0 etcd etcd Key-Value Store
|
|
k8s.gcr.io/coredns/coredns:v1.10.1 coredns CoreDNS Service Discovery
|
|
k8s.gcr.io/pause:3.9 pause Kubernetes Pause Container
|
|
|
|
# 网络组件
|
|
quay.io/calico/cni:v3.26.0 calico/cni Calico CNI Plugin
|
|
quay.io/calico/node:v3.26.0 calico/node Calico Node Agent
|
|
quay.io/calico/kube-controllers:v3.26.0 calico/kube-controllers Calico Kubernetes Controllers
|
|
quay.io/calico/typha:v3.26.0 calico/typha Calico Typha Daemon
|
|
|
|
flannelcni/flannel:v0.22.0 flannel Flannel CNI Plugin
|
|
flannel/flannel-cni-plugin:v1.2.0 flannel-cni-plugin Flannel CNI Plugin
|
|
|
|
# Ingress 控制器
|
|
registry.k8s.io/ingress-nginx/controller:v1.9.4 ingress-nginx/controller NGINX Ingress Controller
|
|
registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.0 ingress-nginx/kube-webhook-certgen NGINX Webhook Cert Generator
|
|
|
|
# 存储组件
|
|
quay.io/external_storage/rbd-provisioner:v2.1.1-k8s1.11 rbd-provisioner Ceph RBD Provisioner
|
|
quay.io/ceph/ceph:v15.2.17 ceph Ceph Container
|
|
quay.io/cephcsi/cephcsi:v3.7.0 cephcsi Ceph CSI Driver
|
|
|
|
# 监控组件
|
|
prom/prometheus:v2.45.0 prometheus Prometheus Monitoring
|
|
prom/alertmanager:v0.25.0 alertmanager AlertManager
|
|
grafana/grafana:10.1.0 grafana Grafana Visualization
|
|
quay.io/prometheus-operator/prometheus-operator:v0.68.0 prometheus-operator Prometheus Operator
|
|
quay.io/prometheus-operator/prometheus-config-reloader:v0.68.0 prometheus-config-reloader Prometheus Config Reloader
|
|
|
|
# 日志组件
|
|
fluent/fluent-bit:2.2.0 fluent-bit Fluent Bit Log Processor
|
|
elastic/elasticsearch:8.8.0 elasticsearch Elasticsearch
|
|
elastic/kibana:8.8.0 kibana Kibana Visualization
|
|
|
|
# 安全组件
|
|
quay.io/coreos/oidc-discovery-provider:v1.0 oidc-discovery-provider OIDC Discovery Provider
|
|
quay.io/jetstack/cert-manager-controller:v1.13.0 cert-manager/cert-manager-cert-manager Cert Manager Controller
|
|
quay.io/jetstack/cert-manager-webhook:v1.13.0 cert-manager/cert-manager-webhook Cert Manager Webhook
|
|
quay.io/jetstack/cert-manager-cainjector:v1.13.0 cert-manager/cert-manager-cainjector Cert Manager CA Injector
|
|
|
|
# CI/CD 组件
|
|
gcr.io/kaniko-project/executor:v1.9.1 kaniko Kaniko Image Builder
|
|
argoproj/argo-cd:v2.7.3 argo-cd ArgoCD GitOps
|
|
argoproj/argo-workflows:v3.4.8 argo-workflows Argo Workflows
|
|
gcr.io/jenkinsx/inigo:0.1.73 inigo Jenkins X Build Controller
|
|
|
|
# 服务网格
|
|
istio/proxyv2:1.19.0 istio/proxyv2 Istio Envoy Proxy
|
|
istio/pilot:1.19.0 istio/pilot Istio Pilot
|
|
docker.io/istio/citadel:1.19.0 istio/citadel Istio Citadel
|
|
|
|
# 数据库
|
|
postgres:15.3 postgres PostgreSQL Database
|
|
mysql:8.1.0 mysql MySQL Database
|
|
redis:7.0.11 redis Redis Cache
|
|
mongo:6.0.13 mongo MongoDB
|
|
|
|
# 应用镜像
|
|
nginx:1.24.0 nginx Nginx Web Server
|
|
httpd:2.4.57 httpd Apache HTTP Server
|
|
tomcat:10.1.13-jdk11-temurin tomcat Apache Tomcat
|
|
openjdk:17-jdk-slim openjdk OpenJDK 17
|
|
python:3.11.4-slim python Python 3.11
|
|
node:18.16.1-slim node Node.js 18
|
|
|
|
# 工具镜像
|
|
busybox:1.36.1 busybox BusyBox Utilities
|
|
alpine:3.18.3 alpine Alpine Linux
|
|
ubuntu:22.04 ubuntu Ubuntu 22.04
|
|
|
|
# 多架构支持
|
|
--multi-arch k8s.gcr.io/kube-apiserver:v1.28.0 kube-apiserver-multi Kubernetes API Server (Multi-arch)
|
|
--multi-arch k8s.gcr.io/kube-controller-manager:v1.28.0 kube-controller-manager-multi Kubernetes Controller Manager (Multi-arch)
|
|
--multi-arch k8s.gcr.io/kube-scheduler:v1.28.0 kube-scheduler-multi Kubernetes Scheduler (Multi-arch)
|
|
EOF
|
|
|
|
success "默认镜像配置文件已创建"
|
|
info "您可以编辑 ${IMAGES_CONFIG_FILE} 来自定义镜像列表"
|
|
}
|
|
|
|
# 解析镜像配置
|
|
parse_images_config() {
|
|
local images=()
|
|
|
|
if [[ ! -f "${IMAGES_CONFIG_FILE}" ]]; then
|
|
error "镜像配置文件不存在: ${IMAGES_CONFIG_FILE}"
|
|
return 1
|
|
fi
|
|
|
|
while IFS= read -r line; do
|
|
# 跳过空行和注释
|
|
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
|
|
|
|
# 解析多架构标记
|
|
local multi_arch=false
|
|
if [[ "$line" =~ ^--multi-arch[[:space:]]+(.+)$ ]]; then
|
|
multi_arch=true
|
|
line="${BASH_REMATCH[1]}"
|
|
fi
|
|
|
|
# 解析镜像信息
|
|
if [[ "$line" =~ ^([a-zA-Z0-9.-_/]+:[^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]*(.*)$ ]]; then
|
|
local source_image="${BASH_REMATCH[1]}"
|
|
local target_alias="${BASH_REMATCH[2]}"
|
|
local description="${BASH_REMATCH[3]}"
|
|
|
|
images+=("$source_image|$target_alias|$description|$multi_arch")
|
|
fi
|
|
done < "${IMAGES_CONFIG_FILE}"
|
|
|
|
if [[ ${#images[@]} -eq 0 ]]; then
|
|
error "镜像配置文件中没有找到有效的镜像配置"
|
|
return 1
|
|
fi
|
|
|
|
printf '%s\n' "${images[@]}"
|
|
return 0
|
|
}
|
|
|
|
# 获取镜像架构信息
|
|
get_image_architectures() {
|
|
local image="$1"
|
|
local arch_info
|
|
|
|
# 使用 docker manifest inspect 获取架构信息
|
|
if arch_info=$(docker manifest inspect "$image" 2>/dev/null); then
|
|
echo "$arch_info" | jq -r '.manifests[].platform.architecture' | sort -u | tr '\n' ',' | sed 's/,$//'
|
|
else
|
|
# 如果没有 manifest 信息,尝试获取本地镜像的架构
|
|
if docker image inspect "$image" &>/dev/null; then
|
|
docker image inspect "$image" --format='{{.Architecture}}' 2>/dev/null || echo "amd64"
|
|
else
|
|
echo "amd64" # 默认架构
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# 拉取镜像
|
|
pull_image() {
|
|
local source_image="$1"
|
|
local multi_arch="${2:-false}"
|
|
|
|
info "拉取镜像: $source_image"
|
|
|
|
if [[ "$multi_arch" == "true" ]]; then
|
|
# 拉取多架构镜像
|
|
if docker manifest create "$source_image" >/dev/null 2>&1 || true; then
|
|
docker manifest rm "$source_image" >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
if docker pull "$source_image" --platform linux/amd64,linux/arm64; then
|
|
success "多架构镜像拉取成功: $source_image"
|
|
return 0
|
|
else
|
|
warning "多架构镜像拉取失败,尝试单架构拉取"
|
|
if docker pull "$source_image"; then
|
|
success "镜像拉取成功: $source_image"
|
|
return 0
|
|
else
|
|
error "镜像拉取失败: $source_image"
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
# 拉取单架构镜像
|
|
if docker pull "$source_image"; then
|
|
success "镜像拉取成功: $source_image"
|
|
return 0
|
|
else
|
|
error "镜像拉取失败: $source_image"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# 推送镜像到 Harbor
|
|
push_image_to_harbor() {
|
|
local source_image="$1"
|
|
local target_alias="$2"
|
|
local multi_arch="${3:-false}"
|
|
|
|
local harbor_port
|
|
if [[ "$HARBOR_USE_HTTPS" == "true" ]]; then
|
|
harbor_port="${HARBOR_HTTPS_PORT}"
|
|
else
|
|
harbor_port="${HARBOR_PORT}"
|
|
fi
|
|
|
|
local harbor_image="${HARBOR_DOMAIN}:${harbor_port}/${HARBOR_PROJECT}/${target_alias}"
|
|
|
|
info "推送镜像到 Harbor: $source_image -> $harbor_image"
|
|
|
|
# 标记镜像
|
|
if docker tag "$source_image" "$harbor_image"; then
|
|
success "镜像标记成功: $harbor_image"
|
|
else
|
|
error "镜像标记失败: $source_image -> $harbor_image"
|
|
return 1
|
|
fi
|
|
|
|
# 推送镜像
|
|
if [[ "$multi_arch" == "true" ]]; then
|
|
# 推送多架构镜像
|
|
if docker push "$harbor_image"; then
|
|
success "多架构镜像推送成功: $harbor_image"
|
|
return 0
|
|
else
|
|
error "多架构镜像推送失败: $harbor_image"
|
|
return 1
|
|
fi
|
|
else
|
|
# 推送单架构镜像
|
|
if docker push "$harbor_image"; then
|
|
success "镜像推送成功: $harbor_image"
|
|
return 0
|
|
else
|
|
error "镜像推送失败: $harbor_image"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# 清理本地镜像
|
|
cleanup_local_image() {
|
|
local source_image="$1"
|
|
local harbor_image="$2"
|
|
|
|
# 可选:清理本地临时镜像
|
|
if [[ "${CLEANUP_LOCAL_IMAGES:-false}" == "true" ]]; then
|
|
info "清理本地镜像: $source_image"
|
|
docker rmi "$source_image" 2>/dev/null || true
|
|
docker rmi "$harbor_image" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
# 处理单个镜像
|
|
process_image() {
|
|
local image_info="$1"
|
|
IFS='|' read -r source_image target_alias description multi_arch <<< "$image_info"
|
|
|
|
progress "处理镜像: $source_image -> $target_alias ($description)"
|
|
|
|
# 拉取镜像
|
|
if ! pull_image "$source_image" "$multi_arch"; then
|
|
error "跳过镜像: $source_image (拉取失败)"
|
|
return 1
|
|
fi
|
|
|
|
# 推送到 Harbor
|
|
local harbor_port
|
|
if [[ "$HARBOR_USE_HTTPS" == "true" ]]; then
|
|
harbor_port="${HARBOR_HTTPS_PORT}"
|
|
else
|
|
harbor_port="${HARBOR_PORT}"
|
|
fi
|
|
local harbor_image="${HARBOR_DOMAIN}:${harbor_port}/${HARBOR_PROJECT}/${target_alias}"
|
|
if ! push_image_to_harbor "$source_image" "$target_alias" "$multi_arch"; then
|
|
error "跳过镜像: $source_image (推送失败)"
|
|
return 1
|
|
fi
|
|
|
|
# 清理本地镜像(可选)
|
|
cleanup_local_image "$source_image" "$harbor_image"
|
|
|
|
success "镜像处理完成: $target_alias"
|
|
return 0
|
|
}
|
|
|
|
# 同步所有镜像
|
|
sync_all_images() {
|
|
info "开始同步 Kubernetes 镜像到 Harbor..."
|
|
|
|
# 解析镜像配置
|
|
local images
|
|
if ! images=($(parse_images_config)); then
|
|
error "无法解析镜像配置"
|
|
return 1
|
|
fi
|
|
|
|
local total_images=${#images[@]}
|
|
local success_count=0
|
|
local failed_count=0
|
|
|
|
info "总共需要处理 ${total_images} 个镜像"
|
|
|
|
# 处理每个镜像
|
|
for i in "${!images[@]}"; do
|
|
local image_info="${images[$i]}"
|
|
local current_num=$((i + 1))
|
|
|
|
progress "[$current_num/$total_images] 处理镜像..."
|
|
|
|
if process_image "$image_info"; then
|
|
((success_count++))
|
|
else
|
|
((failed_count++))
|
|
fi
|
|
|
|
# 显示进度
|
|
local progress_percent=$((current_num * 100 / total_images))
|
|
echo -e "\r${CYAN}进度: ${progress_percent}% (${success_count} 成功, ${failed_count} 失败)${NC}"
|
|
done
|
|
|
|
echo "" # 换行
|
|
|
|
# 显示结果统计
|
|
info "镜像同步完成!"
|
|
success "成功: ${success_count} 个镜像"
|
|
if [[ $failed_count -gt 0 ]]; then
|
|
error "失败: ${failed_count} 个镜像"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# 显示 Harbor 项目中的镜像
|
|
list_harbor_images() {
|
|
info "列出 Harbor 项目 '${HARBOR_PROJECT}' 中的镜像..."
|
|
|
|
local harbor_url="http://${HARBOR_DOMAIN}:${HARBOR_PORT}"
|
|
local project_id
|
|
|
|
# 获取项目 ID
|
|
project_id=$(curl -s -u "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
|
"${harbor_url}/api/v2.0/projects?name=${HARBOR_PROJECT}" | \
|
|
jq -r '.[] | select(.name == "'"${HARBOR_PROJECT}"'") | .project_id')
|
|
|
|
if [[ -z "$project_id" ]]; then
|
|
error "项目 '${HARBOR_PROJECT}' 不存在"
|
|
return 1
|
|
fi
|
|
|
|
# 获取仓库列表
|
|
local repositories
|
|
repositories=$(curl -s -u "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
|
"${harbor_url}/api/v2.0/projects/${project_id}/repositories" | \
|
|
jq -r '.[].name' | sed "s|^${HARBOR_PROJECT}/||" | sort)
|
|
|
|
if [[ -z "$repositories" ]]; then
|
|
warning "项目 '${HARBOR_PROJECT}' 中没有镜像"
|
|
return 0
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${BLUE}Harbor 项目 '${HARBOR_PROJECT}' 中的镜像:${NC}"
|
|
echo "--------------------------------------------------"
|
|
|
|
for repo in $repositories; do
|
|
# 获取标签信息
|
|
local tags
|
|
tags=$(curl -s -u "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
|
"${harbor_url}/api/v2.0/projects/${project_id}/repositories/${HARBOR_PROJECT}%2F${repo}/artifacts" | \
|
|
jq -r '.[].tags[0].name' | sort -u | tr '\n' ', ' | sed 's/,$//')
|
|
|
|
printf "%-40s %s\n" "$repo" "${tags:-N/A}"
|
|
done
|
|
echo "--------------------------------------------------"
|
|
}
|
|
|
|
# 清理 Harbor 项目中的镜像
|
|
cleanup_harbor_images() {
|
|
local keep_count="${1:-5}"
|
|
|
|
warning "清理 Harbor 项目 '${HARBOR_PROJECT}' 中的旧镜像 (保留最近 ${keep_count} 个版本)"
|
|
|
|
read -p "确定要继续吗? (y/N): " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
info "清理操作已取消"
|
|
return 0
|
|
fi
|
|
|
|
local harbor_url="http://${HARBOR_DOMAIN}:${HARBOR_PORT}"
|
|
local project_id
|
|
|
|
# 获取项目 ID
|
|
project_id=$(curl -s -u "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
|
"${harbor_url}/api/v2.0/projects?name=${HARBOR_PROJECT}" | \
|
|
jq -r '.[] | select(.name == "'"${HARBOR_PROJECT}"'") | .project_id')
|
|
|
|
if [[ -z "$project_id" ]]; then
|
|
error "项目 '${HARBOR_PROJECT}' 不存在"
|
|
return 1
|
|
fi
|
|
|
|
# 获取仓库列表并清理
|
|
local repositories
|
|
repositories=$(curl -s -u "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
|
"${harbor_url}/api/v2.0/projects/${project_id}/repositories" | \
|
|
jq -r '.[].name')
|
|
|
|
for repo_full in $repositories; do
|
|
local repo_name=$(echo "$repo_full" | sed "s|^${HARBOR_PROJECT}/||")
|
|
|
|
# 获取标签列表
|
|
local artifacts
|
|
artifacts=$(curl -s -u "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
|
"${harbor_url}/api/v2.0/projects/${project_id}/repositories/${repo_full}/artifacts" | \
|
|
jq -r '.[] | select(.tags != null) | "\(.tags[0].name)|\(.created)"')
|
|
|
|
if [[ -n "$artifacts" ]]; then
|
|
# 按创建时间排序,保留最新的 N 个
|
|
local old_tags
|
|
old_tags=$(echo "$artifacts" | sort -r | tail -n +$((keep_count + 1)) | cut -d'|' -f1)
|
|
|
|
for tag in $old_tags; do
|
|
info "删除旧镜像: ${repo_name}:${tag}"
|
|
curl -s -X DELETE -u "${HARBOR_USER}:${HARBOR_PASSWORD}" \
|
|
"${harbor_url}/api/v2.0/projects/${project_id}/repositories/${repo_full}/artifacts/${tag}" > /dev/null || true
|
|
done
|
|
fi
|
|
done
|
|
|
|
success "旧镜像清理完成"
|
|
}
|
|
|
|
# 显示帮助信息
|
|
show_help() {
|
|
cat << EOF
|
|
Harbor Kubernetes 镜像预下载和推送脚本
|
|
|
|
用法: $0 [命令] [参数]
|
|
|
|
命令:
|
|
sync 同步所有镜像到 Harbor
|
|
list 列出 Harbor 项目中的镜像
|
|
cleanup [数量] 清理 Harbor 项目中的旧镜像 (默认保留 5 个)
|
|
config 显示镜像配置文件路径
|
|
help 显示帮助信息
|
|
|
|
环境变量:
|
|
HARBOR_DOMAIN Harbor 域名 (默认: 192.168.1.83)
|
|
HARBOR_PORT Harbor 端口 (默认: 8080)
|
|
HARBOR_USER Harbor 用户名 (默认: admin)
|
|
HARBOR_PASSWORD Harbor 密码 (默认: admin)
|
|
HARBOR_PROJECT Harbor 项目名 (默认: kubernetes)
|
|
CLEANUP_LOCAL_IMAGES 是否清理本地镜像 (默认: false)
|
|
LOG_FILE 日志文件路径
|
|
IMAGES_CONFIG_FILE 镜像配置文件路径
|
|
|
|
镜像配置文件格式:
|
|
<源镜像>:<标签> <目标别名> <描述>
|
|
--multi-arch <源镜像>:<标签> <目标别名> <描述> # 多架构镜像
|
|
|
|
示例:
|
|
$0 sync # 同步所有镜像
|
|
$0 list # 列出 Harbor 中的镜像
|
|
$0 cleanup 10 # 保留最近 10 个版本
|
|
$0 config # 显示配置文件路径
|
|
|
|
配置文件编辑:
|
|
${IMAGES_CONFIG_FILE}
|
|
|
|
EOF
|
|
}
|
|
|
|
# 主函数
|
|
main() {
|
|
local command="${1:-help}"
|
|
|
|
# 设置环境变量
|
|
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
|
|
|
|
case "$command" in
|
|
"sync")
|
|
check_dependencies
|
|
ensure_directories
|
|
create_default_images_config
|
|
check_harbor_connection
|
|
harbor_login
|
|
ensure_harbor_project
|
|
sync_all_images
|
|
;;
|
|
"list")
|
|
check_dependencies
|
|
ensure_directories
|
|
check_harbor_connection
|
|
list_harbor_images
|
|
;;
|
|
"cleanup")
|
|
check_dependencies
|
|
ensure_directories
|
|
check_harbor_connection
|
|
harbor_login
|
|
cleanup_harbor_images "${2:-5}"
|
|
;;
|
|
"config")
|
|
check_dependencies
|
|
ensure_directories
|
|
create_default_images_config
|
|
echo "镜像配置文件: ${IMAGES_CONFIG_FILE}"
|
|
;;
|
|
"help"|"-h"|"--help")
|
|
show_help
|
|
;;
|
|
*)
|
|
error "未知命令: $command"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# 如果脚本被直接执行,调用主函数
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
main "$@"
|
|
fi |