基本框架

This commit is contained in:
2026-03-21 04:36:06 +08:00
commit de1be1dbe5
125 changed files with 10302 additions and 0 deletions

5
ansible/ansible.cfg Normal file
View File

@@ -0,0 +1,5 @@
[defaults]
# 首次连接时跳过 host key 确认(实验室环境可接受)
host_key_checking = False
# 使用 inventory 同目录
inventory = inventory.ini

View File

@@ -0,0 +1,11 @@
# 01-08 HAProxy 配置
用于 `docs/01-08-openwrt-haproxy.md`,可与 Ansible 共用(复制到 OpenWrt 或通过 playbook 下发)。
| 文件 | 说明 |
|------|------|
| haproxy.cfg | 基础配置TCP 健康检查 |
| haproxy-proxy.cfg | 启用 send-proxy-v2Traefik 真实 IP |
| haproxy-proxy-http-tls.cfg | HTTP 检查 + TLS 检查 + PROXY 组合 |
按实际节点 IP 修改 `192.168.2.61``192.168.2.64`。80/443 被封时可将 `bind *:80` / `bind *:443` 改为 `*:18080` / `*:18443`

View File

@@ -0,0 +1,39 @@
# 01-08 HAProxy - 健康检查升级HTTP+TLS+ PROXY Protocol
# 组合k3s_http 用 option httpchkk3s_https 用 ssl-hello-chk均带 send-proxy-v2
# 文档docs/01-08-openwrt-haproxy.md 第 5 节「健康检查与 PROXY 组合」
global
log /dev/log local0
maxconn 4096
defaults
mode http
option httplog
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http_in
bind *:80
default_backend k3s_http
frontend https_in
bind *:443
mode tcp
default_backend k3s_https
backend k3s_http
option httpchk GET /
balance roundrobin
server ylc61 192.168.2.61:80 check send-proxy-v2
server ylc62 192.168.2.62:80 check send-proxy-v2
server ylc63 192.168.2.63:80 check send-proxy-v2
server ylc64 192.168.2.64:80 check send-proxy-v2
backend k3s_https
mode tcp
option ssl-hello-chk
balance roundrobin
server ylc61 192.168.2.61:443 check send-proxy-v2
server ylc62 192.168.2.62:443 check send-proxy-v2
server ylc63 192.168.2.63:443 check send-proxy-v2
server ylc64 192.168.2.64:443 check send-proxy-v2

View File

@@ -0,0 +1,37 @@
# 01-08 HAProxy - 启用 PROXY Protocolsend-proxy-v2
# 用于 Traefik 获取真实客户端 IP需配合 Traefik trustedIPs
# 文档docs/01-08-openwrt-haproxy.md 第 5 节
global
log /dev/log local0
maxconn 4096
defaults
mode http
option httplog
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http_in
bind *:80
default_backend k3s_http
frontend https_in
bind *:443
mode tcp
default_backend k3s_https
backend k3s_http
balance roundrobin
server ylc61 192.168.2.61:80 check send-proxy-v2
server ylc62 192.168.2.62:80 check send-proxy-v2
server ylc63 192.168.2.63:80 check send-proxy-v2
server ylc64 192.168.2.64:80 check send-proxy-v2
backend k3s_https
mode tcp
balance roundrobin
server ylc61 192.168.2.61:443 check send-proxy-v2
server ylc62 192.168.2.62:443 check send-proxy-v2
server ylc63 192.168.2.63:443 check send-proxy-v2
server ylc64 192.168.2.64:443 check send-proxy-v2

View File

@@ -0,0 +1,38 @@
# 01-08 OpenWrt HAProxy 负载均衡 - 基础配置
# 文档docs/01-08-openwrt-haproxy.md
# 将 192.168.2.6164 按实际 K3s 节点 IP 修改
global
log /dev/log local0
maxconn 4096
# 部分 OpenWrt 需 daemon / pidfile按发行版调整若无 /dev/log 可改 log 127.0.0.1 local0
defaults
mode http
option httplog
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http_in
bind *:80
default_backend k3s_http
frontend https_in
bind *:443
mode tcp
default_backend k3s_https
backend k3s_http
balance roundrobin
server ylc61 192.168.2.61:80 check
server ylc62 192.168.2.62:80 check
server ylc63 192.168.2.63:80 check
server ylc64 192.168.2.64:80 check
backend k3s_https
mode tcp
balance roundrobin
server ylc61 192.168.2.61:443 check
server ylc62 192.168.2.62:443 check
server ylc63 192.168.2.63:443 check
server ylc64 192.168.2.64:443 check

View File

@@ -0,0 +1,37 @@
# docs/03-04-k3s-cloudflare-tunnel-配置接入.md — 替换 TUNNEL_TOKEN 后应用
apiVersion: v1
kind: Secret
metadata:
name: cloudflared-credentials
namespace: kube-system
type: Opaque
stringData:
TUNNEL_TOKEN: "<YOUR_TUNNEL_TOKEN>"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflared
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: cloudflared
template:
metadata:
labels:
app: cloudflared
spec:
containers:
- name: cloudflared
image: cloudflare/cloudflared:latest
args:
- tunnel
- run
env:
- name: TUNNEL_TOKEN
valueFrom:
secretKeyRef:
name: cloudflared-credentials
key: TUNNEL_TOKEN

View File

@@ -0,0 +1,9 @@
# GitLab CI 示例(与 docs 对照)
| 文件 | 文档 |
|------|------|
| `gitlab-ci-minimal.example.yml` | `docs/05-04-k3s-配置gitlab-cicd.md` |
| `gitlab-ci-multi-arch-deploy.example.yml` | `docs/05-04-k3s-配置gitlab-cicd.md` |
| `gitlab-ci-runner-tags.example.yml` | `docs/05-03-k3s-安装gitlab-含runner.md` |
复制为 `.gitlab-ci.yml``include` 引用;变量与 Runner 以文档为准。

View File

@@ -0,0 +1,20 @@
# docs/05-04-k3s-配置gitlab-cicd.md — 最小 .gitlab-ci.yml 示例
stages:
- lint
- deploy
variables:
KUBECONFIG: "/builds/${CI_PROJECT_PATH}/kubeconfig"
lint:
stage: lint
script:
- yamllint manifests || true
deploy:
stage: deploy
script:
- echo "$KUBE_CONFIG_CONTENT" > "$KUBECONFIG"
- kubectl --kubeconfig="$KUBECONFIG" apply -f manifests/
only:
- main

View File

@@ -0,0 +1,14 @@
# docs/05-04-k3s-配置gitlab-cicd.md — 多架构 Runner tags 示例
deploy_x86:
stage: deploy
tags: [x86]
script:
- echo "$KUBE_CONFIG_CONTENT" > "$KUBECONFIG"
- kubectl --kubeconfig="$KUBECONFIG" apply -f manifests/x86/
deploy_arm64:
stage: deploy
tags: [arm64]
script:
- echo "$KUBE_CONFIG_CONTENT" > "$KUBECONFIG"
- kubectl --kubeconfig="$KUBECONFIG" apply -f manifests/arm64/

View File

@@ -0,0 +1,15 @@
# docs/05-03-k3s-安装gitlab-含runner.md — Runner tag 与 job 对应示例
build_x86:
tags: [x86]
script:
- echo "build for x86"
build_arm64:
tags: [arm64]
script:
- echo "build for arm64"
build_armv7:
tags: [armv7]
script:
- echo "build for armv7"

View File

@@ -0,0 +1,53 @@
# docs/05-01-k3s-部署homer首页面板.md — 按需修改 host
apiVersion: apps/v1
kind: Deployment
metadata:
name: homer
namespace: homer
spec:
replicas: 1
selector:
matchLabels:
app: homer
template:
metadata:
labels:
app: homer
spec:
containers:
- name: homer
image: b4bz/homer:latest
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: homer
namespace: homer
spec:
selector:
app: homer
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: homer
namespace: homer
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: home.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: homer
port:
number: 80

View File

@@ -0,0 +1,38 @@
# docs/03-05-k3s-local-path-pvc.md
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-pvc-demo
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-local-pvc-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx-local-pvc-demo
template:
metadata:
labels:
app: nginx-local-pvc-demo
spec:
containers:
- name: nginx
image: nginx:alpine
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: local-pvc-demo

View File

@@ -0,0 +1,27 @@
# docs/03-06-k3s-使用nfs存储.md — 按环境修改 server/path
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-demo
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
server: 192.168.2.22
path: /data/nfs
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc-demo
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
volumeName: nfs-pv-demo

