日常更新
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# 可复用:在 kube-system 下确保 cloudflare-api-token Secret(key=api-token)。
|
||||
# 必填环境/变量:调用方须将 token 传入 verify_cf_api_token(非空则 apply;不要在日志中回显)。
|
||||
- name: Assert verify_cf_api_token for secret creation
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- verify_cf_api_token is defined
|
||||
- (verify_cf_api_token | trim | length) > 0
|
||||
fail_msg: "verify_common ensure-cloudflare-api-token-secret:verify_cf_api_token 为空"
|
||||
|
||||
- name: Apply cloudflare-api-token Secret in kube-system
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl -n kube-system create secret generic cloudflare-api-token \
|
||||
--from-literal=api-token="$CF_API_TOKEN" \
|
||||
--dry-run=client -o yaml \
|
||||
| KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl apply -f -
|
||||
environment:
|
||||
CF_API_TOKEN: "{{ verify_cf_api_token | trim }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
no_log: true
|
||||
@@ -0,0 +1,22 @@
|
||||
# 可复用:在 kube-system 下确保 cloudflared-credentials Secret(key=TUNNEL_TOKEN)。
|
||||
# 调用方传入 verify_tunnel_token(非空);no_log,勿在日志中回显 token。
|
||||
- name: Assert verify_tunnel_token for cloudflared secret
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- verify_tunnel_token is defined
|
||||
- (verify_tunnel_token | trim | length) > 0
|
||||
fail_msg: "verify_common ensure-cloudflared-tunnel-secret:verify_tunnel_token 为空"
|
||||
|
||||
- name: Apply cloudflared-credentials Secret in kube-system
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl -n kube-system create secret generic cloudflared-credentials \
|
||||
--from-literal=TUNNEL_TOKEN="$TUNNEL_TOKEN" \
|
||||
--dry-run=client -o yaml \
|
||||
| KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl apply -f -
|
||||
environment:
|
||||
TUNNEL_TOKEN: "{{ verify_tunnel_token | trim }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
no_log: true
|
||||
16
ansible/roles/verify_common/tasks/gate-debug-end-play.yml
Normal file
16
ansible/roles/verify_common/tasks/gate-debug-end-play.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
# 可复用:仅在进入门禁分支时输出一行 [GATE] 并 end_play。
|
||||
# 调用方在「本 task」上写 when:;条件为假时整段 include 被跳过,不会出现 debug/meta 两条 skipping。
|
||||
# 必填:verify_gate_message(字符串,须含 [GATE] 供 verify.sh 解析)
|
||||
- name: Assert verify_gate_message for gate-debug-end-play
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- verify_gate_message is defined
|
||||
- (verify_gate_message | string | trim | length) > 0
|
||||
fail_msg: "verify_common gate-debug-end-play:需设置 verify_gate_message"
|
||||
|
||||
- name: Emit gated message (verify_common)
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ verify_gate_message }}"
|
||||
|
||||
- name: End play after gate (verify_common)
|
||||
meta: end_play
|
||||
78
ansible/roles/verify_common/tasks/http-curl-expect.yml
Normal file
78
ansible/roles/verify_common/tasks/http-curl-expect.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
# 可复用:HTTP curl 重试 + 可选响应头精确匹配(OC 友好日志:[OC-ASSERT])。
|
||||
#
|
||||
# 必填之一:verify_http_url(整 URL)或 verify_http_entry_base(与 verify_http_path 拼接,path 默认 /)。
|
||||
# 可选:verify_http_host_header(Host:)、verify_http_response_header_name/_value(需同时设才校验)、
|
||||
# verify_http_expected_code(默认 200)、verify_http_retries(默认 10)、verify_http_retry_sleep(默认 2)、
|
||||
# verify_http_connect_timeout(默认 3)、verify_http_max_time(默认 8)、
|
||||
# verify_http_tls_insecure(默认 false;true 时对 curl 加 -k,用于自签/实验室 HTTPS)、
|
||||
# verify_http_assertion_label(默认 http_expect,用于稳定命名)。
|
||||
|
||||
- name: Resolve effective URL for http-curl-expect
|
||||
ansible.builtin.set_fact:
|
||||
_vhttp_url: >-
|
||||
{%- if verify_http_url is defined and verify_http_url | trim | length > 0 -%}
|
||||
{{- verify_http_url | trim -}}
|
||||
{%- elif verify_http_entry_base is defined and verify_http_entry_base | trim | length > 0 -%}
|
||||
{{- (verify_http_entry_base | trim | regex_replace('/+$', '')) ~ '/' ~ (verify_http_path | default('/') | trim | regex_replace('^/+', '')) -}}
|
||||
{%- else -%}
|
||||
|
||||
{%- endif -%}
|
||||
|
||||
- name: Assert http-curl-expect has a target URL
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _vhttp_url is defined
|
||||
- (_vhttp_url | default('') | trim | length) > 0
|
||||
fail_msg: "verify_common http-curl-expect:需设置 verify_http_url 或 verify_http_entry_base"
|
||||
|
||||
# 可选 verify_http_delegate:例如 localhost = 在控制端 curl(适合节点本机 curl 不通入口 IP 时)
|
||||
- name: HTTP curl retry with optional response header (verify_common)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
url={{ _vhttp_url | quote }}
|
||||
assertion={{ (verify_http_assertion_label | default('http_expect')) | quote }}
|
||||
retries={{ verify_http_retries | default(10) | int }}
|
||||
sleep_s={{ verify_http_retry_sleep | default(2) | int }}
|
||||
connect={{ verify_http_connect_timeout | default(3) | int }}
|
||||
maxt={{ verify_http_max_time | default(8) | int }}
|
||||
expect_code="{{ verify_http_expected_code | default(200) | string }}"
|
||||
host={{ (verify_http_host_header | default('') | trim) | quote }}
|
||||
hdr_name={{ (verify_http_response_header_name | default('') | trim) | quote }}
|
||||
hdr_val={{ (verify_http_response_header_value | default('') | trim) | quote }}
|
||||
{% if verify_http_tls_insecure | default(false) | bool %}
|
||||
tls_insecure=1
|
||||
{% else %}
|
||||
tls_insecure=0
|
||||
{% endif %}
|
||||
|
||||
ok=0
|
||||
i=1
|
||||
while [ "$i" -le "$retries" ]; do
|
||||
kflag=""
|
||||
if [ "$tls_insecure" = "1" ] && echo "$url" | grep -q '^https://'; then
|
||||
kflag="-k"
|
||||
fi
|
||||
if [ -n "$host" ]; then
|
||||
code=$(curl $kflag -s -o /dev/null -w "%{http_code}" -H "Host: ${host}" --connect-timeout "$connect" --max-time "$maxt" "$url" 2>/dev/null || echo "000")
|
||||
else
|
||||
code=$(curl $kflag -s -o /dev/null -w "%{http_code}" --connect-timeout "$connect" --max-time "$maxt" "$url" 2>/dev/null || echo "000")
|
||||
fi
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=http probe=status_code try=${i}/${retries} url=${url} host=${host:-<none>} http_code=${code}"
|
||||
if [ "$code" = "$expect_code" ]; then ok=1; break; fi
|
||||
sleep "$sleep_s"
|
||||
i=$((i+1))
|
||||
done
|
||||
test "$ok" = "1"
|
||||
|
||||
if [ -n "$hdr_name" ]; then
|
||||
if [ -n "$host" ]; then
|
||||
resp_hdr=$(curl $kflag -sS -D - -o /dev/null -H "Host: ${host}" --connect-timeout "$connect" --max-time "$maxt" "$url" 2>/dev/null | awk -v h="$hdr_name" -F': ' 'BEGIN{hl=tolower(h)} tolower($1)==hl {print $2; exit}' | tr -d '\r')
|
||||
else
|
||||
resp_hdr=$(curl $kflag -sS -D - -o /dev/null --connect-timeout "$connect" --max-time "$maxt" "$url" 2>/dev/null | awk -v h="$hdr_name" -F': ' 'BEGIN{hl=tolower(h)} tolower($1)==hl {print $2; exit}' | tr -d '\r')
|
||||
fi
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=http probe=response_header name=${hdr_name} value=${resp_hdr:-} expected=${hdr_val}"
|
||||
test "$resp_hdr" = "$hdr_val"
|
||||
fi
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
@@ -0,0 +1,52 @@
|
||||
# 在集群内起临时 Pod 做 HTTP 探针(不经宿主机 :80)。
|
||||
# 默认可选 traefik.kube-system + verify_traefik_path;若集群 Traefik ClusterIP 不可达,请在 playbook 设 verify_incluster_http_url 直链 Service(如 http://nginx-m1.default.svc.cluster.local/)。
|
||||
#
|
||||
# 必填:verify_traefik_kubeconfig、verify_traefik_assertion
|
||||
# 与 URL 二选一默认:verify_traefik_path(配合 Traefik)或 verify_incluster_http_url(直链 backend Service)
|
||||
# 可选:verify_traefik_header_name / verify_traefik_header_value(同时非空则校验响应头)
|
||||
|
||||
- name: Resolve in-cluster probe URL
|
||||
ansible.builtin.set_fact:
|
||||
_vf_url: "{{ verify_incluster_http_url | default('http://traefik.kube-system.svc.cluster.local' ~ (verify_traefik_path | default('/')), true) }}"
|
||||
|
||||
- name: Ephemeral pod name for in-cluster HTTP check
|
||||
ansible.builtin.set_fact:
|
||||
_vf_http_pod: "vf-http-{{ 1000000000 | random }}-{{ 100000 | random }}"
|
||||
|
||||
- name: Render in-cluster probe Pod manifest
|
||||
ansible.builtin.template:
|
||||
src: incluster-traefik-http-probe-pod.yml.j2
|
||||
dest: "/tmp/{{ _vf_http_pod }}-probe.yaml"
|
||||
mode: "0644"
|
||||
|
||||
- name: Apply probe Pod and wait for success
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
export KUBECONFIG={{ verify_traefik_kubeconfig }}
|
||||
POD={{ _vf_http_pod | quote }}
|
||||
f="/tmp/{{ _vf_http_pod }}-probe.yaml"
|
||||
kubectl delete pod -n default "$POD" --ignore-not-found --wait=false 2>/dev/null || true
|
||||
kubectl apply -f "$f"
|
||||
ok=0
|
||||
for i in $(seq 1 120); do
|
||||
phase=$(kubectl get pod -n default "$POD" -o jsonpath='{.status.phase}' 2>/dev/null || echo "")
|
||||
if [ "$phase" = "Succeeded" ]; then ok=1; break; fi
|
||||
if [ "$phase" = "Failed" ]; then
|
||||
echo "[ERR] probe pod Failed"
|
||||
kubectl describe pod -n default "$POD" | tail -50 || true
|
||||
kubectl logs -n default "$POD" 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$ok" != "1" ]; then
|
||||
echo "[ERR] probe pod timeout (expected Succeeded)"
|
||||
kubectl describe pod -n default "$POD" | tail -50 || true
|
||||
kubectl logs -n default "$POD" 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
kubectl delete pod -n default "$POD" --wait=false 2>/dev/null || true
|
||||
rm -f "$f"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
@@ -0,0 +1,23 @@
|
||||
# 可复用:断言 Endpoints 存在至少一个 address(与 nodejs-demo 等用例一致)。
|
||||
# 必填:verify_endpoints_service
|
||||
# 可选:verify_endpoints_namespace(默认 default)、verify_endpoints_assertion_label(日志)、k3s_kubeconfig
|
||||
- name: Assert verify_endpoints_service for kubectl-endpoints-ready
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- verify_endpoints_service is defined
|
||||
- (verify_endpoints_service | trim | length) > 0
|
||||
fail_msg: "verify_common kubectl-endpoints-ready:verify_endpoints_service 为空"
|
||||
|
||||
- name: Endpoints have addresses (verify_common)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
KUBECONFIG="{{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }}"
|
||||
svc="{{ verify_endpoints_service | trim }}"
|
||||
ns="{{ verify_endpoints_namespace | default('default') }}"
|
||||
label="{{ verify_endpoints_assertion_label | default('endpoints_ready') }}"
|
||||
eps=$(kubectl get endpoints "$svc" -n "$ns" -o jsonpath='{.subsets[0].addresses[0].ip}' 2>/dev/null || true)
|
||||
echo "[OC-ASSERT] assertion=${label} svc=${svc} namespace=${ns} endpoints.ip=${eps:-}"
|
||||
test -n "$eps"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
19
ansible/roles/verify_common/tasks/kubectl-rollout-status.yml
Normal file
19
ansible/roles/verify_common/tasks/kubectl-rollout-status.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
# 可复用:等待 Deployment/StatefulSet/DaemonSet rollout 完成。
|
||||
# 调用方需设置 verify_rollout_ref,例如 deployment/nginx-m1
|
||||
- name: Assert verify_rollout_ref is set
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- verify_rollout_ref is defined
|
||||
- (verify_rollout_ref | trim | length) > 0
|
||||
fail_msg: "verify_common kubectl-rollout-status:缺少变量 verify_rollout_ref(如 deployment/nginx-m1)"
|
||||
|
||||
- name: kubectl rollout status (verify_common)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} \
|
||||
kubectl rollout status {{ verify_rollout_ref }} \
|
||||
-n {{ verify_rollout_namespace | default('default') }} \
|
||||
--timeout={{ verify_rollout_timeout_s | default(180) }}s
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
@@ -0,0 +1,41 @@
|
||||
# 可复用:按顺序对多个 label selector 执行 kubectl wait pod --for=condition=ready。
|
||||
# 必填:verify_kubectl_wait_items(列表元素含 selector、timeout_s)
|
||||
# 可选:verify_kubectl_wait_namespace(默认 default)、k3s_kubeconfig
|
||||
- name: Assert verify_kubectl_wait_items for kubectl-wait-pods-ready
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- verify_kubectl_wait_items is defined
|
||||
- verify_kubectl_wait_items | length > 0
|
||||
fail_msg: "verify_common kubectl-wait-pods-ready:需设置非空 verify_kubectl_wait_items"
|
||||
|
||||
- name: kubectl wait pods ready (verify_common)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
KCFG="{{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }}"
|
||||
ns="{{ verify_kubectl_wait_namespace | default('default') }}"
|
||||
{% for item in verify_kubectl_wait_items %}
|
||||
echo "[OC-ASSERT] assertion=kubectl_wait_pod_ready selector={{ item.selector | quote }} namespace=${ns} timeout={{ item.timeout_s | int }}s"
|
||||
ok=0
|
||||
for attempt in 1 2 3 4 5 6 7 8; do
|
||||
set +e
|
||||
out=$(KUBECONFIG="$KCFG" kubectl wait --for=condition=ready pod \
|
||||
-l "{{ item.selector }}" -n "$ns" --timeout={{ item.timeout_s | int }}s 2>&1)
|
||||
rc=$?
|
||||
set -e
|
||||
if [ "$rc" -eq 0 ]; then ok=1; break; fi
|
||||
if echo "$out" | grep -qE 'NotFound|not found'; then
|
||||
echo "[OC-ASSERT] assertion=kubectl_wait_pod_ready selector={{ item.selector | quote }} retry=${attempt} reason=pod_churn_notfound"
|
||||
sleep 3
|
||||
continue
|
||||
fi
|
||||
echo "$out" >&2
|
||||
exit "$rc"
|
||||
done
|
||||
if [ "$ok" != "1" ]; then
|
||||
echo "[OC-ASSERT] assertion=kubectl_wait_pod_ready selector={{ item.selector | quote }} result=fail reason=exhausted_retries"
|
||||
exit 1
|
||||
fi
|
||||
{% endfor %}
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
@@ -0,0 +1,72 @@
|
||||
- name: Copy nodejs demo manifest
|
||||
ansible.builtin.copy:
|
||||
src: "{{ nodejs_manifest_src }}"
|
||||
dest: "{{ nodejs_manifest_dest }}"
|
||||
mode: "0644"
|
||||
|
||||
- name: Apply nodejs demo manifest
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ nodejs_manifest_dest }}
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
|
||||
- name: Rollout status nodejs-demo
|
||||
ansible.builtin.include_tasks: kubectl-rollout-status.yml
|
||||
vars:
|
||||
verify_rollout_ref: deployment/nodejs-demo
|
||||
verify_rollout_timeout_s: "{{ nodejs_rollout_timeout_s | default(180) | int }}"
|
||||
|
||||
- name: Assert Service targetPort matches expected (optional)
|
||||
when: nodejs_expected_target_port is defined and (nodejs_expected_target_port | int) > 0
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
exp="{{ nodejs_expected_target_port | int }}"
|
||||
got=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get svc nodejs-demo -n default -o jsonpath='{.spec.ports[0].targetPort}')
|
||||
echo "svc/nodejs-demo targetPort=$got expected=$exp"
|
||||
test "$got" = "$exp"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
|
||||
- name: Assert Endpoints exist
|
||||
ansible.builtin.include_tasks: kubectl-endpoints-ready.yml
|
||||
vars:
|
||||
verify_endpoints_service: nodejs-demo
|
||||
verify_endpoints_assertion_label: "{{ nodejs_endpoints_assertion_label | default('nodejs_demo_endpoints') }}"
|
||||
|
||||
- name: TLS SNI + certificate (optional, caller sets nodejs_tls_sni_*)
|
||||
when: nodejs_tls_sni_probe_enabled | default(false) | bool
|
||||
ansible.builtin.include_tasks: tls-openssl-sni.yml
|
||||
vars:
|
||||
verify_tls_connect_host: "{{ nodejs_tls_sni_connect_host }}"
|
||||
verify_tls_port: "{{ nodejs_tls_sni_port | default(443) | int }}"
|
||||
verify_tls_servername: "{{ nodejs_tls_sni_servername }}"
|
||||
verify_tls_assertion_label: "{{ nodejs_tls_sni_assertion_label }}"
|
||||
|
||||
- name: HTTP check nodejs demo (path/host optional)
|
||||
when: nodejs_http_check_enabled | default(true)
|
||||
ansible.builtin.include_tasks: http-curl-expect.yml
|
||||
vars:
|
||||
verify_http_entry_base: "{{ nodejs_verify_entry_base }}"
|
||||
verify_http_path: "{{ nodejs_verify_path | default('/node') }}"
|
||||
verify_http_host_header: "{{ nodejs_verify_host | default('') }}"
|
||||
verify_http_assertion_label: "{{ nodejs_http_assertion_label | default('nodejs_demo_entry_http') }}"
|
||||
verify_http_connect_timeout: "{{ nodejs_http_connect_timeout | default(3) | int }}"
|
||||
verify_http_max_time: "{{ nodejs_http_max_time | default(8) | int }}"
|
||||
verify_http_retries: "{{ nodejs_http_retries | default(10) | int }}"
|
||||
verify_http_retry_sleep: "{{ nodejs_http_retry_sleep | default(2) | int }}"
|
||||
verify_http_tls_insecure: "{{ nodejs_http_tls_insecure | default(false) | bool }}"
|
||||
|
||||
- name: Teardown when VERIFY_TEARDOWN=1
|
||||
when:
|
||||
- verify_teardown == "1"
|
||||
- not (nodejs_verify_skip_teardown | default(false) | bool)
|
||||
ansible.builtin.shell: |
|
||||
set -e
|
||||
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete -f {{ nodejs_manifest_dest }} --ignore-not-found=true
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: true
|
||||
|
||||
109
ansible/roles/verify_common/tasks/noop-doc-verify.yml
Normal file
109
ansible/roles/verify_common/tasks/noop-doc-verify.yml
Normal file
@@ -0,0 +1,109 @@
|
||||
- name: Assert docs file exists
|
||||
ansible.builtin.stat:
|
||||
path: "{{ repo_root }}/docs/{{ doc_filename }}"
|
||||
register: _doc_stat
|
||||
|
||||
- name: Fail when docs file missing
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _doc_stat.stat.exists
|
||||
fail_msg: "docs file missing: docs/{{ doc_filename }}"
|
||||
|
||||
- name: Find matching ansible/files doc_id directory
|
||||
ansible.builtin.find:
|
||||
paths: "{{ repo_root }}/ansible/files"
|
||||
file_type: directory
|
||||
patterns: "{{ doc_id }}"
|
||||
use_regex: false
|
||||
register: _files_dirs
|
||||
|
||||
- name: Fail when ansible/files doc_id directory missing
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _files_dirs.matched | int >= 1
|
||||
fail_msg: "ansible/files missing doc_id directory: ansible/files/{{ doc_id }}"
|
||||
|
||||
- name: Show noop verification summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "doc_id={{ doc_id }}"
|
||||
- "doc={{ doc_filename }}"
|
||||
- "files_dirs={{ _files_dirs.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: Verify cluster reachable (kubectl get nodes) [runbook baseline]
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl get nodes
|
||||
args:
|
||||
executable: /bin/bash
|
||||
delegate_to: "{{ groups['k3s_server'][0] }}"
|
||||
become: true
|
||||
run_once: true
|
||||
changed_when: false
|
||||
|
||||
- name: Verify core namespace exists (kube-system) [runbook baseline]
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl get ns kube-system
|
||||
args:
|
||||
executable: /bin/bash
|
||||
delegate_to: "{{ groups['k3s_server'][0] }}"
|
||||
become: true
|
||||
run_once: true
|
||||
changed_when: false
|
||||
|
||||
- name: Find YAML manifests under ansible/files doc_id dirs
|
||||
ansible.builtin.find:
|
||||
paths: "{{ _files_dirs.files | map(attribute='path') | list }}"
|
||||
file_type: file
|
||||
patterns:
|
||||
- "*.yml"
|
||||
- "*.yaml"
|
||||
recurse: true
|
||||
use_regex: false
|
||||
register: _files_manifests
|
||||
|
||||
- name: Show manifest count summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "doc_id={{ doc_id }}"
|
||||
- "manifest_files={{ _files_manifests.matched | default(0) }}"
|
||||
- "manifest_paths={{ (_files_manifests.files | map(attribute='path') | list)[:12] }}"
|
||||
|
||||
- name: Build kubernetes-manifest validation list (exclude example/non-k8s files)
|
||||
ansible.builtin.set_fact:
|
||||
_k8s_manifest_files: >-
|
||||
{{
|
||||
(_files_manifests.files | default([]))
|
||||
| rejectattr('path', 'search', '\\.example\\.')
|
||||
| rejectattr('path', 'search', 'docker-compose')
|
||||
| list
|
||||
}}
|
||||
|
||||
- name: Show filtered kubernetes manifest count
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "doc_id={{ doc_id }}"
|
||||
- "k8s_manifest_files={{ _k8s_manifest_files | length }}"
|
||||
- "k8s_manifest_paths={{ (_k8s_manifest_files | map(attribute='path') | list)[:12] }}"
|
||||
|
||||
- name: Server-side dry-run apply (kubectl apply --dry-run=server) [doc assertion]
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} \
|
||||
kubectl apply --dry-run=client -f -
|
||||
args:
|
||||
executable: /bin/bash
|
||||
stdin: "{{ lookup('ansible.builtin.file', item.path) }}"
|
||||
loop: "{{ _files_manifests.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
delegate_to: "{{ groups['k3s_server'][0] }}"
|
||||
become: true
|
||||
run_once: true
|
||||
changed_when: false
|
||||
when:
|
||||
- (_files_manifests.matched | default(0) | int) > 0
|
||||
- "'example.' not in item.path"
|
||||
- "'docker-compose' not in item.path"
|
||||
|
||||
85
ansible/roles/verify_common/tasks/tls-openssl-sni.yml
Normal file
85
ansible/roles/verify_common/tasks/tls-openssl-sni.yml
Normal file
@@ -0,0 +1,85 @@
|
||||
# 可复用:TLS 握手 + SNI,openssl s_client;输出 [OC-ASSERT](Epic 3.3 入口侧 TLS 证据)。
|
||||
#
|
||||
# 必填:verify_tls_connect_host(建议 IP 或解析到的入口地址)、verify_tls_servername(SNI,常同 HTTP Host)
|
||||
# 可选:verify_tls_port(默认 443)、verify_tls_timeout_sec(默认 10)、
|
||||
# verify_tls_expect_subject_substring(若设置,则 x509 subject 须包含该子串,grep -F)、
|
||||
# verify_tls_expect_san_substring(若设置,则 SAN 扩展文本须包含该子串)、
|
||||
# verify_tls_cafile(若设置,s_client 增加 -CAfile;路径须在执行主机存在)、
|
||||
# verify_tls_insecure_skip_verify(默认 false;true 时仅审计标注「不对链做强断言」,仍解析对端呈现证书)、
|
||||
# verify_tls_assertion_label(默认 tls_sni_handshake)
|
||||
|
||||
- name: Assert tls-openssl-sni required vars
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- verify_tls_connect_host is defined
|
||||
- (verify_tls_connect_host | trim | length) > 0
|
||||
- verify_tls_servername is defined
|
||||
- (verify_tls_servername | trim | length) > 0
|
||||
fail_msg: "verify_common tls-openssl-sni:需设置 verify_tls_connect_host 与 verify_tls_servername"
|
||||
|
||||
- name: TLS handshake + certificate subject (openssl s_client)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
assertion={{ (verify_tls_assertion_label | default('tls_sni_handshake')) | quote }}
|
||||
host={{ verify_tls_connect_host | trim | quote }}
|
||||
port={{ verify_tls_port | default(443) | int }}
|
||||
sni={{ verify_tls_servername | trim | quote }}
|
||||
timeout={{ verify_tls_timeout_sec | default(10) | int }}
|
||||
expect={{ (verify_tls_expect_subject_substring | default('') | trim) | quote }}
|
||||
expect_san={{ (verify_tls_expect_san_substring | default('') | trim) | quote }}
|
||||
cafile={{ (verify_tls_cafile | default('') | trim) | quote }}
|
||||
insecure={{ ('1' if (verify_tls_insecure_skip_verify | default(false) | bool) else '0') | quote }}
|
||||
|
||||
extra_ca=""
|
||||
if [ -n "$cafile" ]; then
|
||||
if [ ! -f "$cafile" ]; then
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=cafile result=fail reason=missing_file path=${cafile}"
|
||||
exit 1
|
||||
fi
|
||||
extra_ca="-CAfile ${cafile}"
|
||||
fi
|
||||
|
||||
if [ "$insecure" = "1" ]; then
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=insecure_skip_verify value=1 note=peer_cert_only_lab_or_troubleshoot"
|
||||
fi
|
||||
|
||||
raw=$(echo | timeout "$timeout" openssl s_client -connect "${host}:${port}" -servername "$sni" ${extra_ca} </dev/null 2>&1 || true)
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=s_client_log excerpt"
|
||||
echo "$raw" | tail -25
|
||||
|
||||
subj=$(echo "$raw" | openssl x509 -noout -subject 2>/dev/null || true)
|
||||
if [ -z "$subj" ]; then
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=subject result=fail reason=no_certificate_or_handshake"
|
||||
exit 1
|
||||
fi
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=subject value=${subj}"
|
||||
|
||||
dates=$(echo "$raw" | openssl x509 -noout -dates 2>/dev/null || true)
|
||||
not_after=$(echo "$dates" | grep '^notAfter=' | cut -d= -f2- | tr -d '\r' || true)
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=cert_not_after value=${not_after:-unknown}"
|
||||
|
||||
san=$(echo "$raw" | openssl x509 -noout -ext subjectAltName 2>/dev/null || true)
|
||||
if [ -z "$(echo "$san" | tr -d '[:space:]')" ]; then
|
||||
san=$(echo "$raw" | openssl x509 -noout -text 2>/dev/null | awk '/X509v3 Subject Alternative Name:/{getline; print; exit}' || true)
|
||||
fi
|
||||
san_log=$(echo "$san" | tr '\n\r\t' ' ' | cut -c1-240)
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=san excerpt=${san_log:-<none>}"
|
||||
|
||||
if [ -n "$expect" ]; then
|
||||
echo "$subj" | grep -Fq -- "$expect" || {
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=subject_match result=fail expected_substring=${expect}"
|
||||
exit 1
|
||||
}
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=subject_match result=ok"
|
||||
fi
|
||||
|
||||
if [ -n "$expect_san" ]; then
|
||||
echo "$san" | grep -Fq -- "$expect_san" || {
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=san_match result=fail expected_substring=${expect_san}"
|
||||
exit 1
|
||||
}
|
||||
echo "[OC-ASSERT] assertion=${assertion} phase=tls probe=san_match result=ok"
|
||||
fi
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
@@ -0,0 +1,38 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: {{ _vf_http_pod }}
|
||||
namespace: default
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
# 与 02-0x M1/M2 类似:探针跑在控制面,避免部分环境下工作节点到 traefik ClusterIP 异常
|
||||
nodeSelector:
|
||||
node-role.kubernetes.io/control-plane: ""
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: probe
|
||||
image: curlimages/curl:8.5.0
|
||||
env:
|
||||
- name: URL
|
||||
value: {{ _vf_url | to_json }}
|
||||
- name: ASSERT
|
||||
value: {{ verify_traefik_assertion | to_json }}
|
||||
- name: HDRN
|
||||
value: {{ verify_traefik_header_name | default('') | trim | to_json }}
|
||||
- name: HDRV
|
||||
value: {{ verify_traefik_header_value | default('') | trim | to_json }}
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- |
|
||||
set -euo pipefail
|
||||
# -4:集群仅 IPv4 时避免优先 AAAA 导致「连不上 ClusterIP」
|
||||
code=$(curl -4 -sS -o /dev/null -w "%{http_code}" --connect-timeout 25 --max-time 60 "$URL")
|
||||
echo "[OC-ASSERT] assertion=${ASSERT} phase=http probe=in_cluster status_code=${code}"
|
||||
test "$code" = "200"
|
||||
if [ -n "$HDRN" ] && [ -n "$HDRV" ]; then
|
||||
curl -4 -sS -D - -o /dev/null --connect-timeout 25 --max-time 60 "$URL" | tr -d "\r" | grep -qi "^${HDRN}: *${HDRV}$" || exit 1
|
||||
echo "[OC-ASSERT] assertion=${ASSERT} phase=http probe=response_header name=${HDRN} value=${HDRV}"
|
||||
fi
|
||||
Reference in New Issue
Block a user