fix: 将 k3s-ansible 作为普通目录添加
This commit is contained in:
521
scripts/complete-automation.sh
Executable file
521
scripts/complete-automation.sh
Executable file
@@ -0,0 +1,521 @@
|
||||
#!/bin/bash
|
||||
# JPD集群完整自动化配置脚本
|
||||
# 包括:Ingress配置、cert-manager、ArgoCD配置、测试应用部署
|
||||
|
||||
set -e
|
||||
|
||||
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 JPD集群完整自动化配置"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 1: 配置Gitea Ingress
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 1/6: 配置Gitea Ingress"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gitea
|
||||
namespace: gitea
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: git.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gitea-http
|
||||
port:
|
||||
number: 3000
|
||||
EOF
|
||||
|
||||
echo "✅ Gitea Ingress配置完成"
|
||||
echo " 访问地址: http://git.jpd.net3w.com"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 2: 配置ArgoCD访问
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 2/6: 配置ArgoCD访问"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 配置ArgoCD为NodePort
|
||||
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort"}}'
|
||||
|
||||
# 创建ArgoCD Ingress
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: argocd-server
|
||||
namespace: argocd
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
traefik.ingress.kubernetes.io/router.middlewares: argocd-stripprefix@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: argocd.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: argocd-server
|
||||
port:
|
||||
number: 80
|
||||
EOF
|
||||
|
||||
ARGOCD_PORT=$(kubectl get svc argocd-server -n argocd -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
ARGOCD_PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
|
||||
|
||||
echo "✅ ArgoCD访问配置完成"
|
||||
echo " NodePort访问: http://149.13.91.216:$ARGOCD_PORT"
|
||||
echo " 域名访问: http://argocd.jpd.net3w.com"
|
||||
echo " 用户名: admin"
|
||||
echo " 密码: $ARGOCD_PASSWORD"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 3: 部署cert-manager
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 3/6: 部署cert-manager"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
|
||||
|
||||
echo "⏳ 等待cert-manager就绪..."
|
||||
sleep 30
|
||||
kubectl wait --for=condition=ready pod -l app=cert-manager -n cert-manager --timeout=300s || true
|
||||
kubectl wait --for=condition=ready pod -l app=webhook -n cert-manager --timeout=300s || true
|
||||
|
||||
# 创建Let's Encrypt ClusterIssuer
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: admin@jpd.net3w.com
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: traefik
|
||||
EOF
|
||||
|
||||
echo "✅ cert-manager部署完成"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 4: 配置HTTPS Ingress
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 4/6: 配置HTTPS Ingress"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 更新Gitea Ingress支持HTTPS
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gitea-https
|
||||
namespace: gitea
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- git.jpd.net3w.com
|
||||
secretName: gitea-tls
|
||||
rules:
|
||||
- host: git.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gitea-http
|
||||
port:
|
||||
number: 3000
|
||||
EOF
|
||||
|
||||
# 更新ArgoCD Ingress支持HTTPS
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: argocd-server-https
|
||||
namespace: argocd
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- argocd.jpd.net3w.com
|
||||
secretName: argocd-server-tls
|
||||
rules:
|
||||
- host: argocd.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: argocd-server
|
||||
port:
|
||||
number: 80
|
||||
EOF
|
||||
|
||||
echo "✅ HTTPS Ingress配置完成"
|
||||
echo " Gitea HTTPS: https://git.jpd.net3w.com"
|
||||
echo " ArgoCD HTTPS: https://argocd.jpd.net3w.com"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 5: 部署测试应用
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 5/6: 部署测试应用"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 创建测试应用命名空间
|
||||
kubectl create namespace demo-app --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 部署Nginx测试应用
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-demo
|
||||
namespace: demo-app
|
||||
labels:
|
||||
app: nginx-demo
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx-demo
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-demo
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumeMounts:
|
||||
- name: html
|
||||
mountPath: /usr/share/nginx/html
|
||||
volumes:
|
||||
- name: html
|
||||
configMap:
|
||||
name: nginx-html
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: nginx-html
|
||||
namespace: demo-app
|
||||
data:
|
||||
index.html: |
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>JPD集群测试应用</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
}
|
||||
h1 {
|
||||
color: #667eea;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.status {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.info {
|
||||
text-align: left;
|
||||
background: #f3f4f6;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.info p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.emoji {
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="emoji">🚀</div>
|
||||
<h1>JPD K3s集群测试应用</h1>
|
||||
<div class="status">✅ 运行正常</div>
|
||||
<div class="info">
|
||||
<p><strong>集群名称:</strong> JPD Cluster</p>
|
||||
<p><strong>部署方式:</strong> Kubernetes Deployment</p>
|
||||
<p><strong>副本数:</strong> 3</p>
|
||||
<p><strong>容器镜像:</strong> nginx:alpine</p>
|
||||
<p><strong>访问域名:</strong> demo.jpd.net3w.com</p>
|
||||
<p><strong>GitOps工具:</strong> ArgoCD</p>
|
||||
<p><strong>Git仓库:</strong> Gitea</p>
|
||||
</div>
|
||||
<p style="margin-top: 20px; color: #6b7280;">
|
||||
主机名: <span id="hostname">加载中...</span>
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
fetch('/hostname.txt')
|
||||
.then(r => r.text())
|
||||
.then(h => document.getElementById('hostname').textContent = h)
|
||||
.catch(() => document.getElementById('hostname').textContent = '未知');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx-demo
|
||||
namespace: demo-app
|
||||
spec:
|
||||
selector:
|
||||
app: nginx-demo
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx-demo
|
||||
namespace: demo-app
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: demo.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: nginx-demo
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx-demo-https
|
||||
namespace: demo-app
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- demo.jpd.net3w.com
|
||||
secretName: nginx-demo-tls
|
||||
rules:
|
||||
- host: demo.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: nginx-demo
|
||||
port:
|
||||
number: 80
|
||||
EOF
|
||||
|
||||
echo "⏳ 等待测试应用就绪..."
|
||||
kubectl wait --for=condition=ready pod -l app=nginx-demo -n demo-app --timeout=120s
|
||||
|
||||
echo "✅ 测试应用部署完成"
|
||||
echo " 访问地址: http://demo.jpd.net3w.com"
|
||||
echo " HTTPS访问: https://demo.jpd.net3w.com"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 6: 部署自动化测试
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 6/6: 部署自动化测试"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 创建自动化测试CronJob
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: health-check
|
||||
namespace: demo-app
|
||||
spec:
|
||||
schedule: "*/5 * * * *" # 每5分钟运行一次
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: curl
|
||||
image: curlimages/curl:latest
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "=== 健康检查开始 ==="
|
||||
echo "时间: \$(date)"
|
||||
echo ""
|
||||
|
||||
# 测试Gitea
|
||||
echo "测试 Gitea..."
|
||||
if curl -f -s http://gitea-http.gitea.svc.cluster.local:3000 > /dev/null; then
|
||||
echo "✅ Gitea: 正常"
|
||||
else
|
||||
echo "❌ Gitea: 异常"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 测试ArgoCD
|
||||
echo "测试 ArgoCD..."
|
||||
if curl -f -s -k http://argocd-server.argocd.svc.cluster.local > /dev/null; then
|
||||
echo "✅ ArgoCD: 正常"
|
||||
else
|
||||
echo "❌ ArgoCD: 异常"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 测试Demo应用
|
||||
echo "测试 Demo应用..."
|
||||
if curl -f -s http://nginx-demo.demo-app.svc.cluster.local > /dev/null; then
|
||||
echo "✅ Demo应用: 正常"
|
||||
else
|
||||
echo "❌ Demo应用: 异常"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 所有服务健康检查通过 ==="
|
||||
restartPolicy: OnFailure
|
||||
EOF
|
||||
|
||||
# 立即运行一次测试
|
||||
kubectl create job --from=cronjob/health-check health-check-manual -n demo-app || true
|
||||
|
||||
echo "✅ 自动化测试部署完成"
|
||||
echo " 测试频率: 每5分钟"
|
||||
echo " 查看测试日志: kubectl logs -n demo-app -l job-name=health-check-manual"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 最终状态检查
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🎉 部署完成!最终状态"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "📊 集群资源:"
|
||||
kubectl get nodes -o wide
|
||||
echo ""
|
||||
|
||||
echo "📦 所有Pod:"
|
||||
kubectl get pods --all-namespaces | grep -E "NAMESPACE|Running|Completed"
|
||||
echo ""
|
||||
|
||||
echo "🌐 所有Ingress:"
|
||||
kubectl get ingress --all-namespaces
|
||||
echo ""
|
||||
|
||||
echo "🔐 访问信息:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "Gitea:"
|
||||
echo " HTTP: http://git.jpd.net3w.com"
|
||||
echo " HTTPS: https://git.jpd.net3w.com"
|
||||
echo " 用户名: gitea_admin"
|
||||
echo " 密码: GitAdmin@2026"
|
||||
echo ""
|
||||
echo "ArgoCD:"
|
||||
echo " HTTP: http://argocd.jpd.net3w.com"
|
||||
echo " HTTPS: https://argocd.jpd.net3w.com"
|
||||
echo " NodePort: http://149.13.91.216:$ARGOCD_PORT"
|
||||
echo " 用户名: admin"
|
||||
echo " 密码: $ARGOCD_PASSWORD"
|
||||
echo ""
|
||||
echo "测试应用:"
|
||||
echo " HTTP: http://demo.jpd.net3w.com"
|
||||
echo " HTTPS: https://demo.jpd.net3w.com"
|
||||
echo ""
|
||||
echo "💡 提示:"
|
||||
echo " - HTTPS证书需要1-2分钟签发"
|
||||
echo " - 自动化测试每5分钟运行一次"
|
||||
echo " - 查看测试日志: kubectl logs -n demo-app -l job-name=health-check-manual"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
52
scripts/create-argocd-app.sh
Executable file
52
scripts/create-argocd-app.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
echo "=== 创建ArgoCD Application ==="
|
||||
|
||||
# 读取配置
|
||||
GIT_REPO=$(yq eval '.git_repo_url' "$CONFIG_FILE")
|
||||
GIT_USERNAME=$(yq eval '.gitea_user_name' "$CONFIG_FILE")
|
||||
GIT_PASSWORD=$(yq eval '.gitea_user_password' "$CONFIG_FILE")
|
||||
|
||||
# 配置Gitea仓库凭证
|
||||
echo "🔐 配置Gitea仓库凭证..."
|
||||
kubectl create secret generic gitea-creds \
|
||||
-n argocd \
|
||||
--from-literal=username="$GIT_USERNAME" \
|
||||
--from-literal=password="$GIT_PASSWORD" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 生成Application配置
|
||||
cat > /tmp/argocd-app.yaml <<EOF
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: demo-app
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: $GIT_REPO
|
||||
targetRevision: main
|
||||
path: manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
EOF
|
||||
|
||||
# 应用配置
|
||||
kubectl apply -f /tmp/argocd-app.yaml
|
||||
|
||||
echo "✅ ArgoCD Application创建成功!"
|
||||
echo "📊 查看状态: kubectl get application -n argocd"
|
||||
echo "🌐 访问ArgoCD查看同步状态"
|
||||
106
scripts/create-nginx-argocd-app.sh
Executable file
106
scripts/create-nginx-argocd-app.sh
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
echo "=== 创建Nginx应用的ArgoCD Application ==="
|
||||
|
||||
# 读取配置
|
||||
NGINX_GIT_REPO=$(yq eval '.nginx_app_git_repo_url' "$CONFIG_FILE")
|
||||
GIT_USERNAME=$(yq eval '.gitea_user_name' "$CONFIG_FILE")
|
||||
GIT_PASSWORD=$(yq eval '.gitea_user_password' "$CONFIG_FILE")
|
||||
|
||||
# 配置Gitea仓库凭证(如果不存在)
|
||||
echo "🔐 配置Gitea仓库凭证..."
|
||||
kubectl create secret generic gitea-creds \
|
||||
-n argocd \
|
||||
--from-literal=username="$GIT_USERNAME" \
|
||||
--from-literal=password="$GIT_PASSWORD" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 生成Application配置
|
||||
cat > /tmp/nginx-argocd-app.yaml <<EOF
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: nginx-app
|
||||
namespace: argocd
|
||||
labels:
|
||||
app: nginx-test
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: $NGINX_GIT_REPO
|
||||
targetRevision: main
|
||||
path: manifests
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
# 健康检查配置
|
||||
ignoreDifferences:
|
||||
- group: apps
|
||||
kind: Deployment
|
||||
jsonPointers:
|
||||
- /spec/replicas
|
||||
EOF
|
||||
|
||||
# 应用配置
|
||||
echo "📝 创建ArgoCD Application..."
|
||||
kubectl apply -f /tmp/nginx-argocd-app.yaml
|
||||
|
||||
echo ""
|
||||
echo "✅ ArgoCD Application创建成功!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📊 查看状态:"
|
||||
echo " kubectl get application nginx-app -n argocd"
|
||||
echo ""
|
||||
echo "📝 查看详细信息:"
|
||||
echo " kubectl describe application nginx-app -n argocd"
|
||||
echo ""
|
||||
echo "🌐 访问ArgoCD查看同步状态:"
|
||||
echo " https://argocd.jpc.net3w.com"
|
||||
echo ""
|
||||
echo "⏳ 等待同步完成(约3分钟)..."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# 等待并监控同步状态
|
||||
echo ""
|
||||
echo "🔍 监控同步状态..."
|
||||
for i in {1..12}; do
|
||||
sleep 5
|
||||
STATUS=$(kubectl get application nginx-app -n argocd -o jsonpath='{.status.sync.status}' 2>/dev/null || echo "Unknown")
|
||||
HEALTH=$(kubectl get application nginx-app -n argocd -o jsonpath='{.status.health.status}' 2>/dev/null || echo "Unknown")
|
||||
echo "[$i/12] Sync: $STATUS | Health: $HEALTH"
|
||||
|
||||
if [ "$STATUS" = "Synced" ] && [ "$HEALTH" = "Healthy" ]; then
|
||||
echo ""
|
||||
echo "✅ 同步完成!应用已成功部署"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🎉 部署完成!"
|
||||
echo ""
|
||||
echo "📊 验证部署:"
|
||||
echo " kubectl get pods -l app=nginx-test -n default"
|
||||
echo " kubectl get svc nginx-test -n default"
|
||||
echo " kubectl get ingress nginx-test -n default"
|
||||
echo ""
|
||||
echo "🌐 访问应用:"
|
||||
echo " https://ng.jpc.net3w.com"
|
||||
echo ""
|
||||
echo "💡 提示:"
|
||||
echo " - 修改Git仓库中的配置,ArgoCD会自动同步"
|
||||
echo " - 查看ArgoCD UI了解详细的同步状态"
|
||||
echo " - 首次HTTPS访问需等待证书签发(1-2分钟)"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
243
scripts/deploy-all-on-master.sh
Executable file
243
scripts/deploy-all-on-master.sh
Executable file
@@ -0,0 +1,243 @@
|
||||
#!/bin/bash
|
||||
# JPD集群完整部署脚本 - 在Master节点上运行
|
||||
# 使用方法: bash deploy-all-on-master.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 JPD集群GitOps自动化部署"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 检查是否在master节点上
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
echo "❌ kubectl未找到,请确保在K3s master节点上运行此脚本"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 配置kubectl
|
||||
echo "📝 配置kubectl..."
|
||||
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
||||
sudo chmod 644 /etc/rancher/k3s/k3s.yaml
|
||||
|
||||
# 验证集群
|
||||
echo "🔍 验证集群状态..."
|
||||
kubectl get nodes -o wide
|
||||
echo ""
|
||||
|
||||
# 检查Helm
|
||||
if ! command -v helm &> /dev/null; then
|
||||
echo "📦 安装Helm..."
|
||||
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||
echo "✅ Helm安装完成"
|
||||
else
|
||||
echo "✅ Helm已安装"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 1/4: 部署Gitea"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 添加Gitea Helm仓库
|
||||
echo "📝 添加Gitea Helm仓库..."
|
||||
helm repo add gitea-charts https://dl.gitea.com/charts/
|
||||
helm repo update
|
||||
|
||||
# 创建gitea命名空间
|
||||
echo "📝 创建gitea命名空间..."
|
||||
kubectl create namespace gitea --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 部署Gitea
|
||||
echo "🚀 部署Gitea..."
|
||||
helm upgrade --install gitea gitea-charts/gitea \
|
||||
--namespace gitea \
|
||||
--set gitea.admin.username=gitea_admin \
|
||||
--set gitea.admin.password=GitAdmin@2026 \
|
||||
--set gitea.admin.email=admin@jpd.net3w.com \
|
||||
--set service.http.type=NodePort \
|
||||
--set service.http.nodePort=30080 \
|
||||
--set postgresql-ha.enabled=true \
|
||||
--set redis-cluster.enabled=true \
|
||||
--wait --timeout=10m
|
||||
|
||||
echo "✅ Gitea部署完成"
|
||||
echo ""
|
||||
|
||||
# 等待Gitea就绪
|
||||
echo "⏳ 等待Gitea Pod就绪..."
|
||||
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=gitea -n gitea --timeout=300s
|
||||
|
||||
# 获取Gitea访问信息
|
||||
GITEA_PORT=$(kubectl get svc gitea-http -n gitea -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
|
||||
echo "✅ Gitea访问地址: http://$NODE_IP:$GITEA_PORT"
|
||||
echo " 域名访问: http://git.jpd.net3w.com"
|
||||
echo ""
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 2/4: 部署ArgoCD"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 创建argocd命名空间
|
||||
echo "📝 创建argocd命名空间..."
|
||||
kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 部署ArgoCD
|
||||
echo "🚀 部署ArgoCD..."
|
||||
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
|
||||
|
||||
# 等待ArgoCD就绪
|
||||
echo "⏳ 等待ArgoCD Pod就绪..."
|
||||
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=argocd-server -n argocd --timeout=300s
|
||||
|
||||
# 修改ArgoCD服务为NodePort
|
||||
echo "📝 配置ArgoCD NodePort..."
|
||||
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort"}}'
|
||||
|
||||
# 获取ArgoCD访问信息
|
||||
ARGOCD_PORT=$(kubectl get svc argocd-server -n argocd -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
ARGOCD_PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
|
||||
|
||||
echo "✅ ArgoCD部署完成"
|
||||
echo " 访问地址: https://$NODE_IP:$ARGOCD_PORT"
|
||||
echo " 域名访问: https://argocd.jpd.net3w.com"
|
||||
echo " 用户名: admin"
|
||||
echo " 密码: $ARGOCD_PASSWORD"
|
||||
echo ""
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 3/4: 部署cert-manager"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 部署cert-manager
|
||||
echo "🚀 部署cert-manager..."
|
||||
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
|
||||
|
||||
# 等待cert-manager就绪
|
||||
echo "⏳ 等待cert-manager Pod就绪..."
|
||||
kubectl wait --for=condition=ready pod -l app=cert-manager -n cert-manager --timeout=300s
|
||||
kubectl wait --for=condition=r app=webhook -n cert-manager --timeout=300s
|
||||
|
||||
# 创建Let's Encrypt ClusterIssuer
|
||||
echo "📝 配置Let's Encrypt..."
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: admin@jpd.net3w.com
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: traefik
|
||||
EOF
|
||||
|
||||
echo "✅ cert-manager部署完成"
|
||||
echo ""
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 4/4: 配置Ingress"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 创建Gitea Ingress
|
||||
echo "📝 创建Gitea Ingress..."
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gitea
|
||||
namespace: gitea
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- git.jpd.net3w.com
|
||||
secretName: gitea-tls
|
||||
rules:
|
||||
- host: git.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gitea-http
|
||||
port:
|
||||
number: 3000
|
||||
EOF
|
||||
|
||||
# 创建ArgoCD Ingress
|
||||
echo "📝 创建ArgoCD I"
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: argocd-server
|
||||
namespace: argocd
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- argocd.jpd.net3w.com
|
||||
secretName: argocd-server-tls
|
||||
rules:
|
||||
- host: argocd.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: argocd-server
|
||||
port:
|
||||
number: 443
|
||||
EOF
|
||||
|
||||
echo "✅ Ingress配置完成"
|
||||
echo ""
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🎉 部署完成!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "📊 部署摘要:"
|
||||
echo " ✅ Gitea: http://git.jpd.net3w.com"
|
||||
echo " ✅ ArgoCD: https://argocd.jpd.net3w.com"
|
||||
echo " ✅ cert-manager: 已配置Let's Encrypt"
|
||||
echo ""
|
||||
echo "🔑 访问凭证:"
|
||||
echo " Gitea:"
|
||||
echo " - 用户名: gitea_admin"
|
||||
echo " - 密码: GitAdmin@2026"
|
||||
echo ""
|
||||
echo " ArgoCD:"
|
||||
echo " - 用户名: admin"
|
||||
echo " - 密码: $ARGOCD_PASSWORD"
|
||||
echo ""
|
||||
echo "📝 验证命令:"
|
||||
echo " kubectl get pods --all-namespaces"
|
||||
echo " kubectl get ingress --all-namespaces"
|
||||
echo " kubectl get certificate --all-namespaces"
|
||||
echo ""
|
||||
echo "💡 提示:"
|
||||
echo " - 确保DNS已配置: *.jpd.net3w.com -> 149.13.91.216"
|
||||
echo " - 首次HTTPS访问需等待1-2分钟证书签发"
|
||||
echo " - 可以通过NodePort直接访问服务"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
333
scripts/deploy-all.sh
Executable file
333
scripts/deploy-all.sh
Executable file
@@ -0,0 +1,333 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Load common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Source common library
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
|
||||
# Configuration
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
# Step definitions
|
||||
STEPS=(
|
||||
"check_prerequisites:检查前置条件"
|
||||
"generate_inventory:生成Ansible Inventory"
|
||||
"deploy_k3s:部署K3s集群"
|
||||
"deploy_gitea:部署Gitea"
|
||||
"setup_gitea:初始化Gitea"
|
||||
"deploy_argocd:部署ArgoCD"
|
||||
"deploy_https:配置HTTPS"
|
||||
"create_demo_app:创建示例应用"
|
||||
)
|
||||
|
||||
# Step functions
|
||||
check_prerequisites() {
|
||||
log_step "检查前置条件"
|
||||
|
||||
# Check configuration file
|
||||
check_config_file "$CONFIG_FILE" || return 1
|
||||
|
||||
# Check required tools
|
||||
check_required_tools || return 1
|
||||
|
||||
# Check network connectivity
|
||||
check_network_with_retry "https://www.google.com" 3 || {
|
||||
log_warn "Network connectivity check failed, but continuing..."
|
||||
}
|
||||
|
||||
# Install Python YAML library
|
||||
if ! python3 -c "import yaml" 2>/dev/null; then
|
||||
log "Installing python3-yaml..."
|
||||
sudo apt update && sudo apt install -y python3-yaml
|
||||
fi
|
||||
|
||||
log "✓ All prerequisites checked"
|
||||
return 0
|
||||
}
|
||||
|
||||
generate_inventory() {
|
||||
log_step "生成Ansible Inventory"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/generate-inventory.py" ]; then
|
||||
log_error "generate-inventory.py not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
python3 "$SCRIPT_DIR/generate-inventory.py" || return 1
|
||||
|
||||
log "✓ Ansible inventory generated"
|
||||
return 0
|
||||
}
|
||||
|
||||
deploy_k3s() {
|
||||
log_step "部署K3s集群"
|
||||
|
||||
if [ ! -d "$PROJECT_DIR/k3s-ansible" ]; then
|
||||
log "Cloning k3s-ansible repository..."
|
||||
cd "$PROJECT_DIR"
|
||||
git clone https://github.com/k3s-io/k3s-ansible.git || return 1
|
||||
fi
|
||||
|
||||
# Check if kubectl is already available and cluster is running
|
||||
if check_kubectl; then
|
||||
log "K3s cluster is already running, skipping deployment"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Running Ansible playbook..."
|
||||
cd "$PROJECT_DIR/k3s-ansible"
|
||||
|
||||
ansible-playbook site.yml \
|
||||
-i inventory/hosts.ini \
|
||||
-e "@$CONFIG_FILE" || return 1
|
||||
|
||||
# Configure kubectl
|
||||
log "Configuring kubectl..."
|
||||
mkdir -p ~/.kube
|
||||
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
|
||||
sudo chown $USER:$USER ~/.kube/config
|
||||
|
||||
# Verify cluster
|
||||
log "Verifying cluster..."
|
||||
sleep 10
|
||||
kubectl get nodes || return 1
|
||||
|
||||
log "✓ K3s cluster deployed successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
deploy_gitea() {
|
||||
log_step "部署Gitea"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/deploy-gitea.sh" ]; then
|
||||
log_error "deploy-gitea.sh not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if Gitea is already deployed
|
||||
if kubectl get namespace gitea &>/dev/null && \
|
||||
kubectl get deployment gitea -n gitea &>/dev/null 2>&1; then
|
||||
log "Gitea is already deployed, skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
bash "$SCRIPT_DIR/deploy-gitea.sh" || return 1
|
||||
|
||||
log "✓ Gitea deployed successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
setup_gitea() {
|
||||
log_step "初始化Gitea"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/setup-gitea.sh" ]; then
|
||||
log_error "setup-gitea.sh not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
bash "$SCRIPT_DIR/setup-gitea.sh" || return 1
|
||||
|
||||
log "✓ Gitea initialized successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
deploy_argocd() {
|
||||
log_step "部署ArgoCD"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/deploy-argocd.sh" ]; then
|
||||
log_error "deploy-argocd.sh not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if ArgoCD is already deployed
|
||||
if kubectl get namespace argocd &>/dev/null && \
|
||||
kubectl get deployment argocd-server -n argocd &>/dev/null 2>&1; then
|
||||
log "ArgoCD is already deployed, skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
bash "$SCRIPT_DIR/deploy-argocd.sh" || return 1
|
||||
|
||||
log "✓ ArgoCD deployed successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
deploy_https() {
|
||||
log_step "配置HTTPS"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/deploy-https.sh" ]; then
|
||||
log_warn "deploy-https.sh not found, skipping HTTPS configuration"
|
||||
return 0
|
||||
fi
|
||||
|
||||
bash "$SCRIPT_DIR/deploy-https.sh" || {
|
||||
log_warn "HTTPS configuration failed, but continuing..."
|
||||
return 0
|
||||
}
|
||||
|
||||
log "✓ HTTPS configured successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
create_demo_app() {
|
||||
log_step "创建示例应用"
|
||||
|
||||
if [ ! -f "$SCRIPT_DIR/create-argocd-app.sh" ]; then
|
||||
log_warn "create-argocd-app.sh not found, skipping demo app creation"
|
||||
return 0
|
||||
fi
|
||||
|
||||
bash "$SCRIPT_DIR/create-argocd-app.sh" || {
|
||||
log_warn "Demo app creation failed, but continuing..."
|
||||
return 0
|
||||
}
|
||||
|
||||
log "✓ Demo app created successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Execute step
|
||||
execute_step() {
|
||||
local step_name="$1"
|
||||
|
||||
if type "$step_name" &>/dev/null; then
|
||||
"$step_name"
|
||||
return $?
|
||||
else
|
||||
log_error "Step function not found: $step_name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
echo "=========================================="
|
||||
echo " K3s集群自动化部署"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
log "开始部署流程"
|
||||
log "日志文件: $LOG_FILE"
|
||||
log "状态文件: $STATE_FILE"
|
||||
echo ""
|
||||
|
||||
local failed_steps=()
|
||||
local completed_steps=()
|
||||
local skipped_steps=()
|
||||
|
||||
for step in "${STEPS[@]}"; do
|
||||
step_name="${step%%:*}"
|
||||
step_desc="${step##*:}"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
|
||||
if is_step_completed "$step_name"; then
|
||||
log "✓ 跳过已完成的步骤: $step_desc"
|
||||
skipped_steps+=("$step_desc")
|
||||
continue
|
||||
fi
|
||||
|
||||
log_step "执行步骤: $step_desc"
|
||||
|
||||
if execute_step "$step_name"; then
|
||||
mark_step_completed "$step_name"
|
||||
log "✓ 完成: $step_desc"
|
||||
completed_steps+=("$step_desc")
|
||||
else
|
||||
log_error "✗ 失败: $step_desc"
|
||||
failed_steps+=("$step_desc")
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 部署失败"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
log_error "步骤失败: $step_desc"
|
||||
log_error "请检查日志文件: $LOG_FILE"
|
||||
log_error "修复问题后,可以重新运行此脚本继续部署"
|
||||
echo ""
|
||||
|
||||
print_summary
|
||||
echo "已完成步骤: ${#completed_steps[@]}"
|
||||
for s in "${completed_steps[@]}"; do
|
||||
echo " ✓ $s"
|
||||
done
|
||||
echo ""
|
||||
echo "跳过步骤: ${#skipped_steps[@]}"
|
||||
for s in "${skipped_steps[@]}"; do
|
||||
echo " - $s"
|
||||
done
|
||||
echo ""
|
||||
echo "失败步骤: ${#failed_steps[@]}"
|
||||
for s in "${failed_steps[@]}"; do
|
||||
echo " ✗ $s"
|
||||
done
|
||||
echo ""
|
||||
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 部署完成!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
print_summary
|
||||
echo "总步骤数: ${#STEPS[@]}"
|
||||
echo "已完成: ${#completed_steps[@]}"
|
||||
echo "已跳过: ${#skipped_steps[@]}"
|
||||
echo ""
|
||||
|
||||
if [ ${#completed_steps[@]} -gt 0 ]; then
|
||||
echo "本次完成的步骤:"
|
||||
for s in "${completed_steps[@]}"; do
|
||||
echo " ✓ $s"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ ${#skipped_steps[@]} -gt 0 ]; then
|
||||
echo "跳过的步骤:"
|
||||
for s in "${skipped_steps[@]}"; do
|
||||
echo " - $s"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
log "✓ K3s集群部署完成!"
|
||||
echo ""
|
||||
echo "下一步操作:"
|
||||
echo " 1. 验证部署: ./scripts/verify-deployment.sh"
|
||||
echo " 2. 查看集群状态: kubectl get nodes"
|
||||
echo " 3. 查看所有Pod: kubectl get pods -A"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Handle script arguments
|
||||
case "${1:-}" in
|
||||
--reset)
|
||||
log "重置部署状态..."
|
||||
reset_deployment_state
|
||||
log "状态已重置,可以重新开始部署"
|
||||
exit 0
|
||||
;;
|
||||
--help|-h)
|
||||
echo "用法: $0 [选项]"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " --reset 重置部署状态,从头开始"
|
||||
echo " --help 显示此帮助信息"
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Run main function
|
||||
main
|
||||
135
scripts/deploy-argocd.sh
Executable file
135
scripts/deploy-argocd.sh
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
# Source common library if available
|
||||
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
# Fallback logging functions
|
||||
log() { echo "[INFO] $1"; }
|
||||
log_error() { echo "[ERROR] $1" >&2; }
|
||||
log_warn() { echo "[WARN] $1"; }
|
||||
fi
|
||||
|
||||
log "=== 部署ArgoCD ==="
|
||||
|
||||
# Check and install required tools
|
||||
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
||||
ensure_yq || exit 1
|
||||
ensure_htpasswd || exit 1
|
||||
else
|
||||
# Fallback: Install yq with retry
|
||||
if ! command -v yq &> /dev/null; then
|
||||
log "安装yq..."
|
||||
for attempt in 1 2 3; do
|
||||
if sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 && \
|
||||
sudo chmod +x /usr/local/bin/yq; then
|
||||
log "✓ yq安装成功"
|
||||
break
|
||||
else
|
||||
log_warn "yq安装失败 (尝试 $attempt/3)"
|
||||
[ $attempt -lt 3 ] && sleep 5
|
||||
fi
|
||||
done
|
||||
|
||||
if ! command -v yq &> /dev/null; then
|
||||
log_error "yq安装失败,请手动安装"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install htpasswd if not present
|
||||
if ! command -v htpasswd &> /dev/null; then
|
||||
log "安装htpasswd (apache2-utils)..."
|
||||
if sudo apt update && sudo apt install -y apache2-utils; then
|
||||
log "✓ htpasswd安装成功"
|
||||
else
|
||||
log_error "htpasswd安装失败,请手动安装: sudo apt install apache2-utils"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 读取配置变量
|
||||
ARGOCD_DOMAIN=$(yq eval '.argocd_domain' "$CONFIG_FILE")
|
||||
ARGOCD_PASSWORD=$(yq eval '.argocd_admin_password' "$CONFIG_FILE")
|
||||
|
||||
# 创建命名空间
|
||||
kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 安装ArgoCD with retry
|
||||
log "安装ArgoCD..."
|
||||
ARGOCD_MANIFEST_URL="https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
||||
retry 3 5 "kubectl apply -n argocd -f $ARGOCD_MANIFEST_URL" || {
|
||||
log_error "ArgoCD安装失败"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
for attempt in 1 2 3; do
|
||||
if kubectl apply -n argocd -f "$ARGOCD_MANIFEST_URL"; then
|
||||
log "✓ ArgoCD清单应用成功"
|
||||
break
|
||||
else
|
||||
log_warn "ArgoCD清单应用失败 (尝试 $attempt/3)"
|
||||
[ $attempt -lt 3 ] && sleep 5
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 等待就绪
|
||||
log "等待ArgoCD就绪..."
|
||||
kubectl wait --for=condition=available --timeout=600s deployment/argocd-server -n argocd || {
|
||||
log_error "ArgoCD部署超时"
|
||||
log_error "请检查: kubectl get pods -n argocd"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 配置NodePort访问
|
||||
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort"}}' || {
|
||||
log_warn "NodePort配置可能已存在"
|
||||
}
|
||||
|
||||
# 更新admin密码
|
||||
log "设置admin密码..."
|
||||
BCRYPT_PASSWORD=$(htpasswd -nbBC 10 "" "$ARGOCD_PASSWORD" | tr -d ':\n' | sed 's/$2y/$2a/')
|
||||
|
||||
if [ -z "$BCRYPT_PASSWORD" ]; then
|
||||
log_error "密码加密失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
kubectl -n argocd patch secret argocd-secret \
|
||||
-p "{\"stringData\": {\"admin.password\": \"$BCRYPT_PASSWORD\", \"admin.passwordMtime\": \"$(date +%FT%T%Z)\"}}" || {
|
||||
log_error "密码设置失败"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 重启argocd-server
|
||||
log "重启ArgoCD服务器..."
|
||||
kubectl -n argocd rollout restart deployment argocd-server
|
||||
kubectl -n argocd rollout status deployment argocd-server --timeout=300s || {
|
||||
log_error "ArgoCD服务器重启超时"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 获取访问信息
|
||||
NODEPORT=$(kubectl get svc argocd-server -n argocd -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}')
|
||||
if [ -z "$NODE_IP" ]; then
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
|
||||
fi
|
||||
|
||||
log "=== ArgoCD部署完成 ==="
|
||||
echo "🌐 访问地址: https://$NODE_IP:$NODEPORT"
|
||||
echo "🌐 域名访问: https://$ARGOCD_DOMAIN (需配置Ingress)"
|
||||
echo "👤 用户名: admin"
|
||||
echo "🔑 密码: $ARGOCD_PASSWORD"
|
||||
echo ""
|
||||
log "提示: 首次访问可能需要接受自签名证书"
|
||||
|
||||
70
scripts/deploy-gitea.sh
Executable file
70
scripts/deploy-gitea.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
echo "=== 部署Gitea私有Git服务器 ==="
|
||||
|
||||
# 安装yq(如果未安装)
|
||||
if ! command -v yq &> /dev/null; then
|
||||
echo "📦 安装yq..."
|
||||
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
|
||||
sudo chmod +x /usr/local/bin/yq
|
||||
fi
|
||||
|
||||
# 安装Helm(如果未安装)
|
||||
if ! command -v helm &> /dev/null; then
|
||||
echo "📦 安装Helm..."
|
||||
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
|
||||
fi
|
||||
|
||||
# 读取配置
|
||||
GITEA_DOMAIN=$(yq eval '.gitea_domain' "$CONFIG_FILE")
|
||||
GITEA_ADMIN_USER=$(yq eval '.gitea_admin_user' "$CONFIG_FILE")
|
||||
GITEA_ADMIN_PASSWORD=$(yq eval '.gitea_admin_password' "$CONFIG_FILE")
|
||||
GITEA_ADMIN_EMAIL=$(yq eval '.gitea_admin_email' "$CONFIG_FILE")
|
||||
|
||||
# 创建命名空间
|
||||
kubectl create namespace gitea --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 添加Gitea Helm仓库
|
||||
helm repo add gitea-charts https://dl.gitea.com/charts/
|
||||
helm repo update
|
||||
|
||||
# 部署Gitea
|
||||
echo "📦 部署Gitea..."
|
||||
helm upgrade --install gitea gitea-charts/gitea \
|
||||
--namespace gitea \
|
||||
--set gitea.admin.username="$GITEA_ADMIN_USER" \
|
||||
--set gitea.admin.password="$GITEA_ADMIN_PASSWORD" \
|
||||
--set gitea.admin.email="$GITEA_ADMIN_EMAIL" \
|
||||
--set service.http.type=NodePort \
|
||||
--set service.ssh.type=NodePort \
|
||||
--set gitea.config.server.DOMAIN="$GITEA_DOMAIN" \
|
||||
--set gitea.config.server.ROOT_URL="http://$GITEA_DOMAIN" \
|
||||
--set persistence.enabled=true \
|
||||
--set persistence.size=10Gi \
|
||||
--wait --timeout=10m
|
||||
|
||||
# 等待Gitea就绪
|
||||
echo "⏳ 等待Gitea就绪..."
|
||||
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=gitea -n gitea --timeout=600s
|
||||
|
||||
# 获取访问信息
|
||||
HTTP_NODEPORT=$(kubectl get svc gitea-http -n gitea -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
SSH_NODEPORT=$(kubectl get svc gitea-ssh -n gitea -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}')
|
||||
if [ -z "$NODE_IP" ]; then
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
|
||||
fi
|
||||
|
||||
echo "=== Gitea部署完成 ==="
|
||||
echo "🌐 HTTP访问: http://$NODE_IP:$HTTP_NODEPORT"
|
||||
echo "🌐 域名访问: http://$GITEA_DOMAIN (需配置Ingress)"
|
||||
echo "🔐 SSH端口: $SSH_NODEPORT"
|
||||
echo "👤 管理员用户: $GITEA_ADMIN_USER"
|
||||
echo "🔑 管理员密码: $GITEA_ADMIN_PASSWORD"
|
||||
echo ""
|
||||
echo "⚠️ 请运行 ./scripts/setup-gitea.sh 完成初始化配置"
|
||||
261
scripts/deploy-https.sh
Executable file
261
scripts/deploy-https.sh
Executable file
@@ -0,0 +1,261 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
# Source common library if available
|
||||
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
log() { echo "[INFO] $1"; }
|
||||
log_error() { echo "[ERROR] $1" >&2; }
|
||||
log_warn() { echo "[WARN] $1"; }
|
||||
fi
|
||||
|
||||
log "=== 配置HTTPS证书 ==="
|
||||
echo ""
|
||||
|
||||
# Ensure yq is available
|
||||
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
||||
ensure_yq || exit 1
|
||||
else
|
||||
if ! command -v yq &> /dev/null; then
|
||||
log_error "yq未安装,请先运行: sudo apt install yq"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Read configuration
|
||||
ARGOCD_DOMAIN=$(yq eval '.argocd_domain' "$CONFIG_FILE")
|
||||
GITEA_DOMAIN=$(yq eval '.gitea_domain' "$CONFIG_FILE")
|
||||
DOMAIN_NAME=$(yq eval '.domain_name' "$CONFIG_FILE")
|
||||
|
||||
log "域名配置:"
|
||||
echo " ArgoCD: $ARGOCD_DOMAIN"
|
||||
echo " Gitea: $GITEA_DOMAIN"
|
||||
echo " 主域名: $DOMAIN_NAME"
|
||||
echo ""
|
||||
|
||||
# Step 1: Install cert-manager CRDs
|
||||
log "步骤 1/4: 安装cert-manager CRDs..."
|
||||
|
||||
CERT_MANAGER_VERSION="v1.13.3"
|
||||
CERT_MANAGER_CRD_URL="https://github.com/cert-manager/cert-manager/releases/download/${CERT_MANAGER_VERSION}/cert-manager.crds.yaml"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
||||
retry 3 5 "kubectl apply -f $CERT_MANAGER_CRD_URL" || {
|
||||
log_error "cert-manager CRDs安装失败"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
kubectl apply -f "$CERT_MANAGER_CRD_URL" || {
|
||||
log_error "cert-manager CRDs安装失败"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
log "✓ cert-manager CRDs安装成功"
|
||||
echo ""
|
||||
|
||||
# Step 2: Install cert-manager
|
||||
log "步骤 2/4: 安装cert-manager..."
|
||||
|
||||
kubectl create namespace cert-manager --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
CERT_MANAGER_URL="https://github.com/cert-manager/cert-manager/releases/download/${CERT_MANAGER_VERSION}/cert-manager.yaml"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
||||
retry 3 5 "kubectl apply -f $CERT_MANAGER_URL" || {
|
||||
log_error "cert-manager安装失败"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
kubectl apply -f "$CERT_MANAGER_URL" || {
|
||||
log_error "cert-manager安装失败"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
log "等待cert-manager就绪..."
|
||||
kubectl wait --for=condition=available --timeout=300s deployment/cert-manager -n cert-manager || {
|
||||
log_error "cert-manager部署超时"
|
||||
exit 1
|
||||
}
|
||||
|
||||
kubectl wait --for=condition=available --timeout=300s deployment/cert-manager-webhook -n cert-manager || {
|
||||
log_error "cert-manager-webhook部署超时"
|
||||
exit 1
|
||||
}
|
||||
|
||||
log "✓ cert-manager安装成功"
|
||||
echo ""
|
||||
|
||||
# Step 3: Create ClusterIssuers
|
||||
log "步骤 3/4: 创建Let's Encrypt ClusterIssuers..."
|
||||
|
||||
# Create staging issuer (for testing)
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-staging
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
email: admin@${DOMAIN_NAME}
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-staging
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: traefik
|
||||
EOF
|
||||
|
||||
log "✓ Staging ClusterIssuer创建成功"
|
||||
|
||||
# Create production issuer
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: admin@${DOMAIN_NAME}
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: traefik
|
||||
EOF
|
||||
|
||||
log "✓ Production ClusterIssuer创建成功"
|
||||
echo ""
|
||||
|
||||
# Wait for ClusterIssuers to be ready
|
||||
log "等待ClusterIssuers就绪..."
|
||||
sleep 5
|
||||
|
||||
if kubectl get clusterissuer letsencrypt-staging -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | grep -q "True"; then
|
||||
log "✓ Staging ClusterIssuer就绪"
|
||||
else
|
||||
log_warn "Staging ClusterIssuer可能未就绪,请检查: kubectl describe clusterissuer letsencrypt-staging"
|
||||
fi
|
||||
|
||||
if kubectl get clusterissuer letsencrypt-prod -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | grep -q "True"; then
|
||||
log "✓ Production ClusterIssuer就绪"
|
||||
else
|
||||
log_warn "Production ClusterIssuer可能未就绪,请检查: kubectl describe clusterissuer letsencrypt-prod"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 4: Create HTTPS Ingresses
|
||||
log "步骤 4/4: 创建HTTPS Ingress..."
|
||||
|
||||
# ArgoCD HTTPS Ingress
|
||||
if kubectl get namespace argocd &>/dev/null; then
|
||||
log "创建ArgoCD HTTPS Ingress..."
|
||||
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: argocd-server-https
|
||||
namespace: argocd
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- ${ARGOCD_DOMAIN}
|
||||
secretName: argocd-tls-cert
|
||||
rules:
|
||||
- host: ${ARGOCD_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: argocd-server
|
||||
port:
|
||||
number: 80
|
||||
EOF
|
||||
|
||||
log "✓ ArgoCD HTTPS Ingress创建成功"
|
||||
else
|
||||
log_warn "ArgoCD未部署,跳过Ingress创建"
|
||||
fi
|
||||
|
||||
# Gitea HTTPS Ingress
|
||||
if kubectl get namespace gitea &>/dev/null; then
|
||||
log "创建Gitea HTTPS Ingress..."
|
||||
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gitea-https
|
||||
namespace: gitea
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- ${GITEA_DOMAIN}
|
||||
secretName: gitea-tls-cert
|
||||
rules:
|
||||
- host: ${GITEA_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gitea-http
|
||||
port:
|
||||
number: 3000
|
||||
EOF
|
||||
|
||||
log "✓ Gitea HTTPS Ingress创建成功"
|
||||
else
|
||||
log_warn "Gitea未部署,跳过Ingress创建"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log "=== HTTPS配置完成 ==="
|
||||
echo ""
|
||||
|
||||
log "证书申请状态检查:"
|
||||
echo " 查看证书: kubectl get certificate -A"
|
||||
echo " 查看ClusterIssuer: kubectl get clusterissuer"
|
||||
echo ""
|
||||
|
||||
log "注意事项:"
|
||||
echo " 1. 证书申请需要几分钟时间"
|
||||
echo " 2. 确保DNS已正确解析到集群IP"
|
||||
echo " 3. 确保80端口可从外网访问(用于HTTP-01验证)"
|
||||
echo " 4. 首次使用建议先用staging测试,避免触发速率限制"
|
||||
echo ""
|
||||
|
||||
log "验证HTTPS访问:"
|
||||
echo " ArgoCD: https://${ARGOCD_DOMAIN}"
|
||||
echo " Gitea: https://${GITEA_DOMAIN}"
|
||||
echo ""
|
||||
|
||||
log "故障排查:"
|
||||
echo " 查看cert-manager日志: kubectl logs -n cert-manager deployment/cert-manager"
|
||||
echo " 查看证书详情: kubectl describe certificate <cert-name> -n <namespace>"
|
||||
echo " 查看证书请求: kubectl get certificaterequest -A"
|
||||
echo ""
|
||||
186
scripts/deploy-nginx-app.sh
Executable file
186
scripts/deploy-nginx-app.sh
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 Nginx测试应用 - 自动化部署"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 检查依赖
|
||||
echo "🔍 检查依赖..."
|
||||
command -v kubectl >/dev/null 2>&1 || { echo "❌ kubectl未安装"; exit 1; }
|
||||
command -v yq >/dev/null 2>&1 || { echo "❌ yq未安装"; exit 1; }
|
||||
echo "✅ 依赖检查通过"
|
||||
echo ""
|
||||
|
||||
# 检查kubectl连接
|
||||
echo "🔍 检查K3s集群连接..."
|
||||
if ! kubectl cluster-info >/dev/null 2>&1; then
|
||||
echo "❌ 无法连接到K3s集群"
|
||||
echo "💡 请确保已配置kubectl访问权限"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ K3s集群连接正常"
|
||||
echo ""
|
||||
|
||||
# 检查Gitea是否运行
|
||||
echo "🔍 检查Gitea服务..."
|
||||
if ! kubectl get svc gitea-http -n gitea >/dev/null 2>&1; then
|
||||
echo "❌ Gitea服务未运行"
|
||||
echo "💡 请先运行: ./scripts/deploy-gitea.sh"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Gitea服务运行正常"
|
||||
echo ""
|
||||
|
||||
# 检查ArgoCD是否运行
|
||||
echo "🔍 检查ArgoCD服务..."
|
||||
if ! kubectl get namespace argocd >/dev/null 2>&1; then
|
||||
echo "❌ ArgoCD未安装"
|
||||
echo "💡 请先运行: ./scripts/deploy-argocd.sh"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ ArgoCD服务运行正常"
|
||||
echo ""
|
||||
|
||||
# 步骤1: 创建Gitea仓库
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 1/3: 创建Gitea仓库"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 读取配置
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
GITEA_USER=$(yq eval '.gitea_user_name' "$CONFIG_FILE")
|
||||
GITEA_PASSWORD=$(yq eval '.gitea_user_password' "$CONFIG_FILE")
|
||||
GITEA_ORG=$(yq eval '.gitea_org_name' "$CONFIG_FILE")
|
||||
NGINX_REPO=$(yq eval '.nginx_app_repo_name' "$CONFIG_FILE")
|
||||
|
||||
# 获取Gitea访问地址
|
||||
GITEA_NODEPORT=$(kubectl get svc gitea-http -n gitea -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}')
|
||||
if [ -z "$NODE_IP" ]; then
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
|
||||
fi
|
||||
GITEA_URL="http://$NODE_IP:$GITEA_NODEPORT"
|
||||
|
||||
# 检查仓库是否已存在
|
||||
echo "🔍 检查仓库是否存在..."
|
||||
REPO_EXISTS=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-u "$GITEA_USER:$GITEA_PASSWORD" \
|
||||
"$GITEA_URL/api/v1/repos/$GITEA_ORG/$NGINX_REPO")
|
||||
|
||||
if [ "$REPO_EXISTS" = "200" ]; then
|
||||
echo "⚠️ 仓库已存在: $GITEA_ORG/$NGINX_REPO"
|
||||
echo ""
|
||||
read -p "是否删除并重新创建?(y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "🗑️ 删除现有仓库..."
|
||||
curl -s -X DELETE \
|
||||
-u "$GITEA_USER:$GITEA_PASSWORD" \
|
||||
"$GITEA_URL/api/v1/repos/$GITEA_ORG/$NGINX_REPO"
|
||||
echo "✅ 仓库已删除"
|
||||
else
|
||||
echo "⏭️ 跳过仓库创建"
|
||||
SKIP_PUSH=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SKIP_PUSH" != "true" ]; then
|
||||
echo "📝 创建新仓库..."
|
||||
curl -s -X POST \
|
||||
-u "$GITEA_USER:$GITEA_PASSWORD" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"name\":\"$NGINX_REPO\",\"description\":\"Nginx test application for GitOps demo\",\"private\":false,\"auto_init\":false}" \
|
||||
"$GITEA_URL/api/v1/org/$GITEA_ORG/repos" > /dev/null
|
||||
echo "✅ 仓库创建成功: $GITEA_ORG/$NGINX_REPO"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 步骤2: 推送应用到Gitea
|
||||
if [ "$SKIP_PUSH" != "true" ]; then
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📤 步骤 2/3: 推送应用到Gitea"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
"$SCRIPT_DIR/push-nginx-app.sh"
|
||||
echo ""
|
||||
else
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "⏭️ 步骤 2/3: 跳过推送(仓库已存在)"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# 步骤3: 创建ArgoCD Application
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🎯 步骤 3/3: 创建ArgoCD Application"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 检查Application是否已存在
|
||||
if kubectl get application nginx-app -n argocd >/dev/null 2>&1; then
|
||||
echo "⚠️ ArgoCD Application已存在"
|
||||
echo ""
|
||||
read -p "是否删除并重新创建?(y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "🗑️ 删除现有Application..."
|
||||
kubectl delete application nginx-app -n argocd
|
||||
echo "✅ Application已删除"
|
||||
sleep 2
|
||||
else
|
||||
echo "⏭️ 跳过Application创建"
|
||||
SKIP_ARGOCD=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SKIP_ARGOCD" != "true" ]; then
|
||||
"$SCRIPT_DIR/create-nginx-argocd-app.sh"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🎉 Nginx测试应用部署完成!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "📊 部署信息:"
|
||||
echo " - 应用名称: nginx-test"
|
||||
echo " - 命名空间: default"
|
||||
echo " - 域名: https://ng.jpc.net3w.com"
|
||||
echo " - Git仓库: $GITEA_URL/$GITEA_ORG/$NGINX_REPO"
|
||||
echo ""
|
||||
echo "🔍 验证命令:"
|
||||
echo " # 查看Pod状态"
|
||||
echo " kubectl get pods -l app=nginx-test -n default"
|
||||
echo ""
|
||||
echo " # 查看Service"
|
||||
echo " kubectl get svc nginx-test -n default"
|
||||
echo ""
|
||||
echo " # 查看Ingress"
|
||||
echo " kubectl get ingress nginx-test -n default"
|
||||
echo ""
|
||||
echo " # 查看ArgoCD Application"
|
||||
echo " kubectl get application nginx-app -n argocd"
|
||||
echo ""
|
||||
echo "🌐 访问地址:"
|
||||
echo " - 应用: https://ng.jpc.net3w.com"
|
||||
echo " - ArgoCD: https://argocd.jpc.net3w.com"
|
||||
echo " - Gitea: $GITEA_URL/$GITEA_ORG/$NGINX_REPO"
|
||||
echo ""
|
||||
echo "💡 更新应用:"
|
||||
echo " 1. SSH到master节点"
|
||||
echo " 2. cd /home/fei/k3s/nginx-app"
|
||||
echo " 3. ./update-app.sh v2.0"
|
||||
echo " 4. 等待ArgoCD自动同步(约3分钟)"
|
||||
echo ""
|
||||
echo "📝 注意事项:"
|
||||
echo " - 确保DNS已配置: ng.jpc.net3w.com -> $NODE_IP"
|
||||
echo " - 首次HTTPS访问需等待证书签发(1-2分钟)"
|
||||
echo " - ArgoCD每3分钟检查一次Git仓库更新"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
46
scripts/deploy.sh
Executable file
46
scripts/deploy.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
echo "=== K3s集群自动化部署 ==="
|
||||
|
||||
# 检查配置文件
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "❌ 错误: 配置文件不存在: $CONFIG_FILE"
|
||||
echo "请复制 config/cluster-vars.yml.example 为 config/cluster-vars.yml 并填写配置"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 安装依赖
|
||||
if ! command -v ansible &> /dev/null; then
|
||||
echo "📦 安装Ansible..."
|
||||
sudo apt update
|
||||
sudo apt install -y ansible python3-pip python3-yaml
|
||||
fi
|
||||
|
||||
# 生成inventory
|
||||
echo "📝 生成Ansible inventory..."
|
||||
cd "$PROJECT_DIR"
|
||||
python3 "$SCRIPT_DIR/generate-inventory.py"
|
||||
|
||||
# 部署K3s集群
|
||||
echo "🚀 部署K3s集群..."
|
||||
cd "$PROJECT_DIR/k3s-ansible"
|
||||
ansible-playbook site.yml \
|
||||
-i inventory/hosts.ini \
|
||||
-e "@$CONFIG_FILE"
|
||||
|
||||
# 配置kubectl
|
||||
echo "⚙️ 配置kubectl..."
|
||||
mkdir -p ~/.kube
|
||||
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
|
||||
sudo chown $USER:$USER ~/.kube/config
|
||||
|
||||
# 验证集群
|
||||
echo "✅ 验证集群状态..."
|
||||
kubectl get nodes
|
||||
|
||||
echo "=== K3s集群部署完成 ==="
|
||||
50
scripts/generate-inventory.py
Executable file
50
scripts/generate-inventory.py
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
import yaml
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 读取变量文件
|
||||
config_file = 'config/cluster-vars.yml'
|
||||
if not os.path.exists(config_file):
|
||||
print(f"错误: 配置文件不存在: {config_file}")
|
||||
sys.exit(1)
|
||||
|
||||
with open(config_file, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
# 生成inventory (k3s-ansible需要server和agent组)
|
||||
inventory = "[server]\n"
|
||||
for node in config['master_nodes']:
|
||||
line = f"{node['private_ip']} ansible_host={node['public_ip']} ansible_user={node['ssh_user']}"
|
||||
# 支持密码认证
|
||||
if 'ssh_password' in node:
|
||||
line += f" ansible_ssh_pass={node['ssh_password']} ansible_become_pass={node['ssh_password']}"
|
||||
elif 'ssh_key_path' in node:
|
||||
line += f" ansible_ssh_private_key_file={node['ssh_key_path']}"
|
||||
inventory += line + "\n"
|
||||
|
||||
inventory += "\n[agent]\n"
|
||||
for node in config['worker_nodes']:
|
||||
line = f"{node['private_ip']} ansible_host={node['public_ip']} ansible_user={node['ssh_user']}"
|
||||
if 'ssh_password' in node:
|
||||
line += f" ansible_ssh_pass={node['ssh_password']} ansible_become_pass={node['ssh_password']}"
|
||||
elif 'ssh_key_path' in node:
|
||||
line += f" ansible_ssh_private_key_file={node['ssh_key_path']}"
|
||||
inventory += line + "\n"
|
||||
|
||||
inventory += "\n[k3s_cluster:children]\nserver\nagent\n"
|
||||
inventory += "\n[k3s_cluster:vars]\n"
|
||||
inventory += "ansible_python_interpreter=/usr/bin/python3\n"
|
||||
inventory += f"k3s_version={config.get('k3s_version', 'v1.28.5+k3s1')}\n"
|
||||
inventory += f"token={config.get('k3s_token', 'changeme!')}\n"
|
||||
# 使用master节点的内网IP作为API endpoint
|
||||
master_private_ip = config['master_nodes'][0]['private_ip']
|
||||
inventory += f"api_endpoint={master_private_ip}\n"
|
||||
inventory += "flannel_iface=eth0\n"
|
||||
|
||||
# 写入inventory文件
|
||||
output_file = 'k3s-ansible/inventory/hosts.ini'
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(inventory)
|
||||
|
||||
print(f"✓ Inventory生成成功: {output_file}")
|
||||
553
scripts/idempotent-deploy.sh
Executable file
553
scripts/idempotent-deploy.sh
Executable file
@@ -0,0 +1,553 @@
|
||||
#!/bin/bash
|
||||
# JPD集群幂等性自动化部署脚本
|
||||
# 可以安全地重复运行,不会产生错误或不一致状态
|
||||
|
||||
set -e
|
||||
|
||||
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 JPD集群幂等性自动化部署"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 辅助函数:检查资源是否存在
|
||||
resource_exists() {
|
||||
local resource_type=$1
|
||||
local resource_name=$2
|
||||
local namespace=${3:-default}
|
||||
|
||||
if [ "$namespace" = "cluster" ]; then
|
||||
kubectl get "$resource_type" "$resource_name" &>/dev/null
|
||||
else
|
||||
kubectl get "$resource_type" "$resource_name" -n "$namespace" &>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# 辅助函数:等待资源就绪
|
||||
wait_for_pods() {
|
||||
local namespace=$1
|
||||
local label=$2
|
||||
local timeout=${3:-300}
|
||||
|
||||
echo "⏳ 等待 $namespace/$label Pod就绪..."
|
||||
kubectl wait --for=condition=ready pod -l "$label" -n "$namespace" --timeout="${timeout}s" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# 步骤 1: 配置Gitea Ingress
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 1/6: 配置Gitea Ingress"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# HTTP Ingress
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gitea-http
|
||||
namespace: gitea
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: git.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gitea-http
|
||||
port:
|
||||
number: 3000
|
||||
EOF
|
||||
|
||||
echo "✅ Gitea HTTP Ingress配置完成"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 2: 配置ArgoCD访问
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 2/6: 配置ArgoCD访问"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 配置ArgoCD为NodePort(幂等)
|
||||
if ! kubectl get svc argocd-server -n argocd -o jsonpath='{.spec.type}' | grep -q "NodePort"; then
|
||||
echo "配置ArgoCD Service为NodePort..."
|
||||
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort"}}'
|
||||
else
|
||||
echo "ArgoCD Service已经是NodePort类型"
|
||||
fi
|
||||
|
||||
# 配置ArgoCD允许HTTP访问(幂等)
|
||||
if ! kubectl get cm argocd-cmd-params-cm -n argocd -o jsonpath='{.data.server\.insecure}' | grep -q "true"; then
|
||||
echo "配置ArgoCD允许HTTP访问..."
|
||||
kubectl patch cm argocd-cmd-params-cm -n argocd --type merge -p '{"data":{"server.insecure":"true"}}'
|
||||
kubectl rollout restart deployment argocd-server -n argocd
|
||||
sleep 10
|
||||
else
|
||||
echo "ArgoCD已配置为允许HTTP访问"
|
||||
fi
|
||||
|
||||
# HTTP Ingress(简化版,不引用不存在的middleware)
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: argocd-server-http
|
||||
namespace: argocd
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: argocd.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: argocd-server
|
||||
port:
|
||||
number: 80
|
||||
EOF
|
||||
|
||||
ARGOCD_PORT=$(kubectl get svc argocd-server -n argocd -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
ARGOCD_PASSWORD=$(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" 2>/dev/null | base64 -d || echo "密码已删除或不存在")
|
||||
|
||||
echo "✅ ArgoCD访问配置完成"
|
||||
echo " NodePort: http://149.13.91.216:$ARGOCD_PORT"
|
||||
echo " 域名: http://argocd.jpd.net3w.com"
|
||||
echo " 用户名: admin"
|
||||
echo " 密码: $ARGOCD_PASSWORD"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 3: 部署cert-manager
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 3/6: 部署cert-manager"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
if ! resource_exists namespace cert-manager cluster; then
|
||||
echo "部署cert-manager..."
|
||||
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
|
||||
wait_for_pods cert-manager app=cert-manager 300
|
||||
wait_for_pods cert-manager app=webhook 300
|
||||
sleep 10
|
||||
else
|
||||
echo "cert-manager已存在,跳过部署"
|
||||
# 确保Pod就绪
|
||||
wait_for_pods cert-manager app=cert-manager 60
|
||||
wait_for_pods cert-manager app=webhook 60
|
||||
fi
|
||||
|
||||
# 创建ClusterIssuer(幂等)
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: admin@jpd.net3w.com
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: traefik
|
||||
EOF
|
||||
|
||||
echo "✅ cert-manager配置完成"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 4: 配置HTTPS Ingress
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 4/6: 配置HTTPS Ingress"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Gitea HTTPS
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gitea-https
|
||||
namespace: gitea
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- git.jpd.net3w.com
|
||||
secretName: gitea-tls
|
||||
rules:
|
||||
- host: git.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gitea-http
|
||||
port:
|
||||
number: 3000
|
||||
EOF
|
||||
|
||||
# ArgoCD HTTPS
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: argocd-server-https
|
||||
namespace: argocd
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- argocd.jpd.net3w.com
|
||||
secretName: argocd-server-tls
|
||||
rules:
|
||||
- host: argocd.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: argocd-server
|
||||
port:
|
||||
number: 80
|
||||
EOF
|
||||
|
||||
echo "✅ HTTPS Ingress配置完成"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 5: 部署测试应用
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 5/6: 部署测试应用"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# 创建命名空间(幂等)
|
||||
kubectl create namespace demo-app --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
# 部署应用(幂等)
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: nginx-html
|
||||
namespace: demo-app
|
||||
data:
|
||||
index.html: |
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>JPD集群测试应用</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
}
|
||||
h1 {
|
||||
color: #667eea;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.status {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.info {
|
||||
text-align: left;
|
||||
background: #f3f4f6;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.info p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.emoji {
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="emoji">🚀</div>
|
||||
<h1>JPD K3s集群测试应用</h1>
|
||||
<div class="status">✅ 运行正常</div>
|
||||
<div class="info">
|
||||
<p><strong>集群名称:</strong> JPD Cluster</p>
|
||||
<p><strong>部署方式:</strong> Kubernetes Deployment</p>
|
||||
<p><strong>副本数:</strong> 3</p>
|
||||
<p><strong>容器镜像:</strong> nginx:alpine</p>
|
||||
<p><strong>访问域名:</strong> demo.jpd.net3w.com</p>
|
||||
<p><strong>GitOps工具:</strong> ArgoCD</p>
|
||||
<p><strong>Git仓库:</strong> Gitea</p>
|
||||
<p><strong>幂等性:</strong> ✅ 已实现</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-demo
|
||||
namespace: demo-app
|
||||
labels:
|
||||
app: nginx-demo
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx-demo
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-demo
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
volumeMounts:
|
||||
- name: html
|
||||
mountPath: /usr/share/nginx/html
|
||||
volumes:
|
||||
- name: html
|
||||
configMap:
|
||||
name: nginx-html
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx-demo
|
||||
namespace: demo-app
|
||||
spec:
|
||||
selector:
|
||||
app: nginx-demo
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx-demo-http
|
||||
namespace: demo-app
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: demo.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: nginx-demo
|
||||
port:
|
||||
number: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx-demo-https
|
||||
namespace: demo-app
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- demo.jpd.net3w.com
|
||||
secretName: nginx-demo-tls
|
||||
rules:
|
||||
- host: demo.jpd.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: nginx-demo
|
||||
port:
|
||||
number: 80
|
||||
EOF
|
||||
|
||||
wait_for_pods demo-app app=nginx-demo 120
|
||||
|
||||
echo "✅ 测试应用部署完成"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 步骤 6: 部署自动化测试
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 步骤 6/6: 部署自动化测试"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: health-check
|
||||
namespace: demo-app
|
||||
spec:
|
||||
schedule: "*/5 * * * *"
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: curl
|
||||
image: curlimages/curl:latest
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "=== 健康检查开始 ==="
|
||||
echo "时间: \$(date)"
|
||||
echo ""
|
||||
|
||||
FAILED=0
|
||||
|
||||
# 测试Gitea
|
||||
echo "测试 Gitea..."
|
||||
if curl -f -s http://gitea-http.gitea.svc.cluster.local:3000 > /dev/null; then
|
||||
echo "✅ Gitea: 正常"
|
||||
else
|
||||
echo "❌ Gitea: 异常"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# 测试ArgoCD
|
||||
echo "测试 ArgoCD..."
|
||||
if curl -f -s -k http://argocd-server.argocd.svc.cluster.local > /dev/null; then
|
||||
echo "✅ ArgoCD: 正常"
|
||||
else
|
||||
echo "❌ ArgoCD: 异常"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
# 测试Demo应用
|
||||
echo "测试 Demo应用..."
|
||||
if curl -f -s http://nginx-demo.demo-app.svc.cluster.local > /dev/null; then
|
||||
echo "✅ Demo应用: 正常"
|
||||
else
|
||||
echo "❌ Demo应用: 异常"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ \$FAILED -eq 0 ]; then
|
||||
echo "=== 所有服务健康检查通过 ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== 健康检查失败 ==="
|
||||
exit 1
|
||||
fi
|
||||
restartPolicy: OnFailure
|
||||
EOF
|
||||
|
||||
echo "✅ 自动化测试部署完成"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 最终验证
|
||||
# ============================================
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🎉 部署完成!最终验证"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
echo "📊 集群节点:"
|
||||
kubectl get nodes -o wide
|
||||
echo ""
|
||||
|
||||
echo "🌐 Ingress资源:"
|
||||
kubectl get ingress --all-namespaces
|
||||
echo ""
|
||||
|
||||
echo "🔐 证书状态:"
|
||||
kubectl get certificate --all-namespaces
|
||||
echo ""
|
||||
|
||||
echo "🔑 访问信息:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "Gitea:"
|
||||
echo " HTTP: http://git.jpd.net3w.com"
|
||||
echo " HTTPS: https://git.jpd.net3w.com"
|
||||
echo " 用户名: gitea_admin"
|
||||
echo " 密码: GitAdmin@2026"
|
||||
echo ""
|
||||
echo "ArgoCD:"
|
||||
echo " HTTP: http://argocd.jpd.net3w.com"
|
||||
echo " HTTPS: https://argocd.jpd.net3w.com"
|
||||
echo " NodePort: http://149.13.91.216:$ARGOCD_PORT"
|
||||
echo " 用户名: admin"
|
||||
echo " 密码: $ARGOCD_PASSWORD"
|
||||
echo ""
|
||||
echo "测试应用:"
|
||||
echo " HTTP: http://demo.jpd.net3w.com"
|
||||
echo " HTTPS: https://demo.jpd.net3w.com"
|
||||
echo ""
|
||||
echo "💡 提示:"
|
||||
echo " - 此脚本是幂等的,可以安全地重复运行"
|
||||
echo " - HTTPS证书会自动签发和续期"
|
||||
echo " - 自动化测试每5分钟运行一次"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
354
scripts/lib/common.sh
Executable file
354
scripts/lib/common.sh
Executable file
@@ -0,0 +1,354 @@
|
||||
#!/bin/bash
|
||||
# Common utility functions for K3s deployment scripts
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Project directories
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
STATE_FILE="$PROJECT_DIR/.deployment-state"
|
||||
LOG_FILE="$PROJECT_DIR/deployment.log"
|
||||
|
||||
# Logging functions
|
||||
log() {
|
||||
local message="$1"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo -e "${GREEN}[INFO]${NC} $message"
|
||||
echo "[$timestamp] [INFO] $message" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
local message="$1"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo -e "${RED}[ERROR]${NC} $message" >&2
|
||||
echo "[$timestamp] [ERROR] $message" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
local message="$1"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo -e "${YELLOW}[WARN]${NC} $message"
|
||||
echo "[$timestamp] [WARN] $message" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
local message="$1"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
echo -e "${BLUE}[STEP]${NC} $message"
|
||||
echo "[$timestamp] [STEP] $message" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
# State management functions
|
||||
mark_step_completed() {
|
||||
local step_name="$1"
|
||||
echo "$step_name" >> "$STATE_FILE"
|
||||
log "✓ Marked step as completed: $step_name"
|
||||
}
|
||||
|
||||
is_step_completed() {
|
||||
local step_name="$1"
|
||||
if [ ! -f "$STATE_FILE" ]; then
|
||||
return 1
|
||||
fi
|
||||
grep -q "^$step_name$" "$STATE_FILE" 2>/dev/null
|
||||
}
|
||||
|
||||
reset_deployment_state() {
|
||||
if [ -f "$STATE_FILE" ]; then
|
||||
rm -f "$STATE_FILE"
|
||||
log "Deployment state reset"
|
||||
fi
|
||||
}
|
||||
|
||||
# Tool checking functions
|
||||
check_tool() {
|
||||
local tool_name="$1"
|
||||
local install_cmd="$2"
|
||||
|
||||
if command -v "$tool_name" &> /dev/null; then
|
||||
log "✓ Tool available: $tool_name"
|
||||
return 0
|
||||
else
|
||||
log_warn "Tool not found: $tool_name"
|
||||
if [ -n "$install_cmd" ]; then
|
||||
log "Installing $tool_name..."
|
||||
if eval "$install_cmd"; then
|
||||
log "✓ Successfully installed: $tool_name"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to install: $tool_name"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "Please install $tool_name manually"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
check_required_tools() {
|
||||
local all_ok=true
|
||||
|
||||
log_step "Checking required tools..."
|
||||
|
||||
check_tool "python3" "sudo apt update && sudo apt install -y python3" || all_ok=false
|
||||
check_tool "ansible" "sudo apt update && sudo apt install -y ansible" || all_ok=false
|
||||
check_tool "git" "sudo apt update && sudo apt install -y git" || all_ok=false
|
||||
|
||||
if [ "$all_ok" = false ]; then
|
||||
log_error "Some required tools are missing"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "✓ All required tools are available"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Network checking functions
|
||||
check_network() {
|
||||
local test_url="${1:-https://www.google.com}"
|
||||
local timeout="${2:-5}"
|
||||
|
||||
if curl -s --max-time "$timeout" --head "$test_url" > /dev/null 2>&1; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_network_with_retry() {
|
||||
local test_url="${1:-https://www.google.com}"
|
||||
local max_attempts="${2:-3}"
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if check_network "$test_url"; then
|
||||
log "✓ Network connection OK"
|
||||
return 0
|
||||
fi
|
||||
log_warn "Network check failed (attempt $attempt/$max_attempts)"
|
||||
attempt=$((attempt + 1))
|
||||
sleep 2
|
||||
done
|
||||
|
||||
log_error "Network connection failed after $max_attempts attempts"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Retry mechanism
|
||||
retry() {
|
||||
local max_attempts="$1"
|
||||
local delay="$2"
|
||||
shift 2
|
||||
local cmd="$@"
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if eval "$cmd"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ $attempt -lt $max_attempts ]; then
|
||||
log_warn "Command failed (attempt $attempt/$max_attempts), retrying in ${delay}s..."
|
||||
sleep "$delay"
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
log_error "Command failed after $max_attempts attempts: $cmd"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Configuration file validation
|
||||
check_config_file() {
|
||||
local config_file="${1:-$PROJECT_DIR/config/cluster-vars.yml}"
|
||||
|
||||
if [ ! -f "$config_file" ]; then
|
||||
log_error "Configuration file not found: $config_file"
|
||||
log_error "Please copy config/cluster-vars.yml.example to config/cluster-vars.yml and configure it"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "✓ Configuration file exists: $config_file"
|
||||
|
||||
# Check if yq is available for validation
|
||||
if command -v yq &> /dev/null; then
|
||||
if yq eval '.' "$config_file" > /dev/null 2>&1; then
|
||||
log "✓ Configuration file is valid YAML"
|
||||
else
|
||||
log_error "Configuration file has invalid YAML syntax"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Kubernetes cluster checking
|
||||
check_kubectl() {
|
||||
if ! command -v kubectl &> /dev/null; then
|
||||
log_warn "kubectl not found, will be available after K3s installation"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! kubectl cluster-info &> /dev/null; then
|
||||
log_warn "kubectl cannot connect to cluster"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "✓ kubectl is available and connected"
|
||||
return 0
|
||||
}
|
||||
|
||||
wait_for_pods() {
|
||||
local namespace="$1"
|
||||
local label="$2"
|
||||
local timeout="${3:-600}"
|
||||
|
||||
log "Waiting for pods in namespace $namespace with label $label..."
|
||||
|
||||
if kubectl wait --for=condition=ready pod \
|
||||
-l "$label" \
|
||||
-n "$namespace" \
|
||||
--timeout="${timeout}s" 2>/dev/null; then
|
||||
log "✓ Pods are ready"
|
||||
return 0
|
||||
else
|
||||
log_error "Pods failed to become ready within ${timeout}s"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_deployment() {
|
||||
local namespace="$1"
|
||||
local deployment="$2"
|
||||
local timeout="${3:-600}"
|
||||
|
||||
log "Waiting for deployment $deployment in namespace $namespace..."
|
||||
|
||||
if kubectl wait --for=condition=available \
|
||||
--timeout="${timeout}s" \
|
||||
deployment/"$deployment" \
|
||||
-n "$namespace" 2>/dev/null; then
|
||||
log "✓ Deployment is available"
|
||||
return 0
|
||||
else
|
||||
log_error "Deployment failed to become available within ${timeout}s"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Download with retry
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local output="$2"
|
||||
local max_attempts="${3:-3}"
|
||||
|
||||
log "Downloading: $url"
|
||||
|
||||
if retry "$max_attempts" 5 "curl -fsSL '$url' -o '$output'"; then
|
||||
log "✓ Downloaded successfully: $output"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to download: $url"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Install yq if not present
|
||||
ensure_yq() {
|
||||
if command -v yq &> /dev/null; then
|
||||
log "✓ yq is already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Installing yq..."
|
||||
local yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64"
|
||||
local yq_path="/usr/local/bin/yq"
|
||||
|
||||
if download_file "$yq_url" "/tmp/yq" 3; then
|
||||
sudo mv /tmp/yq "$yq_path"
|
||||
sudo chmod +x "$yq_path"
|
||||
log "✓ yq installed successfully"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to install yq"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Install htpasswd if not present
|
||||
ensure_htpasswd() {
|
||||
if command -v htpasswd &> /dev/null; then
|
||||
log "✓ htpasswd is already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Installing htpasswd (apache2-utils)..."
|
||||
if sudo apt update && sudo apt install -y apache2-utils; then
|
||||
log "✓ htpasswd installed successfully"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to install htpasswd"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Install helm if not present
|
||||
ensure_helm() {
|
||||
if command -v helm &> /dev/null; then
|
||||
log "✓ Helm is already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Installing Helm..."
|
||||
if retry 3 5 "curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash"; then
|
||||
log "✓ Helm installed successfully"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to install Helm"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Cleanup function for temporary files
|
||||
cleanup_temp_files() {
|
||||
local temp_dir="$1"
|
||||
if [ -n "$temp_dir" ] && [ -d "$temp_dir" ]; then
|
||||
rm -rf "$temp_dir"
|
||||
log "Cleaned up temporary directory: $temp_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
# Trap for cleanup on exit
|
||||
setup_cleanup_trap() {
|
||||
local temp_dir="$1"
|
||||
trap "cleanup_temp_files '$temp_dir'" EXIT INT TERM
|
||||
}
|
||||
|
||||
# Print summary
|
||||
print_summary() {
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Deployment Summary"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Export functions for use in other scripts
|
||||
export -f log log_error log_warn log_step
|
||||
export -f mark_step_completed is_step_completed reset_deployment_state
|
||||
export -f check_tool check_required_tools
|
||||
export -f check_network check_network_with_retry
|
||||
export -f retry
|
||||
export -f check_config_file check_kubectl
|
||||
export -f wait_for_pods wait_for_deployment
|
||||
export -f download_file
|
||||
export -f ensure_yq ensure_htpasswd ensure_helm
|
||||
export -f cleanup_temp_files setup_cleanup_trap
|
||||
export -f print_summary
|
||||
135
scripts/push-demo-app.sh
Executable file
135
scripts/push-demo-app.sh
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
echo "=== 推送示例应用到Gitea ==="
|
||||
|
||||
# 读取配置
|
||||
GITEA_USER=$(yq eval '.gitea_user_name' "$CONFIG_FILE")
|
||||
GITEA_PASSWORD=$(yq eval '.gitea_user_password' "$CONFIG_FILE")
|
||||
GITEA_ORG=$(yq eval '.gitea_org_name' "$CONFIG_FILE")
|
||||
GITEA_REPO=$(yq eval '.gitea_repo_name' "$CONFIG_FILE")
|
||||
|
||||
# 获取Gitea NodePort
|
||||
GITEA_NODEPORT=$(kubectl get svc gitea-http -n gitea -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}')
|
||||
if [ -z "$NODE_IP" ]; then
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
|
||||
fi
|
||||
|
||||
GITEA_URL="http://$NODE_IP:$GITEA_NODEPORT"
|
||||
REPO_URL="$GITEA_URL/$GITEA_ORG/$GITEA_REPO.git"
|
||||
|
||||
# 创建临时目录
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
echo "📝 创建示例应用清单..."
|
||||
|
||||
# 创建manifests目录
|
||||
mkdir -p manifests
|
||||
|
||||
# 创建示例Deployment
|
||||
cat > manifests/deployment.yaml <<EOF
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: demo-nginx
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: demo-nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: demo-nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.25-alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "200m"
|
||||
EOF
|
||||
|
||||
# 创建示例Service
|
||||
cat > manifests/service.yaml <<EOF
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: demo-nginx
|
||||
namespace: default
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: demo-nginx
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
nodePort: 30080
|
||||
EOF
|
||||
|
||||
# 创建README
|
||||
cat > README.md <<EOF
|
||||
# Demo Application
|
||||
|
||||
这是一个由ArgoCD管理的示例应用。
|
||||
|
||||
## 应用信息
|
||||
- **应用名称**: demo-nginx
|
||||
- **镜像**: nginx:1.25-alpine
|
||||
- **副本数**: 2
|
||||
- **访问端口**: NodePort 30080
|
||||
|
||||
## 更新应用
|
||||
修改 \`manifests/\` 目录下的文件并提交到Git,ArgoCD会自动同步部署。
|
||||
|
||||
## 测试访问
|
||||
\`\`\`bash
|
||||
curl http://<NODE_IP>:30080
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
# 初始化Git仓库
|
||||
echo "🔧 初始化Git仓库..."
|
||||
git init -b main
|
||||
git config user.name "$GITEA_USER"
|
||||
git config user.email "$GITEA_USER@example.com"
|
||||
git add .
|
||||
git commit -m "Initial commit: Add demo nginx application
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
||||
|
||||
# 推送到Gitea
|
||||
echo "📤 推送到Gitea..."
|
||||
git remote add origin "$REPO_URL"
|
||||
|
||||
# 使用用户名密码推送(临时方案)
|
||||
# URL encode the password to handle special characters
|
||||
ENCODED_PASSWORD=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$GITEA_PASSWORD'))")
|
||||
git push -u origin main || {
|
||||
echo "⚠️ 首次推送失败,尝试使用凭证..."
|
||||
git remote set-url origin "http://$GITEA_USER:$ENCODED_PASSWORD@$NODE_IP:$GITEA_NODEPORT/$GITEA_ORG/$GITEA_REPO.git"
|
||||
git push -u origin main
|
||||
}
|
||||
|
||||
# 清理
|
||||
cd "$PROJECT_DIR"
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo "✅ 示例应用推送成功!"
|
||||
echo "📊 仓库地址: $REPO_URL"
|
||||
echo "🌐 访问Gitea查看: $GITEA_URL/$GITEA_ORG/$GITEA_REPO"
|
||||
echo "⏳ 等待ArgoCD同步(约3分钟)..."
|
||||
echo "📊 查看同步状态: kubectl get application -n argocd"
|
||||
538
scripts/push-nginx-app.sh
Executable file
538
scripts/push-nginx-app.sh
Executable file
@@ -0,0 +1,538 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
echo "=== 推送Nginx测试应用到Gitea ==="
|
||||
|
||||
# 读取配置
|
||||
GITEA_USER=$(yq eval '.gitea_user_name' "$CONFIG_FILE")
|
||||
GITEA_PASSWORD=$(yq eval '.gitea_user_password' "$CONFIG_FILE")
|
||||
GITEA_ORG=$(yq eval '.gitea_org_name' "$CONFIG_FILE")
|
||||
NGINX_REPO=$(yq eval '.nginx_app_repo_name' "$CONFIG_FILE")
|
||||
NGINX_DOMAIN=$(yq eval '.nginx_app_domain' "$CONFIG_FILE")
|
||||
|
||||
# 获取Gitea NodePort
|
||||
GITEA_NODEPORT=$(kubectl get svc gitea-http -n gitea -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}')
|
||||
if [ -z "$NODE_IP" ]; then
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
|
||||
fi
|
||||
|
||||
GITEA_URL="http://$NODE_IP:$GITEA_NODEPORT"
|
||||
REPO_URL="$GITEA_URL/$GITEA_ORG/$NGINX_REPO.git"
|
||||
|
||||
# 创建临时目录
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
cd "$TEMP_DIR"
|
||||
|
||||
echo "📝 创建Nginx应用清单..."
|
||||
|
||||
# 创建manifests目录
|
||||
mkdir -p manifests
|
||||
|
||||
# 创建Nginx Deployment
|
||||
cat > manifests/deployment.yaml <<EOF
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-test
|
||||
namespace: default
|
||||
labels:
|
||||
app: nginx-test
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx-test
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-test
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.25-alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: nginx-config
|
||||
mountPath: /etc/nginx/conf.d/default.conf
|
||||
subPath: default.conf
|
||||
- name: html
|
||||
mountPath: /usr/share/nginx/html
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "200m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: nginx-config
|
||||
configMap:
|
||||
name: nginx-config
|
||||
- name: html
|
||||
configMap:
|
||||
name: nginx-html
|
||||
EOF
|
||||
|
||||
# 创建Nginx ConfigMap
|
||||
cat > manifests/configmap.yaml <<EOF
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: nginx-config
|
||||
namespace: default
|
||||
data:
|
||||
default.conf: |
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${NGINX_DOMAIN};
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
}
|
||||
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: nginx-html
|
||||
namespace: default
|
||||
data:
|
||||
index.html: |
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nginx Test - GitOps Demo</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
padding: 60px;
|
||||
max-width: 800px;
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
color: #667eea;
|
||||
font-size: 3em;
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.version {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 10px 30px;
|
||||
border-radius: 50px;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
margin: 20px 0;
|
||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.info {
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 30px;
|
||||
margin: 30px 0;
|
||||
text-align: left;
|
||||
}
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
.info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
color: #495057;
|
||||
}
|
||||
.info-value {
|
||||
color: #667eea;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
background: #28a745;
|
||||
color: white;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
margin: 10px 5px;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 30px;
|
||||
color: #6c757d;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.emoji {
|
||||
font-size: 3em;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="emoji">🚀</div>
|
||||
<h1>Nginx Test Application</h1>
|
||||
<div class="version">Version: v1.0</div>
|
||||
|
||||
<div class="info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">域名:</span>
|
||||
<span class="info-value">${NGINX_DOMAIN}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">应用名称:</span>
|
||||
<span class="info-value">nginx-test</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">镜像:</span>
|
||||
<span class="info-value">nginx:1.25-alpine</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">副本数:</span>
|
||||
<span class="info-value">2</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">部署方式:</span>
|
||||
<span class="info-value">GitOps (ArgoCD)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="badge">✓ Kubernetes</span>
|
||||
<span class="badge">✓ GitOps</span>
|
||||
<span class="badge">✓ ArgoCD</span>
|
||||
<span class="badge">✓ Nginx</span>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>🎯 这是一个通过GitOps自动部署的Nginx测试应用</p>
|
||||
<p>修改Git仓库中的配置,ArgoCD会自动同步部署</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
# 创建Service
|
||||
cat > manifests/service.yaml <<EOF
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx-test
|
||||
namespace: default
|
||||
labels:
|
||||
app: nginx-test
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: nginx-test
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
EOF
|
||||
|
||||
# 创建Ingress
|
||||
cat > manifests/ingress.yaml <<EOF
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx-test
|
||||
namespace: default
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- ${NGINX_DOMAIN}
|
||||
secretName: nginx-test-tls
|
||||
rules:
|
||||
- host: ${NGINX_DOMAIN}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: nginx-test
|
||||
port:
|
||||
number: 80
|
||||
EOF
|
||||
|
||||
# 创建README
|
||||
cat > README.md <<EOF
|
||||
# Nginx Test Application
|
||||
|
||||
这是一个由ArgoCD管理的Nginx测试应用,用于演示GitOps自动化部署。
|
||||
|
||||
## 应用信息
|
||||
|
||||
- **应用名称**: nginx-test
|
||||
- **镜像**: nginx:1.25-alpine
|
||||
- **副本数**: 2
|
||||
- **域名**: ${NGINX_DOMAIN}
|
||||
- **命名空间**: default
|
||||
|
||||
## 架构说明
|
||||
|
||||
\`\`\`
|
||||
Git仓库 (Gitea) → ArgoCD监控 → 自动同步 → K3s集群部署
|
||||
\`\`\`
|
||||
|
||||
## 访问方式
|
||||
|
||||
### 通过域名访问(推荐)
|
||||
\`\`\`bash
|
||||
curl https://${NGINX_DOMAIN}
|
||||
\`\`\`
|
||||
|
||||
### 通过NodePort访问
|
||||
\`\`\`bash
|
||||
# 获取Service信息
|
||||
kubectl get svc nginx-test -n default
|
||||
|
||||
# 访问应用
|
||||
curl http://<NODE_IP>:<NODE_PORT>
|
||||
\`\`\`
|
||||
|
||||
## 更新应用
|
||||
|
||||
### 方式1: 修改版本号
|
||||
编辑 \`manifests/configmap.yaml\` 中的 HTML 内容,修改版本号:
|
||||
\`\`\`html
|
||||
<div class="version">Version: v2.0</div>
|
||||
\`\`\`
|
||||
|
||||
### 方式2: 修改副本数
|
||||
编辑 \`manifests/deployment.yaml\`:
|
||||
\`\`\`yaml
|
||||
spec:
|
||||
replicas: 3 # 修改副本数
|
||||
\`\`\`
|
||||
|
||||
### 方式3: 更新Nginx配置
|
||||
编辑 \`manifests/configmap.yaml\` 中的 nginx 配置。
|
||||
|
||||
提交更改后,ArgoCD会在3分钟内自动检测并部署新版本。
|
||||
|
||||
## 监控部署状态
|
||||
|
||||
\`\`\`bash
|
||||
# 查看ArgoCD Application状态
|
||||
kubectl get application nginx-app -n argocd
|
||||
|
||||
# 查看Pod状态
|
||||
kubectl get pods -l app=nginx-test -n default
|
||||
|
||||
# 查看Ingress状态
|
||||
kubectl get ingress nginx-test -n default
|
||||
|
||||
# 查看应用日志
|
||||
kubectl logs -l app=nginx-test -n default --tail=50
|
||||
\`\`\`
|
||||
|
||||
## 健康检查
|
||||
|
||||
应用提供了健康检查端点:
|
||||
\`\`\`bash
|
||||
curl https://${NGINX_DOMAIN}/health
|
||||
\`\`\`
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 检查Pod状态
|
||||
\`\`\`bash
|
||||
kubectl describe pod -l app=nginx-test -n default
|
||||
\`\`\`
|
||||
|
||||
### 检查Ingress
|
||||
\`\`\`bash
|
||||
kubectl describe ingress nginx-test -n default
|
||||
\`\`\`
|
||||
|
||||
### 检查ArgoCD同步状态
|
||||
\`\`\`bash
|
||||
kubectl describe application nginx-app -n argocd
|
||||
\`\`\`
|
||||
|
||||
## GitOps工作流
|
||||
|
||||
1. 开发者修改 \`manifests/\` 目录下的配置文件
|
||||
2. 提交并推送到Git仓库
|
||||
3. ArgoCD自动检测到变化(每3分钟轮询一次)
|
||||
4. ArgoCD自动同步并部署到K3s集群
|
||||
5. 应用自动更新,无需手动执行kubectl命令
|
||||
|
||||
## 回滚操作
|
||||
|
||||
如果需要回滚到之前的版本:
|
||||
\`\`\`bash
|
||||
# 查看Git历史
|
||||
git log --oneline
|
||||
|
||||
# 回滚到指定commit
|
||||
git revert <commit-hash>
|
||||
git push
|
||||
|
||||
# 或者通过ArgoCD UI进行回滚
|
||||
\`\`\`
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **容器编排**: Kubernetes (K3s)
|
||||
- **Web服务器**: Nginx 1.25
|
||||
- **GitOps工具**: ArgoCD
|
||||
- **Git仓库**: Gitea
|
||||
- **Ingress控制器**: Nginx Ingress Controller
|
||||
- **证书管理**: cert-manager (Let's Encrypt)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保DNS已正确配置,${NGINX_DOMAIN} 指向K3s集群节点IP
|
||||
2. 首次访问HTTPS可能需要等待证书签发(约1-2分钟)
|
||||
3. ArgoCD默认每3分钟检查一次Git仓库更新
|
||||
4. 可以通过ArgoCD UI手动触发同步以立即部署更改
|
||||
|
||||
## 相关链接
|
||||
|
||||
- ArgoCD Dashboard: https://argocd.jpc.net3w.com
|
||||
- Gitea Repository: http://<NODE_IP>:<GITEA_PORT>/k3s-apps/nginx-app
|
||||
- Application URL: https://${NGINX_DOMAIN}
|
||||
EOF
|
||||
|
||||
# 创建更新脚本
|
||||
cat > update-app.sh <<'SCRIPT_EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
VERSION=${1:-v2.0}
|
||||
|
||||
echo "🔄 更新Nginx应用到版本 $VERSION"
|
||||
|
||||
# 修改版本号
|
||||
sed -i "s/Version: v[0-9.]*/Version: $VERSION/" manifests/configmap.yaml
|
||||
|
||||
# 根据版本修改背景色
|
||||
case $VERSION in
|
||||
v1.0)
|
||||
COLOR="linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||
;;
|
||||
v2.0)
|
||||
COLOR="linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
|
||||
;;
|
||||
v3.0)
|
||||
COLOR="linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
|
||||
;;
|
||||
*)
|
||||
COLOR="linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
|
||||
;;
|
||||
esac
|
||||
|
||||
sed -i "s|background: linear-gradient([^)]*)|background: $COLOR|" manifests/configmap.yaml
|
||||
|
||||
# 提交更改
|
||||
git add manifests/configmap.yaml
|
||||
git commit -m "Update nginx-app to $VERSION
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
||||
git push
|
||||
|
||||
echo "✅ 更新完成!"
|
||||
echo "⏳ 等待ArgoCD同步(约3分钟)..."
|
||||
echo "🌐 访问 https://ng.jpc.net3w.com 查看更新"
|
||||
SCRIPT_EOF
|
||||
|
||||
chmod +x update-app.sh
|
||||
|
||||
# 初始化Git仓库
|
||||
echo "🔧 初始化Git仓库..."
|
||||
git init -b main
|
||||
git config user.name "$GITEA_USER"
|
||||
git config user.email "$GITEA_USER@example.com"
|
||||
git add .
|
||||
git commit -m "Initial commit: Add nginx test application
|
||||
|
||||
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
|
||||
|
||||
# 推送到Gitea
|
||||
echo "📤 推送到Gitea..."
|
||||
git remote add origin "$REPO_URL"
|
||||
|
||||
# URL encode the password to handle special characters
|
||||
ENCODED_PASSWORD=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$GITEA_PASSWORD'))")
|
||||
git push -u origin main || {
|
||||
echo "⚠️ 首次推送失败,尝试使用凭证..."
|
||||
git remote set-url origin "http://$GITEA_USER:$ENCODED_PASSWORD@$NODE_IP:$GITEA_NODEPORT/$GITEA_ORG/$NGINX_REPO.git"
|
||||
git push -u origin main
|
||||
}
|
||||
|
||||
# 清理
|
||||
cd "$PROJECT_DIR"
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo ""
|
||||
echo "✅ Nginx测试应用推送成功!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📊 仓库信息:"
|
||||
echo " - 仓库地址: $REPO_URL"
|
||||
echo " - Gitea访问: $GITEA_URL/$GITEA_ORG/$NGINX_REPO"
|
||||
echo ""
|
||||
echo "🌐 应用信息:"
|
||||
echo " - 域名: https://${NGINX_DOMAIN}"
|
||||
echo " - 应用名称: nginx-test"
|
||||
echo " - 命名空间: default"
|
||||
echo ""
|
||||
echo "📝 下一步:"
|
||||
echo " 1. 运行: ./scripts/create-nginx-argocd-app.sh"
|
||||
echo " 2. 等待ArgoCD同步(约3分钟)"
|
||||
echo " 3. 访问: https://${NGINX_DOMAIN}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
51
scripts/setup-gitea.sh
Executable file
51
scripts/setup-gitea.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
echo "=== 初始化Gitea配置 ==="
|
||||
|
||||
# 读取配置
|
||||
GITEA_ADMIN_USER=$(yq eval '.gitea_admin_user' "$CONFIG_FILE")
|
||||
GITEA_ADMIN_PASSWORD=$(yq eval '.gitea_admin_password' "$CONFIG_FILE")
|
||||
GITEA_ORG_NAME=$(yq eval '.gitea_org_name' "$CONFIG_FILE")
|
||||
GITEA_REPO_NAME=$(yq eval '.gitea_repo_name' "$CONFIG_FILE")
|
||||
GITEA_USER_NAME=$(yq eval '.gitea_user_name' "$CONFIG_FILE")
|
||||
GITEA_USER_PASSWORD=$(yq eval '.gitea_user_password' "$CONFIG_FILE")
|
||||
GITEA_USER_EMAIL=$(yq eval '.gitea_user_email' "$CONFIG_FILE")
|
||||
|
||||
# 获取Gitea服务地址
|
||||
GITEA_POD=$(kubectl get pod -n gitea -l app.kubernetes.io/name=gitea -o jsonpath='{.items[0].metadata.name}')
|
||||
GITEA_URL="http://gitea-http.gitea.svc.cluster.local:3000"
|
||||
|
||||
echo "📝 创建用户: $GITEA_USER_NAME"
|
||||
kubectl exec -n gitea "$GITEA_POD" -- su git -c "gitea admin user create \
|
||||
--username '$GITEA_USER_NAME' \
|
||||
--password '$GITEA_USER_PASSWORD' \
|
||||
--email '$GITEA_USER_EMAIL' \
|
||||
--must-change-password=false" || echo "用户可能已存在"
|
||||
|
||||
echo "📝 创建组织: $GITEA_ORG_NAME"
|
||||
kubectl exec -n gitea "$GITEA_POD" -- su git -c "gitea admin org create \
|
||||
--username '$GITEA_ADMIN_USER' \
|
||||
--name '$GITEA_ORG_NAME'" || echo "组织可能已存在"
|
||||
|
||||
echo "📦 创建仓库: $GITEA_REPO_NAME"
|
||||
kubectl exec -n gitea "$GITEA_POD" -- su git -c "gitea admin repo create \
|
||||
--owner '$GITEA_ORG_NAME' \
|
||||
--name '$GITEA_REPO_NAME' \
|
||||
--private=false" || echo "仓库可能已存在"
|
||||
|
||||
echo "👥 添加用户到组织"
|
||||
# 使用Gitea API添加用户到组织
|
||||
kubectl exec -n gitea "$GITEA_POD" -- su git -c "curl -X PUT \
|
||||
-H 'Content-Type: application/json' \
|
||||
-u '$GITEA_ADMIN_USER:$GITEA_ADMIN_PASSWORD' \
|
||||
'$GITEA_URL/api/v1/orgs/$GITEA_ORG_NAME/members/$GITEA_USER_NAME'" || true
|
||||
|
||||
echo "✅ Gitea初始化完成!"
|
||||
echo "📊 仓库地址: $GITEA_URL/$GITEA_ORG_NAME/$GITEA_REPO_NAME.git"
|
||||
echo "👤 ArgoCD用户: $GITEA_USER_NAME"
|
||||
echo "🔑 ArgoCD密码: $GITEA_USER_PASSWORD"
|
||||
280
scripts/test-idempotency.sh
Executable file
280
scripts/test-idempotency.sh
Executable file
@@ -0,0 +1,280 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Source common library if available
|
||||
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
log() { echo "[INFO] $1"; }
|
||||
log_error() { echo "[ERROR] $1" >&2; }
|
||||
log_warn() { echo "[WARN] $1"; }
|
||||
fi
|
||||
|
||||
log "=== K3s集群幂等性测试 ==="
|
||||
echo ""
|
||||
|
||||
# Test counters
|
||||
TOTAL_TESTS=0
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
|
||||
# Test function
|
||||
test_case() {
|
||||
local name="$1"
|
||||
local description="$2"
|
||||
|
||||
TOTAL_TESTS=$((TOTAL_TESTS + 1))
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "测试 #$TOTAL_TESTS: $name"
|
||||
echo "=========================================="
|
||||
echo "描述: $description"
|
||||
echo ""
|
||||
}
|
||||
|
||||
test_pass() {
|
||||
PASSED_TESTS=$((PASSED_TESTS + 1))
|
||||
log "✓ 测试通过"
|
||||
}
|
||||
|
||||
test_fail() {
|
||||
local reason="$1"
|
||||
FAILED_TESTS=$((FAILED_TESTS + 1))
|
||||
log_error "✗ 测试失败: $reason"
|
||||
}
|
||||
|
||||
# Capture initial state
|
||||
capture_state() {
|
||||
local state_file="$1"
|
||||
|
||||
log "捕获系统状态..."
|
||||
|
||||
{
|
||||
echo "=== Nodes ==="
|
||||
kubectl get nodes -o yaml 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== Namespaces ==="
|
||||
kubectl get namespaces -o yaml 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== Deployments ==="
|
||||
kubectl get deployments -A -o yaml 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== Services ==="
|
||||
kubectl get services -A -o yaml 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== ConfigMaps ==="
|
||||
kubectl get configmaps -A -o yaml 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== Secrets (names only) ==="
|
||||
kubectl get secrets -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== PVCs ==="
|
||||
kubectl get pvc -A -o yaml 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== Ingresses ==="
|
||||
kubectl get ingress -A -o yaml 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== ClusterIssuers ==="
|
||||
kubectl get clusterissuer -o yaml 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== Certificates ==="
|
||||
kubectl get certificate -A -o yaml 2>/dev/null || echo "N/A"
|
||||
|
||||
echo "=== ArgoCD Applications ==="
|
||||
kubectl get application -n argocd -o yaml 2>/dev/null || echo "N/A"
|
||||
} > "$state_file"
|
||||
|
||||
log "✓ 状态已保存到: $state_file"
|
||||
}
|
||||
|
||||
# Compare states
|
||||
compare_states() {
|
||||
local before="$1"
|
||||
local after="$2"
|
||||
|
||||
log "比较部署前后状态..."
|
||||
|
||||
if diff -u "$before" "$after" > /dev/null 2>&1; then
|
||||
log "✓ 状态完全一致(幂等性验证通过)"
|
||||
return 0
|
||||
else
|
||||
log_warn "状态存在差异,检查差异详情..."
|
||||
|
||||
# Check for acceptable differences (timestamps, resourceVersion, etc.)
|
||||
local significant_diff=false
|
||||
|
||||
# Filter out expected differences
|
||||
diff -u "$before" "$after" | grep -v "resourceVersion" | \
|
||||
grep -v "creationTimestamp" | \
|
||||
grep -v "generation:" | \
|
||||
grep -v "uid:" | \
|
||||
grep -v "selfLink" | \
|
||||
grep -v "lastTransitionTime" | \
|
||||
grep -v "observedGeneration" > /tmp/filtered_diff.txt || true
|
||||
|
||||
if [ -s /tmp/filtered_diff.txt ]; then
|
||||
log_warn "发现显著差异:"
|
||||
head -50 /tmp/filtered_diff.txt
|
||||
significant_diff=true
|
||||
fi
|
||||
|
||||
rm -f /tmp/filtered_diff.txt
|
||||
|
||||
if [ "$significant_diff" = true ]; then
|
||||
return 1
|
||||
else
|
||||
log "✓ 仅存在预期的元数据差异(幂等性验证通过)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main test flow
|
||||
main() {
|
||||
log "开始幂等性测试"
|
||||
log "此测试将验证部署脚本的幂等性"
|
||||
echo ""
|
||||
|
||||
# Check if cluster is accessible
|
||||
if ! kubectl cluster-info &>/dev/null; then
|
||||
log_error "无法连接到K3s集群,请先部署集群"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 1: Capture initial state
|
||||
test_case "初始状态捕获" "捕获当前集群状态作为基准"
|
||||
|
||||
STATE_BEFORE="/tmp/k3s-state-before-$$.yaml"
|
||||
capture_state "$STATE_BEFORE"
|
||||
test_pass
|
||||
|
||||
# Test 2: Run deploy-all.sh
|
||||
test_case "重复执行部署脚本" "运行deploy-all.sh验证幂等性"
|
||||
|
||||
log "执行部署脚本..."
|
||||
if bash "$SCRIPT_DIR/deploy-all.sh"; then
|
||||
log "✓ 部署脚本执行成功"
|
||||
test_pass
|
||||
else
|
||||
log_error "部署脚本执行失败"
|
||||
test_fail "deploy-all.sh执行失败"
|
||||
fi
|
||||
|
||||
# Test 3: Capture state after redeployment
|
||||
test_case "重新部署后状态捕获" "捕获重新部署后的集群状态"
|
||||
|
||||
STATE_AFTER="/tmp/k3s-state-after-$$.yaml"
|
||||
capture_state "$STATE_AFTER"
|
||||
test_pass
|
||||
|
||||
# Test 4: Compare states
|
||||
test_case "状态一致性验证" "比较部署前后状态,验证幂等性"
|
||||
|
||||
if compare_states "$STATE_BEFORE" "$STATE_AFTER"; then
|
||||
test_pass
|
||||
else
|
||||
test_fail "部署前后状态存在显著差异"
|
||||
fi
|
||||
|
||||
# Test 5: Verify all services are still healthy
|
||||
test_case "服务健康检查" "验证所有服务仍然正常运行"
|
||||
|
||||
log "运行验证脚本..."
|
||||
if bash "$SCRIPT_DIR/verify-deployment.sh" > /tmp/verify-output.txt 2>&1; then
|
||||
log "✓ 所有服务健康"
|
||||
test_pass
|
||||
else
|
||||
log_error "服务验证失败"
|
||||
cat /tmp/verify-output.txt
|
||||
test_fail "服务健康检查失败"
|
||||
fi
|
||||
|
||||
# Test 6: Test individual script idempotency
|
||||
test_case "单个脚本幂等性" "测试各个部署脚本的幂等性"
|
||||
|
||||
local scripts=(
|
||||
"deploy-argocd.sh"
|
||||
"deploy-gitea.sh"
|
||||
"deploy-https.sh"
|
||||
)
|
||||
|
||||
local script_tests_passed=0
|
||||
local script_tests_total=0
|
||||
|
||||
for script in "${scripts[@]}"; do
|
||||
if [ -f "$SCRIPT_DIR/$script" ]; then
|
||||
script_tests_total=$((script_tests_total + 1))
|
||||
log "测试脚本: $script"
|
||||
|
||||
if bash "$SCRIPT_DIR/$script" > /tmp/script-test-$$.log 2>&1; then
|
||||
log " ✓ $script 执行成功"
|
||||
script_tests_passed=$((script_tests_passed + 1))
|
||||
else
|
||||
log_warn " ✗ $script 执行失败"
|
||||
tail -20 /tmp/script-test-$$.log
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $script_tests_passed -eq $script_tests_total ]; then
|
||||
test_pass
|
||||
else
|
||||
test_fail "$script_tests_passed/$script_tests_total 脚本通过测试"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
log "清理临时文件..."
|
||||
rm -f "$STATE_BEFORE" "$STATE_AFTER" /tmp/verify-output.txt /tmp/script-test-$$.log
|
||||
|
||||
# Print summary
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 幂等性测试总结"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "总测试数: $TOTAL_TESTS"
|
||||
echo "通过: $PASSED_TESTS ✓"
|
||||
echo "失败: $FAILED_TESTS ✗"
|
||||
echo ""
|
||||
|
||||
if [ $FAILED_TESTS -eq 0 ]; then
|
||||
log "✓ 所有幂等性测试通过!"
|
||||
echo ""
|
||||
echo "结论: 部署脚本完全支持幂等性,可以安全地重复执行。"
|
||||
echo ""
|
||||
exit 0
|
||||
else
|
||||
log_error "存在 $FAILED_TESTS 个失败的测试"
|
||||
echo ""
|
||||
echo "结论: 部署脚本的幂等性存在问题,需要修复。"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Handle script arguments
|
||||
case "${1:-}" in
|
||||
--help|-h)
|
||||
echo "用法: $0 [选项]"
|
||||
echo ""
|
||||
echo "此脚本测试K3s部署的幂等性,验证脚本可以安全地重复执行。"
|
||||
echo ""
|
||||
echo "测试内容:"
|
||||
echo " 1. 捕获初始集群状态"
|
||||
echo " 2. 重复执行部署脚本"
|
||||
echo " 3. 比较部署前后状态"
|
||||
echo " 4. 验证服务健康"
|
||||
echo " 5. 测试单个脚本幂等性"
|
||||
echo ""
|
||||
echo "选项:"
|
||||
echo " --help 显示此帮助信息"
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Run main function
|
||||
main
|
||||
276
scripts/verify-deployment.sh
Executable file
276
scripts/verify-deployment.sh
Executable file
@@ -0,0 +1,276 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$PROJECT_DIR/config/cluster-vars.yml"
|
||||
|
||||
# Source common library if available
|
||||
if [ -f "$SCRIPT_DIR/lib/common.sh" ]; then
|
||||
source "$SCRIPT_DIR/lib/common.sh"
|
||||
else
|
||||
log() { echo "[INFO] $1"; }
|
||||
log_error() { echo "[ERROR] $1" >&2; }
|
||||
log_warn() { echo "[WARN] $1"; }
|
||||
fi
|
||||
|
||||
log "=== 验证K3s集群部署 ==="
|
||||
echo ""
|
||||
|
||||
# Counters
|
||||
TOTAL_CHECKS=0
|
||||
PASSED_CHECKS=0
|
||||
FAILED_CHECKS=0
|
||||
WARNING_CHECKS=0
|
||||
|
||||
# Check function
|
||||
check() {
|
||||
local name="$1"
|
||||
local command="$2"
|
||||
local is_critical="${3:-true}"
|
||||
|
||||
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
|
||||
echo -n "检查: $name ... "
|
||||
|
||||
if eval "$command" &>/dev/null; then
|
||||
echo "✓ 通过"
|
||||
PASSED_CHECKS=$((PASSED_CHECKS + 1))
|
||||
return 0
|
||||
else
|
||||
if [ "$is_critical" = "true" ]; then
|
||||
echo "✗ 失败"
|
||||
FAILED_CHECKS=$((FAILED_CHECKS + 1))
|
||||
else
|
||||
echo "⚠ 警告"
|
||||
WARNING_CHECKS=$((WARNING_CHECKS + 1))
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Detailed check with output
|
||||
check_detailed() {
|
||||
local name="$1"
|
||||
local command="$2"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " $name"
|
||||
echo "=========================================="
|
||||
eval "$command"
|
||||
echo ""
|
||||
}
|
||||
|
||||
echo "=========================================="
|
||||
echo " 1. 基础环境检查"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
check "kubectl命令可用" "command -v kubectl"
|
||||
check "kubectl连接集群" "kubectl cluster-info"
|
||||
check "配置文件存在" "test -f $CONFIG_FILE"
|
||||
|
||||
if command -v yq &>/dev/null; then
|
||||
check "yq工具可用" "command -v yq"
|
||||
else
|
||||
check "yq工具可用" "false" "false"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 2. K3s集群状态"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
check "所有节点Ready" "kubectl get nodes | grep -v NotReady | grep Ready"
|
||||
check "kube-system命名空间存在" "kubectl get namespace kube-system"
|
||||
check "CoreDNS运行正常" "kubectl get deployment coredns -n kube-system -o jsonpath='{.status.availableReplicas}' | grep -v '^0$'"
|
||||
|
||||
check_detailed "节点状态" "kubectl get nodes -o wide"
|
||||
check_detailed "系统Pod状态" "kubectl get pods -n kube-system"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 3. Gitea服务检查"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
if kubectl get namespace gitea &>/dev/null; then
|
||||
check "Gitea命名空间存在" "kubectl get namespace gitea"
|
||||
check "Gitea部署存在" "kubectl get deployment gitea -n gitea"
|
||||
|
||||
if kubectl get deployment gitea -n gitea &>/dev/null; then
|
||||
check "Gitea Pod运行正常" "kubectl get pods -n gitea -l app.kubernetes.io/name=gitea -o jsonpath='{.items[0].status.phase}' | grep Running"
|
||||
check "Gitea服务可访问" "kubectl get svc gitea-http -n gitea"
|
||||
|
||||
check_detailed "Gitea服务详情" "kubectl get all -n gitea"
|
||||
|
||||
# Get Gitea access info
|
||||
GITEA_NODEPORT=$(kubectl get svc gitea-http -n gitea -o jsonpath='{.spec.ports[0].nodePort}' 2>/dev/null || echo "N/A")
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="ExternalIP")].address}' 2>/dev/null)
|
||||
if [ -z "$NODE_IP" ]; then
|
||||
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null)
|
||||
fi
|
||||
|
||||
echo "Gitea访问信息:"
|
||||
echo " URL: http://$NODE_IP:$GITEA_NODEPORT"
|
||||
echo ""
|
||||
fi
|
||||
else
|
||||
check "Gitea命名空间存在" "false" "false"
|
||||
log_warn "Gitea未部署"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 4. ArgoCD服务检查"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
if kubectl get namespace argocd &>/dev/null; then
|
||||
check "ArgoCD命名空间存在" "kubectl get namespace argocd"
|
||||
check "ArgoCD Server部署存在" "kubectl get deployment argocd-server -n argocd"
|
||||
|
||||
if kubectl get deployment argocd-server -n argocd &>/dev/null; then
|
||||
check "ArgoCD Server运行正常" "kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-server -o jsonpath='{.items[0].status.phase}' | grep Running"
|
||||
check "ArgoCD Application Controller运行正常" "kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-application-controller -o jsonpath='{.items[0].status.phase}' | grep Running"
|
||||
check "ArgoCD Repo Server运行正常" "kubectl get pods -n argocd -l app.kubernetes.io/name=argocd-repo-server -o jsonpath='{.items[0].status.phase}' | grep Running"
|
||||
|
||||
check_detailed "ArgoCD服务详情" "kubectl get all -n argocd"
|
||||
|
||||
# Get ArgoCD access info
|
||||
ARGOCD_NODEPORT=$(kubectl get svc argocd-server -n argocd -o jsonpath='{.spec.ports[0].nodePort}' 2>/dev/null || echo "N/A")
|
||||
|
||||
echo "ArgoCD访问信息:"
|
||||
echo " URL: https://$NODE_IP:$ARGOCD_NODEPORT"
|
||||
echo " 用户名: admin"
|
||||
echo ""
|
||||
fi
|
||||
else
|
||||
check "ArgoCD命名空间存在" "false" "false"
|
||||
log_warn "ArgoCD未部署"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 5. HTTPS证书检查"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
if kubectl get namespace cert-manager &>/dev/null; then
|
||||
check "cert-manager命名空间存在" "kubectl get namespace cert-manager"
|
||||
check "cert-manager部署存在" "kubectl get deployment cert-manager -n cert-manager"
|
||||
|
||||
if kubectl get deployment cert-manager -n cert-manager &>/dev/null; then
|
||||
check "cert-manager运行正常" "kubectl get pods -n cert-manager -l app=cert-manager -o jsonpath='{.items[0].status.phase}' | grep Running"
|
||||
|
||||
# Check ClusterIssuers
|
||||
if kubectl get clusterissuer &>/dev/null 2>&1; then
|
||||
check_detailed "ClusterIssuer状态" "kubectl get clusterissuer"
|
||||
fi
|
||||
|
||||
# Check Certificates
|
||||
if kubectl get certificate -A &>/dev/null 2>&1; then
|
||||
check_detailed "证书状态" "kubectl get certificate -A"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
check "cert-manager命名空间存在" "false" "false"
|
||||
log_warn "cert-manager未部署,HTTPS功能不可用"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 6. GitOps工作流检查"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
if kubectl get namespace argocd &>/dev/null; then
|
||||
# Check for ArgoCD Applications
|
||||
if kubectl get application -n argocd &>/dev/null 2>&1; then
|
||||
APP_COUNT=$(kubectl get application -n argocd --no-headers 2>/dev/null | wc -l)
|
||||
if [ "$APP_COUNT" -gt 0 ]; then
|
||||
check "ArgoCD应用已创建" "test $APP_COUNT -gt 0"
|
||||
check_detailed "ArgoCD应用状态" "kubectl get application -n argocd"
|
||||
else
|
||||
check "ArgoCD应用已创建" "false" "false"
|
||||
log_warn "未找到ArgoCD应用"
|
||||
fi
|
||||
else
|
||||
check "ArgoCD应用已创建" "false" "false"
|
||||
log_warn "ArgoCD CRD可能未就绪"
|
||||
fi
|
||||
else
|
||||
log_warn "ArgoCD未部署,跳过GitOps检查"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 7. 存储检查"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
check "PersistentVolume存在" "kubectl get pv" "false"
|
||||
check "PersistentVolumeClaim存在" "kubectl get pvc -A" "false"
|
||||
|
||||
if kubectl get pvc -A &>/dev/null 2>&1; then
|
||||
check_detailed "存储卷状态" "kubectl get pv,pvc -A"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 验证总结"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "总检查项: $TOTAL_CHECKS"
|
||||
echo "通过: $PASSED_CHECKS ✓"
|
||||
echo "失败: $FAILED_CHECKS ✗"
|
||||
echo "警告: $WARNING_CHECKS ⚠"
|
||||
echo ""
|
||||
|
||||
if [ $FAILED_CHECKS -eq 0 ]; then
|
||||
log "✓ 所有关键检查通过!"
|
||||
|
||||
if [ $WARNING_CHECKS -gt 0 ]; then
|
||||
log_warn "存在 $WARNING_CHECKS 个警告项,建议检查"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " 快速访问指南"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
if [ -n "${NODE_IP:-}" ]; then
|
||||
if [ -n "${GITEA_NODEPORT:-}" ] && [ "$GITEA_NODEPORT" != "N/A" ]; then
|
||||
echo "Gitea:"
|
||||
echo " http://$NODE_IP:$GITEA_NODEPORT"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ -n "${ARGOCD_NODEPORT:-}" ] && [ "$ARGOCD_NODEPORT" != "N/A" ]; then
|
||||
echo "ArgoCD:"
|
||||
echo " https://$NODE_IP:$ARGOCD_NODEPORT"
|
||||
echo " 用户名: admin"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "常用命令:"
|
||||
echo " 查看所有Pod: kubectl get pods -A"
|
||||
echo " 查看节点: kubectl get nodes"
|
||||
echo " 查看服务: kubectl get svc -A"
|
||||
echo ""
|
||||
|
||||
exit 0
|
||||
else
|
||||
log_error "发现 $FAILED_CHECKS 个失败项,请检查并修复"
|
||||
echo ""
|
||||
echo "故障排查建议:"
|
||||
echo " 1. 查看Pod日志: kubectl logs <pod-name> -n <namespace>"
|
||||
echo " 2. 查看Pod详情: kubectl describe pod <pod-name> -n <namespace>"
|
||||
echo " 3. 查看事件: kubectl get events -A --sort-by='.lastTimestamp'"
|
||||
echo " 4. 重新部署: ./scripts/deploy-all.sh"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user