View File

@@ -0,0 +1,115 @@
# 03-02 TLS: M1 控制节点 + Ingress路径 /(根路径),域名 test01.jackadam.top
# ConfigMap首页 + default.conf单文件 subPath 挂载,与 M2M4 一致)
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-m1-html
namespace: default
data:
index.html: |
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M1</title></head>
<body><h1>M1</h1><p>控制节点 + Ingress</p><p><strong>Backend: M1</strong></p></body></html>
default.conf: |
server { listen 80 default_server; server_name _; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M1"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-m1
namespace: default
labels:
app: nginx-m1
matrix: "03-02-m1"
spec:
replicas: 1
selector:
matchLabels:
app: nginx-m1
template:
metadata:
labels:
app: nginx-m1
spec:
nodeSelector:
node-role.kubernetes.io/control-plane: ""
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
volumes:
- name: html
configMap:
name: nginx-m1-html
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
readOnly: true
- name: html
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: nginx-m1
namespace: default
spec:
selector:
app: nginx-m1
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-m1
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls.certresolver: cloudflare
spec:
tls:
- hosts:
- test01.jackadam.top
rules:
- host: test01.jackadam.top
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-m1
port:
number: 80
---
# 03-02 HTTP-onlyM1 路由(仅 web无 TLS与 nginx-m1 共用 Service
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-m1-http
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: test01.jackadam.top
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-m1
port:
number: 80

View File

@@ -0,0 +1,98 @@
# 03-02 TLS: M2 控制节点 + IngressRoute路径 /(根路径),域名 test02.jackadam.top
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-m2-html
namespace: default
data:
index.html: |
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M2</title></head>
<body><h1>M2</h1><p>控制节点 + IngressRoute</p></body></html>
default.conf: |
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M2"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-m2
namespace: default
labels:
app: nginx-m2
matrix: "03-02-m2"
spec:
replicas: 1
selector:
matchLabels:
app: nginx-m2
template:
metadata:
labels:
app: nginx-m2
spec:
nodeSelector:
kubernetes.io/hostname: ylc61
volumes:
- name: html
configMap:
name: nginx-m2-html
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
readOnly: true
- name: html
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: nginx-m2
namespace: default
spec:
selector:
app: nginx-m2
ports:
- port: 80
targetPort: 80
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: nginx-m2
namespace: default
spec:
entryPoints:
- websecure
routes:
- match: Host(`test02.jackadam.top`)
kind: Rule
services:
- name: nginx-m2
port: 80
tls:
certResolver: cloudflare
---
# 03-02 HTTP-onlyM2 路由(仅 web无 TLS与 nginx-m2 共用 Service
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: nginx-m2-http
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`test02.jackadam.top`)
kind: Rule
services:
- name: nginx-m2
port: 80

View File

@@ -0,0 +1,110 @@
# 03-02 TLS: M3 工作节点 + Ingress路径 /(根路径),域名 test03.jackadam.top
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-m3-html
namespace: default
data:
index.html: |
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M3</title></head>
<body><h1>M3</h1><p>工作节点 + Ingress</p></body></html>
default.conf: |
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M3"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-m3
namespace: default
labels:
app: nginx-m3
matrix: "03-02-m3"
spec:
replicas: 1
selector:
matchLabels:
app: nginx-m3
template:
metadata:
labels:
app: nginx-m3
spec:
nodeSelector:
node-role.kubernetes.io/worker: ""
volumes:
- name: html
configMap:
name: nginx-m3-html
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
readOnly: true
- name: html
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: nginx-m3
namespace: default
spec:
selector:
app: nginx-m3
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-m3
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls.certresolver: cloudflare
spec:
tls:
- hosts:
- test03.jackadam.top
rules:
- host: test03.jackadam.top
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-m3
port:
number: 80
---
# 03-02 HTTP-onlyM3 路由(仅 web无 TLS与 nginx-m3 共用 Service
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-m3-http
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: test03.jackadam.top
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-m3
port:
number: 80

View File

@@ -0,0 +1,98 @@
# 03-02 TLS: M4 工作节点 + IngressRoute路径 /(根路径),域名 test04.jackadam.top
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-m4-html
namespace: default
data:
index.html: |
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M4</title></head>
<body><h1>M4</h1><p>工作节点 + IngressRoute</p></body></html>
default.conf: |
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M4"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-m4
namespace: default
labels:
app: nginx-m4
matrix: "03-02-m4"
spec:
replicas: 1
selector:
matchLabels:
app: nginx-m4
template:
metadata:
labels:
app: nginx-m4
spec:
nodeSelector:
kubernetes.io/hostname: ylc64
volumes:
- name: html
configMap:
name: nginx-m4-html
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
readOnly: true
- name: html
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: nginx-m4
namespace: default
spec:
selector:
app: nginx-m4
ports:
- port: 80
targetPort: 80
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: nginx-m4
namespace: default
spec:
entryPoints:
- websecure
routes:
- match: Host(`test04.jackadam.top`)
kind: Rule
services:
- name: nginx-m4
port: 80
tls:
certResolver: cloudflare
---
# 03-02 HTTP-onlyM4 路由(仅 web无 TLS与 nginx-m4 共用 Service
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: nginx-m4-http
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`test04.jackadam.top`)
kind: Rule
services:
- name: nginx-m4
port: 80

View File

