日常更新

This commit is contained in:
2026-03-29 09:08:01 +08:00
parent 31709425e2
commit befdefd222
224 changed files with 7240 additions and 3297 deletions

View File

@@ -0,0 +1,22 @@
# 可复用:在 kube-system 下确保 cloudflare-api-token Secretkey=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-secretverify_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

View File

@@ -0,0 +1,22 @@
# 可复用:在 kube-system 下确保 cloudflared-credentials Secretkey=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-secretverify_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

View 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

View 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_headerHost:、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默认 falsetrue 时对 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

View File

@@ -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

View File

@@ -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-readyverify_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

View 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

View File

@@ -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

View File

@@ -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

View 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"

View File

@@ -0,0 +1,85 @@
# 可复用TLS 握手 + SNIopenssl s_client输出 [OC-ASSERT]Epic 3.3 入口侧 TLS 证据)。
#
# 必填verify_tls_connect_host建议 IP 或解析到的入口地址、verify_tls_servernameSNI常同 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默认 falsetrue 时仅审计标注「不对链做强断言」,仍解析对端呈现证书)、
# 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

View File

@@ -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