554 lines
16 KiB
Bash
Executable File
554 lines
16 KiB
Bash
Executable File
#!/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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|