@@ -0,0 +1,100 @@
# 02-05: Nginx + 控制节点 + IngressM1
# 路径 /demo-m1随机一台控制节点nodeSelector + toleration控制节点常有 NoSchedule 污点)
# ConfigMap首页 + default.conf单文件 subPath 挂载,与 M2M4 一致,便于 nginx 后续扩展)
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-m1-html
namespace: default
data:
index.html: |
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M1</title></head>
<body><h1>M1</h1><p>控制节点 + Ingress</p><p><strong>Backend: M1</strong></p></body></html>
default.conf: |
server { listen 80 default_server; server_name _; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M1"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-m1
namespace: default
labels:
app: nginx-m1
matrix: "02-05-m1"
spec:
replicas: 1
selector:
matchLabels:
app: nginx-m1
template:
metadata:
labels:
app: nginx-m1
spec:
nodeSelector:
node-role.kubernetes.io/control-plane: ""
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
volumes:
- name: html
configMap:
name: nginx-m1-html
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
readOnly: true
- name: html
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: nginx-m1
namespace: default
spec:
selector:
app: nginx-m1
ports:
- port: 80
targetPort: 80
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: stripprefix-m1
namespace: default
spec:
stripPrefix:
prefixes:
- /demo-m1
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-m1
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.middlewares: default-stripprefix-m1@kubernetescrd
spec:
rules:
- http:
paths:
- path: /demo-m1
pathType: Prefix
backend:
service:
name: nginx-m1
port:
number: 80

View File

@@ -0,0 +1,94 @@
# 03-02: Nginx + 控制节点 + IngressRouteM2
# 路径 /demo-m2指定一台控制节点按实际 FQDN 修改 kubernetes.io/hostname
# ConfigMap首页 + default.confX-Backend: M2 便于区分
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-m2-html
namespace: default
data:
index.html: |
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M2</title></head>
<body><h1>M2</h1><p>控制节点 + IngressRoute</p></body></html>
default.conf: |
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M2"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-m2
namespace: default
labels:
app: nginx-m2
matrix: "02-05-m2"
spec:
replicas: 1
selector:
matchLabels:
app: nginx-m2
template:
metadata:
labels:
app: nginx-m2
spec:
nodeSelector:
kubernetes.io/hostname: ylc61
volumes:
- name: html
configMap:
name: nginx-m2-html
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
readOnly: true
- name: html
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: nginx-m2
namespace: default
spec:
selector:
app: nginx-m2
ports:
- port: 80
targetPort: 80
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: stripprefix-m2
namespace: default
spec:
stripPrefix:
prefixes:
- /demo-m2
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: nginx-m2
namespace: default
spec:
entryPoints:
- web
routes:
- match: PathPrefix(`/demo-m2`)
kind: Rule
middlewares:
- name: stripprefix-m2
services:
- name: nginx-m2
port: 80

View File

@@ -0,0 +1,96 @@
# 03-03: Nginx + 工作节点 + IngressM3
# 路径 /demo-m3随机一台工作节点nodeSelector: node-role.kubernetes.io/worker
# ConfigMap首页 + default.confX-Backend: M3 便于区分
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-m3-html
namespace: default
data:
index.html: |
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M3</title></head>
<body><h1>M3</h1><p>工作节点 + Ingress</p></body></html>
default.conf: |
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M3"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-m3
namespace: default
labels:
app: nginx-m3
matrix: "02-05-m3"
spec:
replicas: 1
selector:
matchLabels:
app: nginx-m3
template:
metadata:
labels:
app: nginx-m3
spec:
nodeSelector:
node-role.kubernetes.io/worker: ""
volumes:
- name: html
configMap:
name: nginx-m3-html
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
readOnly: true
- name: html
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: nginx-m3
namespace: default
spec:
selector:
app: nginx-m3
ports:
- port: 80
targetPort: 80
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: stripprefix-m3
namespace: default
spec:
stripPrefix:
prefixes:
- /demo-m3
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-m3
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.middlewares: default-stripprefix-m3@kubernetescrd
spec:
rules:
- http:
paths:
- path: /demo-m3
pathType: Prefix
backend:
service:
name: nginx-m3
port:
number: 80

View File

@@ -0,0 +1,94 @@
# 03-04: Nginx + 工作节点 + IngressRouteM4
# 路径 /demo-m4指定一台工作节点按实际 FQDN 修改 kubernetes.io/hostname
# ConfigMap首页 + default.confX-Backend: M4 便于区分
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-m4-html
namespace: default
data:
index.html: |
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M4</title></head>
<body><h1>M4</h1><p>工作节点 + IngressRoute</p></body></html>
default.conf: |
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M4"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-m4
namespace: default
labels:
app: nginx-m4
matrix: "02-05-m4"
spec:
replicas: 1
selector:
matchLabels:
app: nginx-m4
template:
metadata:
labels:
app: nginx-m4
spec:
nodeSelector:
kubernetes.io/hostname: ylc64
volumes:
- name: html
configMap:
name: nginx-m4-html
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/index.html
subPath: index.html
readOnly: true
- name: html
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: nginx-m4
namespace: default
spec:
selector:
app: nginx-m4
ports:
- port: 80
targetPort: 80
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: stripprefix-m4
namespace: default
spec:
stripPrefix:
prefixes:
- /demo-m4
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: nginx-m4
namespace: default
spec:
entryPoints:
- web
routes:
- match: PathPrefix(`/demo-m4`)
kind: Rule
middlewares:
- name: stripprefix-m4
services:
- name: nginx-m4
port: 80

View File

@@ -0,0 +1,12 @@
# Nginx 矩阵 manifests
用于 `ansible/playbooks/nginx-matrix-deploy.yml` 一键部署。
| 文件 | 场景 | 路径 | 节点 |
|------|------|------|------|
| 01-control-ingress.yaml | M1 控制+Ingress | /demo-m1 | 无 nodeSelector |
| 02-control-ingressroute.yaml | M2 控制+IngressRoute | /demo-m2 | 无 nodeSelector |
| 03-worker-ingress.yaml | M3 工作+Ingress | /demo-m3 | nodeSelector=worker随机 |
| 04-worker-ingressroute.yaml | M4 工作+IngressRoute | /demo-m4 | nodeSelector=ylc64 |
M4 默认指定 ylc64M3 随机工作节点;按实际修改。

View File

@@ -0,0 +1,54 @@
# 对应文档docs/04-01-k3s-nodejs-高级部署.md
# 累积基线Deployment + Service + Ingress
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
containers:
- name: nodejs-demo
image: node:18-alpine
command: ["node", "-e", "require('http').createServer((req,res)=>res.end('Hello World from Node.js')).listen(3000)"]
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- http:
paths:
- path: /node
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,58 @@
# 对应文档docs/04-02-nodejs-镜像与运行命令.md
# 累积04-01 + 固定镜像 tag、imagePullPolicy、command/args
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
command: ["node"]
args:
- "-e"
- "require('http').createServer((req,res)=>res.end('Hello from pinned image')).listen(3000)"
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- http:
paths:
- path: /node
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,75 @@
# 对应文档docs/04-03-nodejs-环境变量与配置注入.md
# 累积04-02 + ConfigMap + 通过 env 注入 APP_MSG镜像仍用 18.20-alpine 与 04-02 一致)
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(3000);
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- http:
paths:
- path: /node
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,75 @@
# 对应文档docs/04-04-nodejs-端口与Service.md
# 累积04-03 + 容器与进程改监听 8080Service targetPort 对齐
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- http:
paths:
- path: /node
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,82 @@
# 对应文档docs/04-05-nodejs-资源请求与限制.md
# 累积04-04 + resources.requests/limits
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- http:
paths:
- path: /node
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,94 @@
# 对应文档docs/04-06-nodejs-探针与健康检查.md
# 累积04-05 + livenessProbe/readinessProbe端口 8080路径 /
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- http:
paths:
- path: /node
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,96 @@
# 对应文档docs/04-07-nodejs-调度与亲和.md
# 累积04-06 + nodeSelector默认 ylc62请改为本集群节点短主机名
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
nodeSelector:
kubernetes.io/hostname: ylc62
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- http:
paths:
- path: /node
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,109 @@
# 对应文档docs/04-08-nodejs-安全上下文.md
# 累积04-07 + pod securityContext.fsGroup、容器 securityContext、只读根、/tmp emptyDir
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
nodeSelector:
kubernetes.io/hostname: ylc62
securityContext:
fsGroup: 1000
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- http:
paths:
- path: /node
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,127 @@
# 对应文档docs/04-09-nodejs-存储与卷.md
# 累积04-08 + PVC nodejs-demo-data默认 storageClassName: local-path可按集群改为 longhorn 等)+ 挂载 /data
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nodejs-demo-data
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
nodeSelector:
kubernetes.io/hostname: ylc62
securityContext:
fsGroup: 1000
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- name: tmp
mountPath: /tmp
- name: data
mountPath: /data
volumes:
- name: tmp
emptyDir: {}
- name: data
persistentVolumeClaim:
claimName: nodejs-demo-data
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- http:
paths:
- path: /node
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,128 @@
# 对应文档docs/04-10-nodejs-Ingress与Traefik.md
# 累积04-09 + Ingress 增加 host、path 改为 /api访问需 Host: app.example.local
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nodejs-demo-data
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
nodeSelector:
kubernetes.io/hostname: ylc62
securityContext:
fsGroup: 1000
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- name: tmp
mountPath: /tmp
- name: data
mountPath: /data
volumes:
- name: tmp
emptyDir: {}
- name: data
persistentVolumeClaim:
claimName: nodejs-demo-data
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: app.example.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,133 @@
# 对应文档docs/04-11-nodejs-副本与滚动发布.md
# 累积04-10 + replicas: 3 + RollingUpdatemaxSurge:1 maxUnavailable:0
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nodejs-demo-data
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
nodeSelector:
kubernetes.io/hostname: ylc62
securityContext:
fsGroup: 1000
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- name: tmp
mountPath: /tmp
- name: data
mountPath: /data
volumes:
- name: tmp
emptyDir: {}
- name: data
persistentVolumeClaim:
claimName: nodejs-demo-data
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: app.example.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,140 @@
# 对应文档docs/04-12-nodejs-TLS与证书.md
# 累积04-11 + Ingress TLSwebsecure、secretName: nodejs-demo-tls
# 应用前请先创建 TLS Secret例如
# kubectl create secret tls nodejs-demo-tls --cert=fullchain.pem --key=privkey.pem -n default
# 证书 SAN 须覆盖 app.example.local与 rules.host / tls.hosts 一致)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nodejs-demo-data
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
nodeSelector:
kubernetes.io/hostname: ylc62
securityContext:
fsGroup: 1000
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- name: tmp
mountPath: /tmp
- name: data
mountPath: /data
volumes:
- name: tmp
emptyDir: {}
- name: data
persistentVolumeClaim:
claimName: nodejs-demo-data
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
tls:
- hosts:
- app.example.local
secretName: nodejs-demo-tls
rules:
- host: app.example.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80

View File

@@ -0,0 +1,157 @@
# 对应文档docs/04-13-nodejs-HPA.md
# 累积04-12 + HorizontalPodAutoscalerCPU 50%min 1 max 5
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nodejs-demo-data
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nodejs-demo-config
namespace: default
data:
APP_MSG: "Hello from ConfigMap"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-demo
namespace: default
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: nodejs-demo
template:
metadata:
labels:
app: nodejs-demo
spec:
nodeSelector:
kubernetes.io/hostname: ylc62
securityContext:
fsGroup: 1000
containers:
- name: nodejs-demo
image: node:18.20-alpine
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
env:
- name: APP_MSG
valueFrom:
configMapKeyRef:
name: nodejs-demo-config
key: APP_MSG
command:
- node
- "-e"
- |
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports:
- containerPort: 8080
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- name: tmp
mountPath: /tmp
- name: data
mountPath: /data
volumes:
- name: tmp
emptyDir: {}
- name: data
persistentVolumeClaim:
claimName: nodejs-demo-data
---
apiVersion: v1
kind: Service
metadata:
name: nodejs-demo
namespace: default
spec:
selector:
app: nodejs-demo
ports:
- port: 80
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nodejs-demo
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
tls:
- hosts:
- app.example.local
secretName: nodejs-demo-tls
rules:
- host: app.example.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: nodejs-demo
port:
number: 80
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nodejs-demo
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nodejs-demo
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50

View File

@@ -0,0 +1,42 @@
# Node.js demo 清单(与 docs/04-0104-14 对齐)
**唯一真源**:本目录下 YAML 与 `docs/` 中说明一致;文档内不重复贴全文,避免漂移。
## 累积规则
- `04-0N-nodejs-demo.yaml` 表示:从 `04-01` 起顺序做完 **04-0104-0N** 各篇能力后的 **一份**`kubectl apply -f` 的完整状态(多资源用 `---` 分隔)。
- **可直接跳到最后一份** 做实验,不必逐文件 apply若要理解每步增量可按编号顺序阅读文档并对照相邻两个 YAML 的差异。
- **04-14**GitOps/CI无独立清单`docs/04-14-nodejs-GitOps与CI流水线.md``docs/05-04-k3s-配置gitlab-cicd.md``docs/03-09-k3s-gitops-集群配置管理.md`
## 文件与文档对照
| 文件 | 文档 | 备注 |
|------|------|------|
| `04-01-nodejs-demo.yaml` | `docs/04-01-k3s-nodejs-高级部署.md` | 基线3000、`/node`、无 host |
| `04-02-nodejs-demo.yaml` | `docs/04-02-nodejs-镜像与运行命令.md` | 固定镜像 tag、`imagePullPolicy` |
| `04-03-nodejs-demo.yaml` | `docs/04-03-nodejs-环境变量与配置注入.md` | + ConfigMapSecret 示例见文末 `nodejs-demo-secret.example.yaml` |
| `04-04-nodejs-demo.yaml` | `docs/04-04-nodejs-端口与Service.md` | 监听改 **8080**(自 04-04 起探针与后续均用 8080 |
| `04-05-nodejs-demo.yaml` | `docs/04-05-nodejs-资源请求与限制.md` | + resources |
| `04-06-nodejs-demo.yaml` | `docs/04-06-nodejs-探针与健康检查.md` | + 探针 |
| `04-07-nodejs-demo.yaml` | `docs/04-07-nodejs-调度与亲和.md` | + `nodeSelector`(默认 **ylc62**,请改为本机节点名) |
| `04-08-nodejs-demo.yaml` | `docs/04-08-nodejs-安全上下文.md` | + 非 root、只读根、`/tmp` emptyDir |
| `04-09-nodejs-demo.yaml` | `docs/04-09-nodejs-存储与卷.md` | + PVC `nodejs-demo-data`(默认 **local-path** |
| `04-10-nodejs-demo.yaml` | `docs/04-10-nodejs-Ingress与Traefik.md` | Ingress`host` + `/api`curl 需 **Host** |
| `04-11-nodejs-demo.yaml` | `docs/04-11-nodejs-副本与滚动发布.md` | replicas=3 + RollingUpdate |
| `04-12-nodejs-demo.yaml` | `docs/04-12-nodejs-TLS与证书.md` | **websecure** + TLS须先创建 `nodejs-demo-tls` Secret |
| `04-13-nodejs-demo.yaml` | `docs/04-13-nodejs-HPA.md` | + HPA需 metrics-server |
## 应用方式
```bash
# 仓库根目录
kubectl apply -f ansible/files/nodejs-demo/04-01-nodejs-demo.yaml
```
或使用 Ansible`ansible/playbooks/nodejs-demo-apply.yml`,变量 `nodejs_demo_manifest` 指定文件名。
## dry-run
```bash
kubectl apply --dry-run=client -f ansible/files/nodejs-demo/04-01-nodejs-demo.yaml
```

View File

@@ -0,0 +1,8 @@
# 示例:勿将真实密钥提交到公开仓库。对应 docs/04-03 Secret 示意。
apiVersion: v1
kind: Secret
metadata:
name: nodejs-demo-secret
namespace: default
stringData:
API_TOKEN: "replace-me"

View File

@@ -0,0 +1,43 @@
# docs/05-02-onenav首页面板.md — 修改 Endpoints IP 与 Ingress host
apiVersion: v1
kind: Service
metadata:
name: onenav-external
namespace: default
spec:
ports:
- name: http
port: 80
targetPort: 7070
---
apiVersion: v1
kind: Endpoints
metadata:
name: onenav-external
namespace: default
subsets:
- addresses:
- ip: 192.168.2.22
ports:
- port: 7070
name: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: onenav
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: onenav.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: onenav-external
port:
number: 80

View File

@@ -0,0 +1,74 @@
# docs/05-08-openclaw-k3s-实验部署.md — 实验用;替换镜像与域名
apiVersion: v1
kind: Namespace
metadata:
name: openclaw
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: openclaw-gateway
namespace: openclaw
spec:
replicas: 1
selector:
matchLabels:
app: openclaw-gateway
template:
metadata:
labels:
app: openclaw-gateway
spec:
containers:
- name: openclaw-gateway
image: registry.local/openclaw:local
imagePullPolicy: IfNotPresent
env:
- name: OPENCLAW_GATEWAY_MODE
value: "local"
ports:
- containerPort: 18789
volumeMounts:
- name: config
mountPath: /home/node/.openclaw
- name: workspace
mountPath: /home/node/.openclaw/workspace
volumes:
- name: config
emptyDir: {}
- name: workspace
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: openclaw-gateway
namespace: openclaw
spec:
selector:
app: openclaw-gateway
ports:
- port: 18789
targetPort: 18789
protocol: TCP
name: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: openclaw-gateway
namespace: openclaw
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: openclaw-k3s.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: openclaw-gateway
port:
number: 18789

View File

@@ -0,0 +1,43 @@
# docs/05-07-openclaw应用部署.md — 修改 IP / host
apiVersion: v1
kind: Service
metadata:
name: openclaw-external
namespace: default
spec:
ports:
- name: http
port: 80
targetPort: 18789
---
apiVersion: v1
kind: Endpoints
metadata:
name: openclaw-external
namespace: default
subsets:
- addresses:
- ip: 192.168.2.70
ports:
- port: 18789
name: http
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: openclaw
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: openclaw.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: openclaw-external
port:
number: 80

View File

@@ -0,0 +1,27 @@
# docs/06-03-k3s-自动备份与恢复-openlist-webdav.md — 替换镜像、hostPath、远端名
apiVersion: batch/v1
kind: CronJob
metadata:
name: app-data-backup
namespace: default
spec:
schedule: "0 3 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: app-data-backup
image: your-registry/app-backup:latest
args:
- /bin/sh
- -c
- rclone sync /data openlist-webdav:backups/app-data
volumeMounts:
- name: app-data
mountPath: /data
volumes:
- name: app-data
hostPath:
path: /data/app
restartPolicy: OnFailure

View File

@@ -0,0 +1,24 @@
# docs/06-03-k3s-自动备份与恢复-openlist-webdav.md — 一次性恢复 Job
apiVersion: batch/v1
kind: Job
metadata:
name: app-data-restore
namespace: default
spec:
template:
spec:
containers:
- name: app-data-restore
image: your-registry/app-backup:latest
args:
- /bin/sh
- -c
- rclone sync openlist-webdav:backups/app-data /data
volumeMounts:
- name: app-data
mountPath: /data
volumes:
- name: app-data
hostPath:
path: /data/app
restartPolicy: OnFailure

View File

@@ -0,0 +1,27 @@
# docs/05-06-openlist挂载网盘与自动备份.md — 替换镜像与 PVC 名
apiVersion: batch/v1
kind: CronJob
metadata:
name: openlist-backup
namespace: default
spec:
schedule: "0 3 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: openlist-backup
image: your-registry/openlist-backup:latest
args:
- /bin/sh
- -c
- /backup.sh
volumeMounts:
- name: backup-target
mountPath: /backup
volumes:
- name: backup-target
persistentVolumeClaim:
claimName: openlist-backup-pvc
restartPolicy: OnFailure

View File

@@ -0,0 +1,38 @@
# 03-02 Traefik ACME 配置HelmChartConfig
# 含ACMECloudflare DNS-01、ping 健康检查websecure、PROXY protocol trustedIPs
# 使用前:替换 <YOUR_REAL_EMAIL>,创建 cloudflare-api-token Secret按实际修改 nodeSelector/trustedIPs
# 部署kubectl apply -f traefik-acme.yaml或复制到 K3s manifests 目录)
---
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
additionalArguments:
- "--log.level=INFO"
- "--certificatesresolvers.cloudflare.acme.dnschallenge.resolvers=1.1.1.1:53,1.0.0.1:53"
- "--certificatesresolvers.cloudflare.acme.email=<YOUR_REAL_EMAIL>"
- "--certificatesresolvers.cloudflare.acme.storage=/data/acme.json"
# - "--certificatesresolvers.cloudflare.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" # 测试用,上线前删除
- "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.cloudflare.acme.dnschallenge.propagation.delayBeforeChecks=600"
# 健康检查GET /ping 在 443(HTTPS) 返回 200供 HAProxy 对 443 做 option httpchk + ssl
- "--ping=true"
- "--ping.entryPoint=websecure"
# PROXY protocoltrustedIPs 需包含 HAProxy 所在 IP/网段
- "--entrypoints.web.proxyProtocol.trustedIPs=192.168.2.0/24"
- "--entrypoints.websecure.proxyProtocol.trustedIPs=192.168.2.0/24"
env:
- name: CF_DNS_API_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-api-token
key: api-token
nodeSelector:
kubernetes.io/hostname: ylc61

View File

@@ -0,0 +1,25 @@
---
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
web:
expose: true
websecure:
expose: true
# 自定义 HTTP 入口(示例 18080
web18080:
port: 18080
expose:
default: true
exposedPort: 18080
# 自定义 HTTPS 入口(示例 18443
websecure18443:
port: 18443
expose:
default: true
exposedPort: 18443

View File

@@ -0,0 +1,60 @@
# docs/03-03 第 5 节Tomcat + test05.jackadam.top 验证 HTTPS请按需改域名
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-test05
namespace: default
labels:
app: tomcat-test05
spec:
replicas: 1
selector:
matchLabels:
app: tomcat-test05
template:
metadata:
labels:
app: tomcat-test05
spec:
containers:
- name: tomcat
image: tomcat:9.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: tomcat-test05
namespace: default
spec:
selector:
app: tomcat-test05
ports:
- port: 8080
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tomcat-test05-acme
namespace: default
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls.certresolver: cloudflare
spec:
tls:
- hosts:
- test05.jackadam.top
rules:
- host: test05.jackadam.top
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tomcat-test05
port:
number: 8080

View File

@@ -0,0 +1,49 @@
# 03-03 Traefik Dashboard + ACME 合并配置HelmChartConfig
# 含Dashboard、ACMECloudflare DNS-01、ping、PROXY protocol与 03-02 一致)
# 使用前:替换 <YOUR_REAL_EMAIL>,创建 cloudflare-api-token Secret按实际修改 nodeSelector/trustedIPs
# 部署kubectl apply -f traefik-dashboard-acme.yaml
---
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
web:
expose: true
websecure:
expose: true
additionalArguments:
- "--api.dashboard=true"
- "--api.insecure=true"
- "--log.level=INFO"
- "--certificatesresolvers.cloudflare.acme.dnschallenge.resolvers=1.1.1.1:53,1.0.0.1:53"
- "--certificatesresolvers.cloudflare.acme.email=<YOUR_REAL_EMAIL>"
- "--certificatesresolvers.cloudflare.acme.storage=/data/acme.json"
# - "--certificatesresolvers.cloudflare.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" # 测试用,上线前删除
- "--certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.cloudflare.acme.dnschallenge.propagation.delayBeforeChecks=600"
- "--ping=true"
- "--ping.entryPoint=websecure"
- "--entrypoints.web.proxyProtocol.trustedIPs=192.168.2.0/24"
- "--entrypoints.websecure.proxyProtocol.trustedIPs=192.168.2.0/24"
env:
- name: CF_DNS_API_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-api-token
key: api-token
nodeSelector:
kubernetes.io/hostname: ylc61
ingressRoute:
dashboard:
enabled: true

View File

@@ -0,0 +1,37 @@
# 03-01 Traefik DashboardHelmChartConfig + IngressRoute
# 部署kubectl apply -f traefik-dashboard.yaml或复制到 K3s server/manifests/
---
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
web:
expose: true
websecure:
expose: true
traefik:
expose: true
additionalArguments:
- "--api.dashboard=true"
- "--api.insecure=true"
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
namespace: kube-system
spec:
entryPoints:
- web
routes:
- match: PathPrefix(`/dashboard`) || PathPrefix(`/api`)
kind: Rule
services:
- name: api@internal
kind: TraefikService

View File

@@ -0,0 +1,22 @@
---
# 使用 root SSH 连接setup-k3s-workers-ssh.sh 已将同一公钥写入各节点 root
ansible_user: root
timezone: "Asia/Shanghai"
# k3s 相关
k3s_version: "" # 为空表示用 get.k3s.io 默认最新
k3s_data_dir: "/storage"
k3s_server_ip: "192.168.2.61"
# 可选:是否管理 /etc/hosts、firewalld 基线
k3s_manage_hosts: true
k3s_manage_firewalld: true
#(入口节点选择已改为手动 kubectl 打标,不再通过 Ansible 管理 Traefik 入口标签)
## 角色标签(自动打标开关)
# 若希望 Ansible 在安装后自动为节点打 control-plane / worker 角色标签,用于 02-05 矩阵等场景,
# 可开启此开关;默认 true 表示自动按 inventory 中的 k3s_server / k3s_worker 分组打标。
# 如需完全手动管理角色标签,可改为 false并参考 `01-02-k3s-工作节点.md` 中的 kubectl 示例。
k3s_manage_role_labels: true

14
ansible/inventory.ini Normal file
View File

@@ -0,0 +1,14 @@
[k3s_server]
# root SSH 连接setup-k3s-workers-ssh.sh 会配置所有节点(含控制节点)
ylc61 ansible_host=192.168.2.61 ansible_ssh_private_key_file=~/.ssh/id_ed25519_k3s_192.168.2.61
[k3s_worker]
# 使用 setup-k3s-workers-ssh.sh 生成的每节点密钥,路径需与控制机实际一致
ylc62 ansible_host=192.168.2.62 ansible_ssh_private_key_file=~/.ssh/id_ed25519_k3s_192.168.2.62
ylc63 ansible_host=192.168.2.63 ansible_ssh_private_key_file=~/.ssh/id_ed25519_k3s_192.168.2.63
ylc64 ansible_host=192.168.2.64 ansible_ssh_private_key_file=~/.ssh/id_ed25519_k3s_192.168.2.64
[k3s_nodes:children]
k3s_server
k3s_worker

View File

@@ -0,0 +1,197 @@
---
- name: Init base system
hosts: k3s_nodes
become: true
tasks:
# 检查当前节点上 firewalld 的运行状态,供后续条件判断使用
- name: Check if firewalld is running
ansible.builtin.command: firewall-cmd --state
register: firewalld_state
changed_when: false
failed_when: false
# 根据全局 timezone 变量设置系统时区(可选)
- name: Set timezone
ansible.builtin.command: timedatectl set-timezone {{ timezone }}
when: timezone is defined and timezone != ""
# 安装 k3s 所需的基础工具包curl、git 等)
- name: Install basic packages
ansible.builtin.package:
name:
- curl
- git
state: present
# 确保 /etc/hosts 中包含所有 k3s 节点的主机名解析(可选)
- name: Ensure /etc/hosts has entries for all k3s nodes
ansible.builtin.lineinfile:
path: /etc/hosts
regexp: '^\S+\s+{{ item }}\s*$'
line: "{{ hostvars[item]['ansible_host'] }} {{ item }}"
state: present
loop: "{{ groups['k3s_nodes'] }}"
when:
- k3s_manage_hosts | default(true) | bool
- hostvars[item]['ansible_host'] is defined
# k3s 所需端口8472/udpflannel VXLAN全部节点6443/tcpAPI仅 server
# 必须在安装 k3s 前开放,否则 worker 无法连接、flannel 无法建立 overlay
# 在所有 k3s 节点上开放 flannel VXLAN 所需的 8472/udp 端口
- name: Open flannel VXLAN port (8472/udp) on all k3s nodes
ansible.builtin.command: firewall-cmd --permanent --add-port=8472/udp
when:
- k3s_manage_firewalld | default(true) | bool
- firewalld_state.stdout | default('') == 'running'
# 在 server 节点上开放 k3s API 端口 6443/tcp
- name: Open k3s API port (6443/tcp) on server
ansible.builtin.command: firewall-cmd --permanent --add-port=6443/tcp
when:
- k3s_manage_firewalld | default(true) | bool
- inventory_hostname in groups['k3s_server']
- firewalld_state.stdout | default('') == 'running'
# 在完成端口放行后重新加载 firewalld 规则
- name: Reload firewalld after opening k3s ports
ansible.builtin.command: firewall-cmd --reload
when:
- k3s_manage_firewalld | default(true) | bool
- firewalld_state.stdout | default('') == 'running'
- name: Install k3s server
hosts: k3s_server
become: true
tasks:
# 在 server 节点上下载安装并启动 k3s server 进程
- name: Download and install k3s server
ansible.builtin.shell: |
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --data-dir={{ k3s_data_dir }}" sh -
args:
creates: "{{ k3s_data_dir }}/server"
- name: Install k3s agent (workers)
hosts: k3s_worker
become: true
serial: 1 # 逐台安装,减轻并行下载对网络的压力
tasks:
# 从首个 server 节点读取集群 token仅执行一次
- name: Read k3s token from first server
ansible.builtin.slurp:
src: "{{ k3s_data_dir }}/server/token"
delegate_to: "{{ groups['k3s_server'][0] }}"
run_once: true
register: k3s_token_from_server
# 在各 worker 节点上保存解码后的 token 供后续安装使用
- name: Set fact for k3s token on workers
ansible.builtin.set_fact:
k3s_token: "{{ k3s_token_from_server.content | b64decode | trim }}"
# 在每个 worker 节点上下载安装并启动 k3s agent 进程
- name: Install k3s agent
ansible.builtin.shell: |
curl -sfL https://get.k3s.io | K3S_URL=https://{{ k3s_server_ip }}:6443 K3S_TOKEN={{ k3s_token }} INSTALL_K3S_EXEC="agent --data-dir={{ k3s_data_dir }}" sh -
args:
creates: "{{ k3s_data_dir }}/agent"
async: 600
poll: 15
- name: Configure firewalld baseline for k3s (flannel.1 / cni0 -> trusted)
hosts: k3s_nodes
become: true
tasks:
# 为 k3s 配置 firewalld 基线:将 flannel.1 / cni0 加入 trusted 区域
- block:
# 检查节点上 firewalld 是否可用
- name: Check if firewalld is available
ansible.builtin.command: firewall-cmd --state
register: firewalld_check
changed_when: false
failed_when: false
# 等待 CNI 接口 flannel.1 和 cni0 出现k3s 启动并创建完成)
- name: Wait for CNI interfaces (flannel.1, cni0) to appear
ansible.builtin.shell: |
for i in $(seq 1 120); do
ip link show flannel.1 >/dev/null 2>&1 && ip link show cni0 >/dev/null 2>&1 && exit 0
sleep 1
done
exit 1
when: firewalld_check.stdout == 'running'
# 将 flannel.1 / cni0 接口加入 firewalld trusted 区域(运行时和永久)
- name: Add flannel.1 and cni0 to firewalld trusted zone (runtime + permanent)
ansible.builtin.shell: |
firewall-cmd --zone=trusted --add-interface={{ item }}
firewall-cmd --permanent --zone=trusted --add-interface={{ item }}
loop:
- flannel.1
- cni0
when: firewalld_check.stdout == 'running'
# 更新 firewalld 配置使新接口规则立即生效
- name: Reload firewalld
ansible.builtin.command: firewall-cmd --reload
when: firewalld_check.stdout == 'running'
when: k3s_manage_firewalld | default(true) | bool
- name: 安装后验证 - traefik / nodes / curl
hosts: k3s_server
become: true
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
tasks:
# 安装后为控制节点打 control-plane 标签02-05 矩阵 M1 需此标签才能调度),节点名与 inventory 短主机名一致ylc61ylc64
- name: Label control-plane nodes (k3s 不默认打标M1 需此标签)
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl label node {{ item }} node-role.kubernetes.io/control-plane= --overwrite
loop: "{{ groups['k3s_server'] | default([]) }}"
# 可选:为工作节点打 worker 标签02-05 矩阵 M3 需要)
- name: 可选 - 为工作节点打 worker 标签02-05 矩阵 M3 需要)
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl label node {{ item }} node-role.kubernetes.io/worker= --overwrite
loop: "{{ groups['k3s_worker'] | default([]) }}"
when: k3s_manage_role_labels | default(true) | bool
# 查看 kube-system 命名空间中与 Traefik / svclb 相关的 Pod 列表
- name: kubectl get pods -n kube-systemtraefik / svclb
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl get pods -n kube-system -o wide | grep -E 'NAME|traefik|svclb'
register: verify_traefik
changed_when: false
# 打印上一步查询到的 Traefik 相关 Pod 信息
- name: ">>> Traefik 相关 Pods"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ verify_traefik.stdout_lines }}"
# 查询当前集群中的节点列表
- name: kubectl get nodes
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl get nodes
register: verify_nodes
changed_when: false
# 打印节点列表结果,方便确认节点状态与角色
- name: ">>> kubectl get nodes"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ verify_nodes.stdout_lines }}"
# 通过 curl 测试每个节点 80 与 443 入口连通性
- name: curl 测试各节点 80/443 可达性
ansible.builtin.shell: |
for ip in {{ groups['k3s_nodes'] | map('extract', hostvars) | map(attribute='ansible_host') | join(' ') }}; do
c80=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 2 http://$ip 2>/dev/null) || c80="fail"
c443=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 2 https://$ip 2>/dev/null) || c443="fail"
echo "$ip: 80=$c80 443=$c443"
done
register: verify_curl
changed_when: false
- name: ">>> curl 结果"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ verify_curl.stdout_lines }}"

View File

@@ -0,0 +1,166 @@
---
# Ansible 一键部署 nginx 矩阵M1M4
# 对应文档docs/02-05-nginx-验证矩阵-一键部署.md02-0102-04 分篇已整合)
#
# 说明:复制 manifests → kubectl apply → 等待 Pod 就绪 → 验证 Pod 节点分布 → curl 16 目标
# manifestsansible/files/nginx-matrix/M1 control-plane / M2 ylc61 / M3 worker / M4 ylc64按实际修改 02/04 hostname
#
# 执行(在 ansible/ 目录下):
# ansible-playbook -i inventory.ini playbooks/nginx-matrix-deploy.yml
# 或在仓库根目录:
# ansible-playbook -i ansible/inventory.ini ansible/playbooks/nginx-matrix-deploy.yml
- name: Deploy nginx matrix (M1~M4)
hosts: k3s_server
become: true
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
# manifests 在 ansible/files/nginx-matrix/,与 playbook 同项目
manifests_path: "{{ playbook_dir }}/../files/nginx-matrix"
tasks:
- name: Ensure manifests path exists
ansible.builtin.stat:
path: "{{ manifests_path }}"
register: manifests_stat
- name: Fail if manifests not found
ansible.builtin.fail:
msg: "manifests 未找到: {{ manifests_path }},请从仓库根目录或 ansible 同级执行"
when: not manifests_stat.stat.exists
# 部署前确保 control-plane/worker 标签存在M1/M3 需此才能调度节点名为短主机名ylc61ylc64
- name: Ensure control-plane label on k3s_server nodes (for M1)
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl label node {{ item }} node-role.kubernetes.io/control-plane= --overwrite
loop: "{{ groups['k3s_server'] | default([]) }}"
- name: Ensure worker label on k3s_worker nodes (for M3)
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl label node {{ item }} node-role.kubernetes.io/worker= --overwrite
loop: "{{ groups['k3s_worker'] | default([]) }}"
- name: Copy nginx matrix manifests to server
ansible.builtin.copy:
src: "{{ manifests_path }}/"
dest: /tmp/nginx-matrix/
mode: '0644'
# 先删全部 nginx 矩阵 Deployment 再 apply避免旧 ReplicaSet 导致任一 Mx 仍显示默认页
- name: Delete all nginx matrix deployments before apply
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl delete deployment nginx-m1 nginx-m2 nginx-m3 nginx-m4 -n default --ignore-not-found=true
register: del_nginx
changed_when: "'deleted' in del_nginx.stdout"
- name: kubectl apply nginx matrix
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f /tmp/nginx-matrix/ -R
register: k8s_apply
changed_when: "'configured' in k8s_apply.stdout or 'created' in k8s_apply.stdout"
- name: Restart nginx deployments so pods pick up ConfigMap (M1M4 标识)
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout restart deployment nginx-m1 nginx-m2 nginx-m3 nginx-m4 -n default
register: restart_out
changed_when: true
- name: Wait for nginx pods to be ready
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m1 --timeout=60s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m2 --timeout=60s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m3 --timeout=120s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m4 --timeout=120s
register: wait_result
changed_when: false
- name: Verify nginx matrix
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl get pod,svc,ing,ingressroute -n default -o wide
register: verify
changed_when: false
- name: ">>> nginx matrix 资源"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ verify.stdout_lines }}"
- name: 验证 Pod 节点分布M1/M2 应在控制节点M3/M4 应在工作节点)
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl get pod -n default -o custom-columns='NAME:.metadata.name,APP:.metadata.labels.app,NODE:.spec.nodeName' | grep -E '^(NAME|nginx-m)'
register: pod_placement
changed_when: false
- name: ">>> Pod 节点分布"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ pod_placement.stdout_lines }}"
- name: M1 容器内诊断(排查为何仍为 nginx 欢迎页)
ansible.builtin.shell: |
echo "========== 1. M1 容器内 /usr/share/nginx/html/ 目录 =========="
KUBECONFIG={{ k3s_kubeconfig }} kubectl exec -n default deployment/nginx-m1 -- ls -la /usr/share/nginx/html/ 2>/dev/null || echo "(exec 失败)"
echo ""
echo "========== 2. M1 容器内 index.html 内容(前 5 行)=========="
KUBECONFIG={{ k3s_kubeconfig }} kubectl exec -n default deployment/nginx-m1 -- cat /usr/share/nginx/html/index.html 2>/dev/null | head -5 || echo "(exec 失败)"
echo ""
echo "========== 3. M1 容器内 /etc/nginx/conf.d/ 目录 =========="
KUBECONFIG={{ k3s_kubeconfig }} kubectl exec -n default deployment/nginx-m1 -- ls -la /etc/nginx/conf.d/ 2>/dev/null || echo "(exec 失败)"
echo ""
echo "========== 4. M1 容器内 default.conf 内容 =========="
KUBECONFIG={{ k3s_kubeconfig }} kubectl exec -n default deployment/nginx-m1 -- cat /etc/nginx/conf.d/default.conf 2>/dev/null || echo "(exec 失败)"
echo ""
echo "========== 5. M1 容器内 nginx 生效配置中的 server 块(前 40 行)=========="
KUBECONFIG={{ k3s_kubeconfig }} kubectl exec -n default deployment/nginx-m1 -- nginx -T 2>/dev/null | grep -A 200 "server {" | head -40 || echo "(exec 失败)"
register: m1_diag
changed_when: false
failed_when: false
- name: ">>> M1 容器内诊断结果(若 M1 仍为欢迎页,请根据此处输出排查)"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ m1_diag.stdout_lines }}"
- name: 验证 M1M4 标识Pod 内 index.html 含 Mx、响应头 X-Backend
ansible.builtin.shell: |
base="{{ groups['k3s_nodes'] | map('extract', hostvars) | map(attribute='ansible_host') | first }}"
for id in 1 2 3 4; do
echo "=== M$id Pod 内 index.html 前 2 行 ==="
KUBECONFIG={{ k3s_kubeconfig }} kubectl exec -n default deployment/nginx-m$id -- cat /usr/share/nginx/html/index.html 2>/dev/null | head -2 || echo "(exec 失败)"
echo "=== M$id 响应头 X-Backend ==="
curl -sI "http://$base/demo-m$id/" 2>/dev/null | grep -i x-backend || echo "(未看到 X-Backend)"
echo ""
done
register: m_check
changed_when: false
failed_when: false
- name: ">>> M1M4 验证"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ m_check.stdout_lines }}"
- name: curl 验证16 个目标4 节点 × 4 路径)
ansible.builtin.shell: |
bases="{{ groups['k3s_nodes'] | map('extract', hostvars) | map(attribute='ansible_host') | join(' ') }}"
paths="/demo-m1 /demo-m2 /demo-m3 /demo-m4"
count=0
ok=0
echo "=== 16 个目标 (4 节点 × 4 路径) ==="
echo "节点 M1(控制+Ingress) M2(控制+IR) M3(工作+Ingress) M4(工作+IR)"
for base in $bases; do
m1=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 http://$base/demo-m1 2>/dev/null) || m1="fail"
m2=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 http://$base/demo-m2 2>/dev/null) || m2="fail"
m3=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 http://$base/demo-m3 2>/dev/null) || m3="fail"
m4=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 http://$base/demo-m4 2>/dev/null) || m4="fail"
printf "%-12s %-16s %-11s %-16s %s\n" "$base" "$m1" "$m2" "$m3" "$m4"
for c in $m1 $m2 $m3 $m4; do count=$((count+1)); [ "$c" = "200" ] && ok=$((ok+1)); done
done
echo "---"
echo "共验证 $count 个目标,$ok 个返回 200"
register: curl_result
changed_when: false
- name: ">>> curl 矩阵"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ curl_result.stdout_lines }}"

View File

@@ -0,0 +1,188 @@
---
# Ansible 一键部署 nginx 矩阵 TLS 版M1M4HTTPS
# 对应文档docs/03-02-k3s-traefik-acme.md
#
# 说明:复制 TLS + HTTP-only manifests → 自动删除已存在的不含 TLS 的 nginx 矩阵02-05→ kubectl apply含 TLS 与 HTTP-only 共 8 个路由)→ 等待 Pod 就绪 → HTTP-only / HTTPS curl 矩阵验证test01test04.jackadam.top
# manifestsansible/files/nginx-matrix-tls/,域名为 test01test04.jackadam.topM2/M4 hostname 按实际修改Ingress/IngressRoute 中 TLS 路由仅绑定 websecureHTTP-only 路由仅绑定 web
# 前置:已按 03-02 配置 ACMESecret + traefik-acme.yaml且 test01test04.jackadam.top 已解析到入口 IP
#
# 执行(在 ansible/ 目录下):
# ansible-playbook -i inventory.ini playbooks/nginx-matrix-tls-deploy.yml
# 或在仓库根目录:
# ansible-playbook -i ansible/inventory.ini ansible/playbooks/nginx-matrix-tls-deploy.yml
# 验证时对所有 k3s_nodes 做 HTTPS 请求(所有节点均为入口点,与 02-05 HTTP 矩阵一致)
- name: Deploy or cleanup nginx matrix TLS (M1~M4, HTTPS)
hosts: k3s_server
become: true
run_once: true
vars:
# mode 由 -e mode=cleanup 传入,未传时默认为 deploy勿在 vars 中写 mode: "{{ mode | default('deploy') }}" 会递归)
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
manifests_path: "{{ playbook_dir }}/../files/nginx-matrix-tls"
tls_domains:
- test01.jackadam.top
- test02.jackadam.top
- test03.jackadam.top
- test04.jackadam.top
tasks:
- name: Deploy nginx matrix TLS (mode=deploy)
when: (mode | default('deploy')) == 'deploy'
block:
- name: Ensure manifests path exists
ansible.builtin.stat:
path: "{{ manifests_path }}"
register: manifests_stat
- name: Fail if manifests not found
ansible.builtin.fail:
msg: "manifests 未找到: {{ manifests_path }},请从仓库根目录或 ansible 同级执行"
when: not manifests_stat.stat.exists
# 部署前确保 control-plane/worker 标签存在M1/M3 需此才能调度节点名为短主机名ylc61ylc64
- name: Ensure control-plane label on k3s_server nodes (for M1)
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl label node {{ item }} node-role.kubernetes.io/control-plane= --overwrite
loop: "{{ groups['k3s_server'] | default([]) }}"
- name: Ensure worker label on k3s_worker nodes (for M3)
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl label node {{ item }} node-role.kubernetes.io/worker= --overwrite
loop: "{{ groups['k3s_worker'] | default([]) }}"
- name: Copy nginx matrix TLS manifests to server
ansible.builtin.copy:
src: "{{ manifests_path }}/"
dest: /tmp/nginx-matrix-tls/
mode: '0644'
# 若存在不含 TLS 的 nginx 矩阵02-05先删掉避免与 TLS 版 Ingress 冲突或残留
- name: Delete non-TLS nginx matrix if present (deployments, ingress, ingressroute, middleware, configmaps)
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete deployment,svc -n default nginx-m1 nginx-m2 nginx-m3 nginx-m4 --ignore-not-found=true
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete ingress -n default nginx-m1 nginx-m3 --ignore-not-found=true
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete ingressroute -n default nginx-m2 nginx-m4 --ignore-not-found=true
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete middleware -n default stripprefix-m1 stripprefix-m2 stripprefix-m3 stripprefix-m4 --ignore-not-found=true
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete configmap -n default nginx-m1-html nginx-m2-html nginx-m3-html nginx-m4-html --ignore-not-found=true
register: del_non_tls
changed_when: "'deleted' in del_non_tls.stdout"
- name: kubectl apply nginx matrix TLS + HTTP-only
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f /tmp/nginx-matrix-tls/ -R
register: k8s_apply
changed_when: "'configured' in k8s_apply.stdout or 'created' in k8s_apply.stdout"
- name: Restart nginx deployments so pods pick up ConfigMap (M1M4 标识)
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout restart deployment nginx-m1 nginx-m2 nginx-m3 nginx-m4 -n default
register: restart_out
changed_when: true
- name: Wait for nginx pods to be ready
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m1 --timeout=60s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m2 --timeout=60s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m3 --timeout=120s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m4 --timeout=120s
register: wait_result
changed_when: false
- name: Verify nginx matrix TLS resources
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl get pod,svc,ing,ingressroute -n default -o wide
register: verify
changed_when: false
- name: ">>> nginx matrix TLS 资源"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ verify.stdout_lines }}"
- name: 验证 M1M4 标识Pod 内 index.html 含 Mx、响应头 X-Backend取首个入口节点
ansible.builtin.shell: |
first_ip="{{ groups['k3s_nodes'] | map('extract', hostvars) | map(attribute='ansible_host') | first }}"
for id in 1 2 3 4; do
echo "=== M$id Pod 内 index.html 前 2 行 ==="
KUBECONFIG={{ k3s_kubeconfig }} kubectl exec -n default deployment/nginx-m$id -- cat /usr/share/nginx/html/index.html 2>/dev/null | head -2 || echo "(exec 失败)"
echo "=== M$id 响应头 X-Backend (入口 $first_ip) ==="
curl -sI "https://test0$id.jackadam.top/" --resolve "test0$id.jackadam.top:443:$first_ip" -k 2>/dev/null | grep -i x-backend || echo "(未看到 X-Backend)"
echo ""
done
register: m_check
changed_when: false
failed_when: false
- name: ">>> M1M4 验证"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ m_check.stdout_lines }}"
- name: HTTP curl 验证HTTP-only16 个目标,所有节点 × 4 域名)
ansible.builtin.shell: |
bases="{{ groups['k3s_nodes'] | map('extract', hostvars) | map(attribute='ansible_host') | join(' ') }}"
count=0
ok=0
echo "=== 16 个目标 (4 节点 × 4 域名) HTTP ==="
echo "节点 M1(test01) M2(test02) M3(test03) M4(test04)"
for base in $bases; do
m1=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://test01.jackadam.top/ --resolve "test01.jackadam.top:80:$base" 2>/dev/null) || m1="fail"
m2=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://test02.jackadam.top/ --resolve "test02.jackadam.top:80:$base" 2>/dev/null) || m2="fail"
m3=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://test03.jackadam.top/ --resolve "test03.jackadam.top:80:$base" 2>/dev/null) || m3="fail"
m4=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://test04.jackadam.top/ --resolve "test04.jackadam.top:80:$base" 2>/dev/null) || m4="fail"
printf "%-12s %-14s %-14s %-14s %s\n" "$base" "$m1" "$m2" "$m3" "$m4"
for c in $m1 $m2 $m3 $m4; do count=$((count+1)); [ "$c" = "200" ] && ok=$((ok+1)); done
done
echo "---"
echo "共验证 $count 个目标,$ok 个返回 200"
register: curl_http_result
changed_when: false
failed_when: false
- name: ">>> HTTP curl 矩阵HTTP-only"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ curl_http_result.stdout_lines }}"
- name: HTTPS curl 验证16 个目标:所有节点 × 4 域名,所有节点均为入口点)
ansible.builtin.shell: |
bases="{{ groups['k3s_nodes'] | map('extract', hostvars) | map(attribute='ansible_host') | join(' ') }}"
count=0
ok=0
echo "=== 16 个目标 (4 节点 × 4 域名) HTTPS ==="
echo "节点 M1(test01) M2(test02) M3(test03) M4(test04)"
for base in $bases; do
m1=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 5 https://test01.jackadam.top/ --resolve "test01.jackadam.top:443:$base" 2>/dev/null) || m1="fail"
m2=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 5 https://test02.jackadam.top/ --resolve "test02.jackadam.top:443:$base" 2>/dev/null) || m2="fail"
m3=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 5 https://test03.jackadam.top/ --resolve "test03.jackadam.top:443:$base" 2>/dev/null) || m3="fail"
m4=$(curl -sk -o /dev/null -w "%{http_code}" --connect-timeout 5 https://test04.jackadam.top/ --resolve "test04.jackadam.top:443:$base" 2>/dev/null) || m4="fail"
printf "%-12s %-14s %-14s %-14s %s\n" "$base" "$m1" "$m2" "$m3" "$m4"
for c in $m1 $m2 $m3 $m4; do count=$((count+1)); [ "$c" = "200" ] && ok=$((ok+1)); done
done
echo "---"
echo "共验证 $count 个目标,$ok 个返回 200"
register: curl_result
changed_when: false
failed_when: false
- name: ">>> HTTPS curl 矩阵"
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ curl_result.stdout_lines }}"
- name: Cleanup nginx matrix TLS (mode=cleanup)
when: (mode | default('deploy')) == 'cleanup'
block:
- name: Delete nginx matrix TLS + HTTP-only resources (deployments, ingress, ingressroute, configmaps)
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete deployment,svc -n default nginx-m1 nginx-m2 nginx-m3 nginx-m4 --ignore-not-found=true
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete ingress -n default nginx-m1 nginx-m3 nginx-m1-http nginx-m3-http --ignore-not-found=true
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete ingressroute -n default nginx-m2 nginx-m4 nginx-m2-http nginx-m4-http --ignore-not-found=true
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete configmap -n default nginx-m1-html nginx-m2-html nginx-m3-html nginx-m4-html --ignore-not-found=true
register: del_tls
changed_when: "'deleted' in del_tls.stdout"
- name: Remove copied nginx matrix TLS manifests directory
ansible.builtin.file:
path: /tmp/nginx-matrix-tls
state: absent

View File

@@ -0,0 +1,47 @@
---
# 一键应用 Node.js demo 清单(与 docs/04-0104-13 + ansible/files/nodejs-demo 对齐)
#
# 执行(在仓库根目录):
# ansible-playbook -i ansible/inventory.ini ansible/playbooks/nodejs-demo-apply.yml \
# -e nodejs_demo_manifest=04-01-nodejs-demo.yaml
#
# 默认清单04-01-nodejs-demo.yaml
- name: Apply nodejs-demo Kubernetes manifests
hosts: k3s_server
become: true
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
nodejs_demo_manifest: "04-01-nodejs-demo.yaml"
manifests_dir: "{{ playbook_dir }}/../files/nodejs-demo"
tasks:
- name: Ensure manifest file exists
ansible.builtin.stat:
path: "{{ manifests_dir }}/{{ nodejs_demo_manifest }}"
register: nodejs_manifest_stat
delegate_to: localhost
become: false
- name: Fail if manifest not found
ansible.builtin.fail:
msg: "未找到 {{ manifests_dir }}/{{ nodejs_demo_manifest }},请从仓库根检查文件名"
when: not nodejs_manifest_stat.stat.exists
delegate_to: localhost
become: false
- name: Copy manifest to control plane
ansible.builtin.copy:
src: "{{ manifests_dir }}/{{ nodejs_demo_manifest }}"
dest: "/tmp/{{ nodejs_demo_manifest }}"
mode: "0644"
- name: kubectl apply nodejs-demo manifest
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f /tmp/{{ nodejs_demo_manifest }}
register: nodejs_apply
changed_when: "'configured' in nodejs_apply.stdout or 'created' in nodejs_apply.stdout"
- name: Show kubectl apply output
ansible.builtin.debug:
var: nodejs_apply.stdout_lines