chore(gitops): sync workspace state

This commit is contained in:
Haitao Pan 2026-04-02 16:32:04 +08:00
parent 5224b82b7d
commit e4815a8c97
75 changed files with 100 additions and 1392 deletions

106
README.md
View File

@ -1,100 +1,18 @@
# ansible-playbook # Cloud-Neutral Toolkit GitOps
This repository contains a collection of Ansible playbooks and roles for various infrastructure setups and service management tasks. This repository is the GitOps declaration layer for the Cloud-Neutral Toolkit.
For a quick overview of the directory layout see [docs/repo-structure.md](docs/repo-structure.md). ## Scope
Additional documentation is stored under the `docs/` folder.
## Playbook 角色说明 - Store declarative Kubernetes resources, Flux Kustomizations, and non-sensitive multi-environment values.
- Keep application charts and Helm templates in the dedicated chart repository.
- Keep imperative automation such as Ansible playbooks and inventories out of this repository.
1. playbooks/roles/docker适用于简单的、单机环境的部署主要使用 Docker 和 Docker Compose 进行容器化管理。 ## Layout
2. playbooks/roles/charts面向大规模的 Kubernetes 集群,使用 Helm 和标准化 Chart 部署模式进行高可用和可扩展的管理。
3. playbooks/roles/vhosts传统的非容器化部署方式通常涉及手动配置服务器和虚拟主机适用于不使用容器的应用场景。
- `infra/`: platform, infrastructure, and shared service declarations
- `apps/`: application release declarations
- `clusters/`: cluster-level overlays and entrypoints
- `docs/`: repository conventions and operational documentation
## Role Summary For a quick structure overview, see [docs/repo-structure.md](docs/repo-structure.md).
| Role Name | Description | Docker | Charts | VHosts | CICD | Validate | Last Update |
|-------------------------|-------------------------------------------------------|--------|--------|--------|--------|--------|------------|
| `common` | 通用角色,包含一些常用的功能,如日志记录、监控等。 | | | ✔ | | yes | 2025-02-14 |
| `keycloak` | 用于管理身份认证和授权服务。 | ✔ | | | github | yes | 2024-11-10 |
| `harbor` | 容器镜像仓库角色,用于存储和管理容器镜像。 | ✔ | | | github | yes | 2024-11-14 |
| `app` | 参考模板。 | | | | | | |
| `nginx` | 用于设置 Nginx | | ✔ | ✔ | | | |
| `grafana` | 用于设置 Grafana | | ✔ | ✔ | | | |
| `grafana-loki` | 用于设置 Grafana-loki | | ✔ | ✔ | | | |
| `Grafana-tempo` | 用于设置 Grafana-tempo | | ✔ | ✔ | | | |
| `prometheus` | 用于设置 Prometheus | | ✔ | ✔ | | | |
| `prometheus-transfer` | 用于 Prometheus 数据传输设置。 | | | ✔ | | | |
| `vector` | 用于配置日志收集代理。 | | | ✔ | | | |
| `node-exporter` | 用于导出系统和硬件的监控数据。 | | ✔ | | | | |
| `observability-agent` | 用于管理 Observability 代理。 | | ✔ | ✔ | | | |
| `observability-server` | 用于设置 Observability 服务端。 | | ✔ | ✔ | | | |
| `wireguard-client` | 用于设置 WireGuard 客户端。 | | | ✔ | | | |
| `wireguard-gateway` | 用于设置 WireGuard 网关。 | | | ✔ | | | |
| `vault` | 用于管理敏感数据和密钥。 | | | ✔ | | | |
| `postgresql` | PostgreSQL 数据库角色,用于提供 PostgreSQL 数据库服务。 | | ✔ | | | | |
| `redis` | Redis 数据库角色,用于提供 Redis 数据库服务。 | | ✔ | | | | |
| `chartmuseum` | 图表仓库角色,用于存储和管理 Kubernetes 图表。 | | ✔ | | | | |
| `gitlab` | 代码仓库角色,用于存储和管理代码。 | | ✔ | | | | |
| `mysql` | MySQL 数据库角色,用于提供 MySQL 数据库服务。 | | ✔ | | | | |
| `argo-server` | 用于设置和管理 Argo Server。 | | ✔ | | | | |
| `deepflow` | 用于流量监控与网络性能分析的 DeepFlow 服务。 | | ✔ | | | | |
| `jenkins` | Jenkins 自动化构建工具角色,用于 CI/CD 管道。 | | ✔ | | | | |
| `chaos-mesh` | 用于 Chaos Engineering 测试的 Chaos Mesh 角色。 | | ✔ | | | | |
| `flagger-loadtester` | 用于负载测试的 Flagger Loadtester 角色。 | | ✔ | | | | |
| `splunk-otel-collector` | 用于配置 Splunk OpenTelemetry Collector。 | | ✔ | | | | |
| `openldap` | 用于设置和管理 OpenLDAP 身份认证服务。 | | ✔ | | | | |
| `alerting` | 用于设置和管理警报系统。 | | | ✔ | | | |
| `k3s` | 用于创建 Kubernetes 集群。 | | | ✔ | | | |
| `k3s-reset` | 用于重置 Kubernetes 集群。 | | | ✔ | | | |
| `k3s-addon` | 用于安装 Kubernetes 集群插件。 | | | ✔ | | | |
| `secret-manger` | 密钥管理角色,用于管理密钥。 | | | ✔ | | | |
| `cert-manager` | 证书管理角色,用于管理证书。 | | | ✔ | | | |
| `ssh-trust` | 配置 ops 主机与节点的 SSH 互信。 | | | ✔ | | | |
表格说明
- Docker是否属于 Docker 角色。
- Charts是否属于 Helm Chart 角色。
- VHosts是否属于虚拟主机管理相关角色。
- CICD是否启用 CICD 管道,标明是否集成了自动化流程。
- Validate是否经过验证测试。
- Last Update最后更新时间。
## Usage Examples
- Linux OS Setup
ansible-playbook -i inventory/hosts/all playbooks/common -D -C
ansible-playbook -i inventory/hosts/all playbooks/common -D
- Gather Network Information
ansible-playbook -i inventory gather_network_info.yml -e target_group=master
- Display network information on all nodes
ansible -i inventory all -m script -a 'roles/network_info/tasks/files/display_network_info.sh'
- Deploy Keycloak Server
ansible-playbook -i inventory/hosts/core playbooks/keycloak_server -D
- Set up WireGuard Gateway
ansible-playbook -i inventory/hosts/vpn playbooks/wireguard_gateway.yaml -D
- Set up Grafana Alloy
ansible-playbook -i inventory/k3s-cluster playbooks/init_grafana_alloy -D -C -l cn-k3s-server.svc.plus -e @playbooks/roles/alloy/files/loki_journal_sources_k3s_server.yml -e "ansible_become_pass='xxxx'"
- Setup VPN gateway
ansible-playbook -i inventory/hosts/all playbooks/common -l gateway -D
## Documentation
- [docs/gpu-k8s-role.md](docs/gpu-k8s-role.md) - How to run the GPU-enabled Kubernetes role.
- [docs/repo-structure.md](docs/repo-structure.md) - Overview of repository layout.

View File

@ -1,19 +0,0 @@
只加密 private_key 字段
1. 原始 vpn-keys.yaml
yaml
keys:
- name: master-1
private_key: <master_private_key>
public_key: <master_public_key>
2. 使用 ansible-vault encrypt_string 加密 private_key
- ansible-vault encrypt_string 'private-key-xxxx' --name 'private_key'
- ansible-vault encrypt_string 'public_key-xxxx' --name 'public_key'
示例输出(加密后是 YAML 结构):
yaml
private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
62326432376162336462343864333933356363373235623262306463326432363737623732613763
3962613662616565393463343030653733623066626137610a313465323462623261303031323337

View File

@ -1,145 +0,0 @@
keys:
- name: cn-hub
private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
33643635306332303761356562383035353333373234393132313162613834323963313635326562
3932356235303234356561623762393862666438386565310a376235306238343139386532336162
65623164666665353435653432396530303634666438656566656466643866366139613961363631
6363306631393038320a613163313338313237383837303966356333303737643331616433396430
33316331333766613438356462313130326433363961316162313761616561616466363939613033
3837623938376434656434386135333739613939653133373733
public_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
38336537383061383333643431643261343739323864316235303366623930633366336139386636
6162336232336533636134353863386233303631626363360a376533336664636661373933623230
34333765346661383335663034393561646436333135613838373438396336633061396533613061
3031326364353036630a373862396266653961346334663139626633313362656131663163383563
34376231306239636536313830333962323934343035333263643234363363396164626366353061
3833613132373666303563623863373735396566666239316536
- name: global-hub
private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
63343838666530633031313536616535313936373634396165376132333661616534663937626632
3530646463663462383130323930356239636438643035380a343433303064383531663332303839
32613733323263623836346266383363336361323036383536313031386435386534646661616463
6631346431316334620a643831313033326261333365623037306565663131373664343930623665
31346564363635323765336465646466663631376538626237386165326464326632323438663038
3937363832363731353834633663646538666232336239353936
public_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
34666430316566393939656436323231623935316331373264383830653934323261656136373666
3630356330396362323763383832376538633163636331650a376339326661363431353532303831
37336134303235633334643036326564313163626433613261333062336238316333363165386263
3666386330343261340a333662636630356635373938623335656462633039353565383133613935
35643661363334313733346430633432353736343463613264393433623135613833376435333661
3462643164356563346166656237613334616130386532393565
- name: deepflow-demo
private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
36316136663466336564383766626434626338356130626537663163373530326332366335306136
3266383533373032623366396139653063626338646237310a353439346238653832646437313663
62623239623761326436613833313739386662356263353338666461363438613766663962386162
3539343836623936370a313439316335346235306633333333643738333461323963313038313161
62376566626335306335623134346361326364346433626234383162616636326265356364313938
6534613330643764613733333266313365633635663138636633
public_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
32326461646138373831356335656664643737313032656134663138323439313164353766363134
6232303034663064303235303363663661326433313536660a666133616438316436306463303163
64646530633639616266396563383362306235313662373565323963633039653931376431303565
6136396164346563660a643235646232353061323463396539383266333133343532396139373035
39653262653638363930383861353262303030373332313538383362393633663562303566373737
3062336434313031613534393033616330333363613863613464
- name: icp-aliyun
private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
34383966663239613361363535616332303432393165643433663461633934363535626137326664
6532646433306636393734666164613864636636626630660a636636306435343661366234343661
30326362306537633561636265666232373437353034643462656538653835653831303263306662
3361323333353935350a316539303863646434336136333862626261363031336232666562326434
39303961383563623736383962363330363439313064613632383061313438373330356366323534
6533613662373736373131363463663734656261643839383862
public_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
65393861336537646335613534376635343838656233646333386438653766636539333436623665
6562396637666365613562373565383263353534343931350a323563346239666534303162353432
63646562363362396333333738333664376136303066316135633633323466326233613264623366
6166613531623135660a363465636137643337626137386661306237323731353839303734653436
32643065663739303161626261393062613764346662633365336162613134633131383062646133
6437313463376164386465663365386436633466363633383366
- name: tky-proxy
private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
39303737303631303963613131373734373338663232366534303832646664326365353730313665
3664643734336466613839663239613433373837633064300a373634343034323739646565326464
32343237303731656666323332656138643533323338626631626630316435623564616330333237
6339626537376163360a376663653533663332353163303363386564373233666230323735343863
66363730653134343037363739353464663834373134656639303932646635336664303537376665
3961393930616464343632363039333465633364626433363761
public_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
61366364303934343039356565643939613032373932356264393739343832366231653335373132
3732346666336566396133343836393961336533323530310a636131316266653132346663306461
39613036396330376235623765313166303163393264373436316236366234666532343866383235
3230366539313162310a323130663530653339623366613336616433666136336463306237326461
36363536376230313135336463386566393964613238353134663432353762626166303938323266
3963383862363236643361346165373538323332363764633131
- name: us-proxy
private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
34373039646561366365363831636438633462633536343834356263396331333864396161363630
6631373964666239663064633936333135653663306464320a316463363362313336373437383937
38663665323531346536363030333637663631623765373466386664623332616432613334623933
6362353736396662300a343430633865363637313732383065613836363231623862616535383033
38333861393761633437316435306263356131353133376532323661366465616130616332366436
3430663134636430613139333238343265613764616234383362
public_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
62316266633037313333333966646331613830633733616438666533303735613763376632336562
3864333538333535323862333230663664306561386534340a343038356565643530323061323034
35353663643465616633346363626430623435396263646339373137303830303031326462653966
3266313038373466300a643833373063363862643533393838613266666363326363383034653366
34633063616361653762323130363832353132613531326131323336353339616166396464303337
6338353132333964376163333537363337316438313266623933
- name: ca-proxy
private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
39303965663333646238656661376238653732363366653264353234396635313464316563613761
3937323936393363373265653864313034343462626633360a303036643838366465623965623365
35646332626232356661343966623637613037666336376562323864306630396536646230623664
6431636530326362320a383965356336313563336261633030666534613936653037393737356637
30323935393662333533373561303661366437626264383837376562323466323531616165643233
3233643237303764346130323139613537666132646532643864
public_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
61343962366534343435356236663132656636313634393563663164323630646363666264626434
3439316233626333656362623332613433313130396430610a633839393561326438636533666162
63663330313934353462663334643365323766376337363835633439653064386237373531323637
6338333364366239350a313636636438653736336563383665366661343066373761333431343933
36303062643639613632383565383534306438363461336634343662646435666231343565616333
6239326436633462346466393862336332383665313134393738
- name: icp-huawei
private_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
34356563313165386632656365393865356631663936656337316136343437363538393463363639
3562343736663335643230626335346265336365613835370a373361633064356264623932393232
63386433643761373634333232393136316333353165336463323736366363313662333863656462
3136323033626666340a623730346234396664343863656335303263376562613230373363343938
36633838303966303434336165393838346531383362316161366431393765373765396137316466
3866643163393061613732623938613035396536333837353363
public_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
63383631656563313335646566356237333737653232656439336230633037346566626663653333
6533663536666464616537376236383734313231393762640a643962666334326261386462653233
39386632343965346161623761393034313532633236613430663261366530363638653430383864
3535323031663634320a366134323832323034373430383264353066333666323932663230336333
65643263363538653033326236623434366631366339313964646263316536643237643535313663
3062623634613961636532636438393830613132656266306539

View File

@ -21,13 +21,12 @@ This `docs/` directory now has a bilingual canonical layer for the current repos
## Current Repo Context / 当前仓库背景 ## Current Repo Context / 当前仓库背景
- Root README: `ansible-playbook` - Root README: `Cloud-Neutral Toolkit GitOps`
- Previous docs index: `Documentation` - Previous docs index: `Documentation`
- Manifest evidence / 构建清单: repository structure and scripts only - Manifest evidence / 构建清单: repository structure and scripts only
- Active code and ops directories / 当前主要目录: `scripts/`, `StackFlow/`, `config/` - Active code and ops directories / 当前主要目录: `infra/`, `apps/`, `clusters/`, `config/`, `scripts/`
## Existing Docs To Reconcile / 需要继续归并的现有文档 ## Existing Docs To Reconcile / 需要继续归并的现有文档
- `gpu-k8s-role.md`
- `repo-structure.md` - `repo-structure.md`
- `stackflow/README.md` - `stackflow/README.md`

View File

@ -1,12 +1,12 @@
# GitOps Repository Documentation # GitOps Repository Documentation
This repository organizes GitOps assets, playbooks, and operational roles for infrastructure delivery. This repository organizes declarative GitOps assets for infrastructure delivery.
## Current state snapshot ## Current state snapshot
- Root README title: `ansible-playbook` - Root README title: `Cloud-Neutral Toolkit GitOps`
- Build/runtime evidence: repository structure and scripts only - Build/runtime evidence: repository structure and scripts only
- Primary directories detected: `scripts/`, `StackFlow/`, `config/` - Primary directories detected: `infra/`, `apps/`, `clusters/`, `config/`, `scripts/`
- Existing docs count: 3 - Existing docs count: 3
## Canonical pages ## Canonical pages
@ -20,6 +20,5 @@ This repository organizes GitOps assets, playbooks, and operational roles for in
## Legacy docs to fold in ## Legacy docs to fold in
- `gpu-k8s-role.md`
- `repo-structure.md` - `repo-structure.md`
- `stackflow/README.md` - `stackflow/README.md`

View File

@ -1,6 +1,6 @@
# Architecture # Architecture
This repository organizes GitOps assets, playbooks, and operational roles for infrastructure delivery. This repository organizes declarative GitOps assets for infrastructure delivery.
Use this page as the canonical bilingual overview of system boundaries, major components, and repo ownership. Use this page as the canonical bilingual overview of system boundaries, major components, and repo ownership.

View File

@ -1,6 +1,6 @@
# Deployment # Deployment
This repository organizes GitOps assets, playbooks, and operational roles for infrastructure delivery. This repository organizes declarative GitOps assets for infrastructure delivery.
Use this page to standardize deployment prerequisites, supported topologies, operational checks, and rollback notes. Use this page to standardize deployment prerequisites, supported topologies, operational checks, and rollback notes.

View File

@ -1,6 +1,6 @@
# Design # Design
This repository organizes GitOps assets, playbooks, and operational roles for infrastructure delivery. This repository organizes declarative GitOps assets for infrastructure delivery.
Use this page to consolidate design decisions, ADR-style tradeoffs, and roadmap-sensitive implementation notes. Use this page to consolidate design decisions, ADR-style tradeoffs, and roadmap-sensitive implementation notes.

View File

@ -1,6 +1,6 @@
# Developer Guide # Developer Guide
This repository organizes GitOps assets, playbooks, and operational roles for infrastructure delivery. This repository organizes declarative GitOps assets for infrastructure delivery.
Use this page to document local setup, project structure, test surfaces, and contribution conventions tied to the current codebase. Use this page to document local setup, project structure, test surfaces, and contribution conventions tied to the current codebase.

View File

@ -1,6 +1,6 @@
# User Guide # User Guide
This repository organizes GitOps assets, playbooks, and operational roles for infrastructure delivery. This repository organizes declarative GitOps assets for infrastructure delivery.
Use this page to document primary user/operator tasks, everyday workflows, and navigation to existing how-to material. Use this page to document primary user/operator tasks, everyday workflows, and navigation to existing how-to material.

View File

@ -1,6 +1,6 @@
# Vibe Coding Reference # Vibe Coding Reference
This repository organizes GitOps assets, playbooks, and operational roles for infrastructure delivery. This repository organizes declarative GitOps assets for infrastructure delivery.
Use this page to align AI-assisted coding prompts, repo boundaries, safe edit rules, and documentation update expectations. Use this page to align AI-assisted coding prompts, repo boundaries, safe edit rules, and documentation update expectations.

View File

@ -1,113 +0,0 @@
# GPU Kubernetes Role
This document describes how to use the `gpu-k8s` role to deploy a simple Kubernetes cluster with NVIDIA GPU support.
## Overview
The role performs four main tasks:
1. **Create the Kubernetes cluster** using [sealos](https://github.com/labring/sealos). It runs the provided `sealos run` command to bootstrap the master and worker nodes.
2. **Install NVIDIA drivers and the NVIDIA container toolkit** on the target hosts so that Kubernetes can access GPU resources.
3. **Verify the cluster state** after initialization, displaying the `sealos` version and the current Kubernetes nodes.
4. **Verify GPU access** by deploying the official NVIDIA device plugin and running a small CUDA workload.
When `sealos_version` is set to `latest` (the default), the role automatically
fetches the most recent stable release from GitHub. The Kubernetes image tag is
controlled separately via `kubernetes_version`, which defaults to `v1.25.16` but
can be overridden to any compatible release.
The following command is used to create the cluster (example with one master and one worker):
```bash
REGISTRY=$(playbooks/roles/vhosts/gpu-k8s/files/get_labring_registry.sh)
sealos run \
${REGISTRY}/kubernetes:<kubernetes_version> \
${REGISTRY}/cilium:<cilium_version> \
${REGISTRY}/helm:<helm_version> \
--masters 172.16.11.120 \
--nodes 172.16.11.152 \
--env '{}' \
--cmd "kubeadm init --skip-phases=addon/kube-proxy"
```
If deploying with a non-root user the command also requires `--user` and
`--pk` options pointing to the user's SSH key. The host running Sealos must have
`newuidmap` and `newgidmap` installed (typically provided by the `uidmap`
package) along with the `fuse-overlayfs` binary to enable user namespaces.
After the cluster is running the role installs the NVIDIA device plugin and runs a test pod to ensure `nvidia-smi` works inside the cluster.
## Usage
Add the role to your playbook along with the `ssh-trust` role which configures passwordless access from the ops host to the cluster nodes:
```yaml
- hosts: all
roles:
- ssh-trust
- gpu-k8s
```
By default the SSH key is created for the same user Ansible connects with. You
can override this by setting `ssh_user`. When `ansible_user` is defined it will
be used automatically, otherwise `root` is assumed. The role also allows you to
specify the private key path via `ssh_private_key`:
```yaml
- hosts: all
vars:
ssh_user: ubuntu
ssh_private_key: /home/ubuntu/.ssh/myuser_id_rsa
roles:
- ssh-trust
- gpu-k8s
```
The specified user must be able to log in without a password and have sudo
access on the target hosts.
Example playbook snippet defining the IP lists:
```yaml
- hosts: all
vars:
master_ips:
- "172.16.11.120"
node_ips:
- "172.16.11.152"
roles:
- ssh-trust
- gpu-k8s
```
You can also specify hostnames and let the role look up the IPs:
```yaml
- hosts: all
vars:
masters:
- "k8s-1"
nodes:
- "k8s-2"
- "k8s-3"
roles:
- ssh-trust
- gpu-k8s
```
The playbook expects at least one master and one node. You can provide the
addresses directly via `master_ips` and `node_ips`, or give hostnames in the
`masters` and `nodes` variables. When hostnames are used, the role will look up
their `ansible_host` values from the inventory to obtain the IPs. Up to three
masters can be specified.
Run the playbook with your inventory that contains the master and node IP addresses.
```bash
ansible-playbook -i inventory/hosts/all playbooks/demo_gpu_k8s.yml
```
The final step prints the output of `nvidia-smi` from inside a Kubernetes pod, confirming that the GPU is available.

View File

@ -1,19 +1,12 @@
# Repository Structure # Repository Structure
This repository combines Ansible playbooks with Kubernetes manifests and This repository contains declarative GitOps assets only. Below is a short overview of the key directories.
automation scripts. Below is a short overview of the key directories.
| Directory | Purpose | | Directory | Purpose |
|-----------|---------| |-----------|---------|
| `playbooks` | Ansible playbooks and role definitions. |
| `apps` | Flux HelmRelease and Kustomize files for applications. | | `apps` | Flux HelmRelease and Kustomize files for applications. |
| `clusters` | Kustomize overlays for different clusters referencing the `apps` definitions. | | `clusters` | Kustomize overlays for different clusters referencing the `apps` definitions. |
| `helmfiles` | Sample [helmfile](https://github.com/helmfile/helmfile) declarations. | | `infra` | Platform and infrastructure declarations managed by Flux. |
| `helm` | Local Helm charts used in some playbooks. | | `scripts` | Utility scripts that support validation or operational workflows. |
| `inventory` | Example inventories and group variables for Ansible. | | `config` | Non-sensitive configuration references and examples. |
| `scripts` | Utility scripts such as cluster setup or secret management. |
| `sync` | Tasks for local host setup and testing. |
| `docs` | Additional documentation. | | `docs` | Additional documentation. |
See `docs/gpu-k8s-role.md` for an example walkthrough deploying a GPU-enabled
Kubernetes cluster.

View File

@ -1,10 +1,9 @@
# StackFlow (GitOps YAML Flow) # StackFlow (GitOps YAML Flow)
StackFlow is a declarative YAML describing a full business stack deployment StackFlow is a declarative YAML describing a full business stack deployment
across DNS, cloud resources (IAC), and Ansible-based provisioning. across DNS, cloud resources (IAC), and GitOps-driven delivery.
This repository already contains: This repository already contains:
- `playbooks/` (Ansible provisioning for vhosts/docker/k3s)
- `iac-template/` (Terraform reference templates) - `iac-template/` (Terraform reference templates)
- `.github/workflows/` (bootstrap workflows) - `.github/workflows/` (bootstrap workflows)
@ -27,7 +26,7 @@ Top-level:
- `kind`: `StackFlow` - `kind`: `StackFlow`
- `metadata.name`: stack id - `metadata.name`: stack id
- `global.domain`: root domain, e.g. `svc.plus` - `global.domain`: root domain, e.g. `svc.plus`
- `global.dns_provider`: `cloudflare` (planned), `alicloud` (existing playbooks) - `global.dns_provider`: `cloudflare` (planned), `alicloud` (legacy)
- `global.cloud`: `gcp` - `global.cloud`: `gcp`
- `targets[]`: list of deployable targets - `targets[]`: list of deployable targets
@ -52,7 +51,7 @@ Planned phases:
- `dns-plan`: output required DNS records (no apply) - `dns-plan`: output required DNS records (no apply)
- `dns-apply`: apply DNS changes (provider-specific) - `dns-apply`: apply DNS changes (provider-specific)
- `iac-apply`: provision resources via Terraform - `iac-apply`: provision resources via Terraform
- `deploy`: deploy apps via Ansible or repo-dispatch - `deploy`: deploy apps via GitOps or repo-dispatch
- `observe`: connect monitoring / alerts - `observe`: connect monitoring / alerts
Today we only ship `validate` + `dns-plan` as the first step. Today we only ship `validate` + `dns-plan` as the first step.

View File

@ -1,12 +1,12 @@
# GitOps 运维仓库 文档 # GitOps 运维仓库 文档
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色 该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付
## 当前状态快照 ## 当前状态快照
- 根 README 标题: `ansible-playbook` - 根 README 标题: `Cloud-Neutral Toolkit GitOps`
- 构建与运行时证据: repository structure and scripts only - 构建与运行时证据: repository structure and scripts only
- 自动识别的主要目录: `scripts/`, `StackFlow/`, `config/` - 自动识别的主要目录: `infra/`, `apps/`, `clusters/`, `config/`, `scripts/`
- 现有文档数量: 3 - 现有文档数量: 3
## 核心双语文档 ## 核心双语文档
@ -20,6 +20,5 @@
## 待归并的历史文档 ## 待归并的历史文档
- `gpu-k8s-role.md`
- `repo-structure.md` - `repo-structure.md`
- `stackflow/README.md` - `stackflow/README.md`

View File

@ -1,6 +1,6 @@
# 架构 # 架构
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色 该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付
本页作为系统边界、核心组件与仓库职责的双语总览入口。 本页作为系统边界、核心组件与仓库职责的双语总览入口。

View File

@ -1,6 +1,6 @@
# 部署 # 部署
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色 该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付
本页用于统一部署前提、支持的拓扑、运维检查项与回滚注意事项。 本页用于统一部署前提、支持的拓扑、运维检查项与回滚注意事项。

View File

@ -1,6 +1,6 @@
# 设计 # 设计
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色 该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付
本页用于汇总设计决策、类似 ADR 的权衡记录,以及与路线图相关的实现说明。 本页用于汇总设计决策、类似 ADR 的权衡记录,以及与路线图相关的实现说明。

View File

@ -1,6 +1,6 @@
# 开发手册 # 开发手册
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色 该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付
本页用于记录本地开发环境、项目结构、测试面与贴合当前代码库的贡献约定。 本页用于记录本地开发环境、项目结构、测试面与贴合当前代码库的贡献约定。

View File

@ -1,6 +1,6 @@
# 使用手册 # 使用手册
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色 该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付
本页用于记录主要用户或运维角色的日常任务、常见流程,以及现有操作文档入口。 本页用于记录主要用户或运维角色的日常任务、常见流程,以及现有操作文档入口。

View File

@ -1,6 +1,6 @@
# Vibe Coding 参考 # Vibe Coding 参考
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色 该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付
本页用于统一 AI 辅助开发提示词、仓库边界、安全编辑规则与文档同步要求。 本页用于统一 AI 辅助开发提示词、仓库边界、安全编辑规则与文档同步要求。

View File

@ -1,23 +0,0 @@
repositories:
- name: stable
url: https://charts.onwalk.net
username: {{ requiredEnv "ARF_USERNAME" }}
password: {{ requiredEnv "ARF_PASSWORD" | quote }}
templates:
default: &default
namespace: itsm-dev
createNamespace: true
chart: stable/app-frontend
version: 0.1.3
releases:
- name: itsm-dev
<<: *default
set:
- name: image.repository
value: ""
- name: image.tag
value: ""
- name: service.port
value: ""

View File

@ -1,52 +0,0 @@
repositories:
- name: stable
url: https://charts.onwalk.net
username: {{ requiredEnv "ARF_USERNAME" }}
password: {{ requiredEnv "ARF_PASSWORD" | quote }}
common:
namespace: itsm-tools-stg
createNamespace: false
chart: stable/app-backend
version: 0.1.3
values:
service:
type: ClusterIP
ports:
- name: http
port: 8000
targetPort: http
protocol: TCP
- name: grpc
port: 9000
targetPort: grpc
protocol: TCP
containerPorts:
- name: http
port: 8000
protocol: TCP
- name: grpc
port: 9000
protocol: TCP
releases:
- name: itsm-ticketing
<<: {{"{{" }} include "common" . | nindent 4 {{ "}}" }}
set:
- name: image.repository
value: artifact.onwalk.net/itsm/ticketing
- name: image.tag
value: 'unknown-fa7fed7'
- name: volumeMounts
value:
- name: ticketing-config
mountPath: /ticketing/conf
- name: volumes
value:
- name: ticketing-config
secret:
secretName: itsm
defaultMode: 420
items:
- key: ticketing.yaml
path: config.yaml

View File

@ -1,53 +0,0 @@
repositories:
- name: stable
url: https://charts.onwalk.net
username: {{ requiredEnv "ARF_USERNAME" }}
password: {{ requiredEnv "ARF_PASSWORD" | quote }}
templates:
default: &default
namespace: itsm-dev
createNamespace: true
chart: stable/itsm-tools
version: 0.1.18
releases:
- name: itsm-dev
<<: *default
set:
- name: apisix.dashboard.enabled
value: true
- name: apisix.dashboard.ingress.enabled
value: true
- name: apisix.dashboard.ingress.className
value: nginx
- name: apisix.dashboard.ingress.hosts[0].host
value: apisix-dashboard.onwalk.net
- name: apisix.dashboard.ingress.hosts[0].paths[0]
value: '/*'
- name: apisix.etcd.enabled
value: true
- name: etcd-adapter.enabled
value: false
- name: novu.web.ingress.enabled
value: true
- name: novu.web.ingress.ingressClassName
value: nginx
- name: novu.web.ingress.hostname
value: 'novu.onwalk.net'
- name: novu.redis.enabled
value: false
- name: novu.externalRedis.host
value: redis.local
- name: novu.externalRedis.existingSecret
value: itsm-redis-secret
- name: novu.mongodb.enabled
value: false
- name: novu.externalDatabase.existingSecret
value: itsm-mongodb-secret-rw
- name: novu.localstack.enabled
value: false
- name: novu.externalS3.existingSecret
value: itsm-s3-secret-rw
- name: windmill.databaseUrlSecretName
value: itsm-postgresql-secret-rw

View File

@ -1,51 +0,0 @@
repositories:
- name: stable
url: https://charts.onwalk.net
templates:
default: &default
namespace: itsm-dev
createNamespace: true
chart: stable/itsm
version: 0.1.16
releases:
- name: itsm-dev
<<: *default
set:
- name: apisix.dashboard.enabled
value: true
- name: apisix.dashboard.ingress.enabled
value: true
- name: apisix.dashboard.ingress.className
value: nginx
- name: apisix.dashboard.ingress.hosts[0].host
value: apisix-dashboard.onwalk.net
- name: apisix.dashboard.ingress.hosts[0].paths[0]
value: '/*'
- name: apisix.etcd.enabled
value: true
- name: etcd-adapter.enabled
value: false
- name: novu.web.ingress.enabled
value: true
- name: novu.web.ingress.ingressClassName
value: nginx
- name: novu.web.ingress.hostname
value: 'novu.onwalk.net'
- name: novu.redis.enabled
value: false
- name: novu.externalRedis.host
value: redis.local
- name: novu.externalRedis.existingSecret
value: itsm-redis-secret
- name: novu.mongodb.enabled
value: false
- name: novu.externalDatabase.existingSecret
value: itsm-mongodb-secret-rw
- name: novu.localstack.enabled
value: false
- name: novu.externalS3.existingSecret
value: itsm-s3-secret-rw
- name: windmill.databaseUrlSecretName
value: itsm-postgresql-secret-rw

View File

@ -1,12 +0,0 @@
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
releases:
- name: postgresql
namespace: default
chart: bitnami/postgresql
values:
- auth:
username: my-user
password: my-password

View File

@ -1,4 +1,4 @@
apiVersion: external-secrets.io/v1beta1 apiVersion: external-secrets.io/v1
kind: ExternalSecret kind: ExternalSecret
metadata: metadata:
name: accounts-env name: accounts-env
@ -13,4 +13,3 @@ spec:
dataFrom: dataFrom:
- extract: - extract:
key: core/pre/accounts key: core/pre/accounts

View File

@ -1,4 +1,4 @@
apiVersion: external-secrets.io/v1beta1 apiVersion: external-secrets.io/v1
kind: ExternalSecret kind: ExternalSecret
metadata: metadata:
name: accounts-env name: accounts-env
@ -13,4 +13,3 @@ spec:
dataFrom: dataFrom:
- extract: - extract:
key: core/prod/accounts key: core/prod/accounts

View File

@ -1,4 +1,4 @@
apiVersion: external-secrets.io/v1beta1 apiVersion: external-secrets.io/v1
kind: ExternalSecret kind: ExternalSecret
metadata: metadata:
name: console-env name: console-env
@ -13,4 +13,3 @@ spec:
dataFrom: dataFrom:
- extract: - extract:
key: core/pre/console key: core/pre/console

View File

@ -1,4 +1,4 @@
apiVersion: external-secrets.io/v1beta1 apiVersion: external-secrets.io/v1
kind: ExternalSecret kind: ExternalSecret
metadata: metadata:
name: console-env name: console-env
@ -13,4 +13,3 @@ spec:
dataFrom: dataFrom:
- extract: - extract:
key: core/prod/console key: core/prod/console

View File

@ -13,4 +13,4 @@ spec:
name: platform-config name: platform-config
path: ./infra/infrastructure path: ./infra/infrastructure
dependsOn: dependsOn:
- name: platform-secrets-stack - name: platform-stack

View File

@ -3,8 +3,6 @@ kind: Kustomization
resources: resources:
- namespaces.yaml - namespaces.yaml
- platform-kustomization.yaml - platform-kustomization.yaml
- platform-secrets-kustomization.yaml
- platform-services-kustomization.yaml
- infrastructure-kustomization.yaml - infrastructure-kustomization.yaml
- observability-kustomization.yaml - observability-kustomization.yaml
- console-prod-kustomization.yaml - console-prod-kustomization.yaml

View File

@ -1,16 +0,0 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: platform-secrets-stack
namespace: flux-system
spec:
interval: 5m0s
prune: true
wait: true
timeout: 5m0s
sourceRef:
kind: GitRepository
name: platform-config
path: ./infra/platform-secrets
dependsOn:
- name: platform-stack

View File

@ -1,16 +0,0 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: platform-services-stack
namespace: flux-system
spec:
interval: 5m0s
prune: true
wait: true
timeout: 10m0s
sourceRef:
kind: GitRepository
name: platform-config
path: ./infra/platform/external-dns
dependsOn:
- name: platform-stack

View File

@ -1,4 +1,4 @@
apiVersion: external-secrets.io/v1beta1 apiVersion: external-secrets.io/v1
kind: ExternalSecret kind: ExternalSecret
metadata: metadata:
name: postgresql-auth name: postgresql-auth

View File

@ -1,4 +1,4 @@
apiVersion: external-secrets.io/v1beta1 apiVersion: external-secrets.io/v1
kind: ExternalSecret kind: ExternalSecret
metadata: metadata:
name: postgresql-stunnel-server name: postgresql-stunnel-server

View File

@ -1,17 +0,0 @@
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: vault-platform
spec:
provider:
vault:
server: http://vault.extsvc.svc.cluster.local:8200
path: secret
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: external-secrets
serviceAccountRef:
name: external-secrets
namespace: platform

View File

@ -1,4 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- clustersecretstore.yaml

View File

@ -1,19 +0,0 @@
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: cloudflare-api-token
namespace: platform
spec:
refreshInterval: 1m
secretStoreRef:
kind: ClusterSecretStore
name: vault-platform
target:
name: cloudflare-api-token
creationPolicy: Owner
data:
- secretKey: api-token
remoteRef:
key: platform/cloudflare
property: api-token

View File

@ -1,36 +0,0 @@
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: external-dns
namespace: platform
spec:
interval: 10m0s
chart:
spec:
chart: external-dns
version: ">=1.14.0 <2.0.0"
sourceRef:
kind: HelmRepository
name: external-dns
namespace: flux-system
install:
remediation:
retries: 3
upgrade:
remediation:
retries: 3
values:
provider: cloudflare
policy: sync
registry: txt
txtOwnerId: svc-plus-k3s
sources:
- ingress
domainFilters:
- svc.plus
env:
- name: CF_API_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-api-token
key: api-token

View File

@ -1,7 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: platform
resources:
- externalsecret.yaml
- helmrelease.yaml

View File

@ -7,7 +7,7 @@ spec:
interval: 10m0s interval: 10m0s
url: oci://ghcr.io/x-evor/k3s-platform-chart url: oci://ghcr.io/x-evor/k3s-platform-chart
ref: ref:
semver: "0.1.3" semver: "0.1.4"
layerSelector: layerSelector:
mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
operation: copy operation: copy

View File

@ -73,6 +73,59 @@ components:
type: roundrobin type: roundrobin
nodes: nodes:
"accounts.core-pre.svc.cluster.local:80": 1 "accounts.core-pre.svc.cluster.local:80": 1
externalDns:
enabled: true
releaseName: external-dns
sourceRef:
kind: HelmRepository
name: external-dns
namespace: flux-system
chart:
name: external-dns
version: ">=1.14.0 <2.0.0"
secret:
name: cloudflare-api-token
refreshInterval: 1m
secretStoreRef:
kind: ClusterSecretStore
name: vault-platform
target:
name: cloudflare-api-token
creationPolicy: Owner
data:
secretKey: api-token
remoteRef:
key: platform/cloudflare
property: api-token
values:
provider: cloudflare
policy: sync
registry: txt
txtOwnerId: svc-plus-k3s
sources:
- ingress
domainFilters:
- svc.plus
env:
- name: CF_API_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-api-token
key: api-token
externalSecretsStore:
enabled: true
name: vault-platform
vault:
server: http://vault.extsvc.svc.cluster.local:8200
path: secret
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: external-secrets
serviceAccountRef:
name: external-secrets
namespace: platform
vault: vault:
enabled: true enabled: true
releaseName: vault releaseName: vault

View File

@ -1,10 +0,0 @@
[all]
k8s-1 ansible_host=13.158.69.227
k8s-2 ansible_host=57.183.6.87
k8s-3 ansible_host=43.207.133.165
[all:vars]
ansible_port=22
ansible_ssh_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_host_key_checking=False

View File

@ -1,5 +0,0 @@
ansible_port: 22
ansible_ssh_user: ubuntu
ansible_ssh_private_key_file: ~/.ssh/id_rsa
ansible_host_key_checking: False

View File

@ -1,13 +0,0 @@
[all]
xray-sg.svc.plus ansible_host=18.142.253.71
cn-console.svc.plus ansible_host=8.155.148.173 ansible_ssh_user=root
global-console.svc.plus ansible_host=52.195.9.10
[gateway]
vpn-gateway.svc.plus ansible_host=167.179.72.223 ansible_ssh_user=root
[all:vars]
ansible_port=22
ansible_ssh_user=ubuntu
ansible_host_key_checking=False
ansible_ssh_private_key_file=~/.ssh/id_rsa

View File

@ -1,2 +0,0 @@
[vpn-gateway]
xproxy.onwalk.net ansible_host=43.206.158.21

View File

@ -1,12 +0,0 @@
[all]
cn-gateway.svc.plus ansible_host=10.254.0.1
cn-k3s-server.svc.plus ansible_host=10.254.0.3
cn-hw-node.svc.plus ansible_host=10.254.0.4
global-gateway.svc.plus ansible_host=10.255.0.1
global-k3s-server.svc.plus ansible_host=10.255.0.3
[all:vars]
ansible_port=22
ansible_ssh_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_host_key_checking=False

View File

@ -1,58 +0,0 @@
# HAProxy Role
This role provisions a thin, include-only HAProxy configuration tree under `/etc/haproxy/`. It follows a map-driven SNI routing strategy so that new hostnames can be added without touching the frontend logic.
## Layout
```
/etc/haproxy/
├── haproxy.cfg # Main entry point (kept thin)
├── global.cfg # global + defaults
├── frontends/
│ ├── fe_443.cfg
│ └── fe_stats.cfg
├── backends/
│ ├── console/
│ │ ├── bk_cn.cfg
│ │ └── bk_global.cfg
│ ├── xray/
│ │ ├── bk_xray_jp.cfg
│ │ ├── bk_xray_sg.cfg
│ │ └── bk_xray_hk.cfg
│ └── fallback/
│ └── bk_blackhole.cfg
├── maps/
│ ├── sni.map
│ └── sni_backend.map
├── certs/ # For HTTP/TLS termination if needed
├── scripts/
│ └── reload.sh
└── logs/
```
### Main configuration (`haproxy.cfg`)
- Includes `global.cfg` for shared defaults.
- Includes all frontends and recursively includes business backends.
- Contains no business logic to keep reloads predictable.
### Global/defaults (`global.cfg`)
Applies TCP defaults and keeps per-node tuning minimal. HK/JP can adjust `maxconn` per-node if needed.
### Frontends
- `fe_443.cfg` performs TLS inspection and routes via the SNI map (`maps/sni_backend.map`), falling back to `bk_blackhole`.
- `fe_stats.cfg` exposes the HAProxy stats UI on `:8404`.
### Maps
- `maps/sni_backend.map` maps SNI hosts to backends; adding a domain means adding a single line.
- `maps/sni.map` documents the expected format when TLS termination is required.
### Backends
Business backends are grouped by domain family (console/xray/fallback) with TCP health checks and consistent timings. The fallback backend intentionally blackholes unmatched traffic.
### Reload helper
`scripts/reload.sh` validates the configuration and reloads HAProxy gracefully (preferring `systemctl` when present).
## Usage
1. Include the role in a play targeting your HAProxy hosts.
2. Update `maps/sni_backend.map` with the desired hostname-to-backend mapping.
3. Adjust backend server endpoints per site; keep the directory layout identical across regions.
4. Run the play to copy the configuration tree and trigger a graceful reload via the handler.

View File

@ -1,5 +0,0 @@
---
haproxy_conf_dir: /etc/haproxy
haproxy_user: root
haproxy_group: root
haproxy_reload_cmd: /etc/haproxy/scripts/reload.sh

View File

@ -1,7 +0,0 @@
backend bk_console_cn
balance roundrobin
option tcp-check
default-server inter 3s fall 3 rise 2
server cn1 10.10.1.1:8443 check
server cn2 10.10.1.2:8443 check

View File

@ -1,7 +0,0 @@
backend bk_console_global
balance roundrobin
option tcp-check
default-server inter 3s fall 3 rise 2
server g1 10.10.0.1:8443 check
server g2 10.10.0.2:8443 check

View File

@ -1,2 +0,0 @@
backend bk_blackhole
server reject 127.0.0.1:1

View File

@ -1,7 +0,0 @@
backend bk_xray_hk
balance roundrobin
option tcp-check
default-server inter 3s fall 3 rise 2
server hk1 10.22.0.1:1443 check send-proxy-v2
server hk2 10.22.0.2:1443 check send-proxy-v2

View File

@ -1,7 +0,0 @@
backend bk_xray_jp
balance roundrobin
option tcp-check
default-server inter 3s fall 3 rise 2
server jp1 10.20.0.1:1443 check send-proxy-v2
server jp2 10.20.0.2:1443 check send-proxy-v2

View File

@ -1,7 +0,0 @@
backend bk_xray_sg
balance roundrobin
option tcp-check
default-server inter 3s fall 3 rise 2
server sg1 10.21.0.1:1443 check send-proxy-v2
server sg2 10.21.0.2:1443 check send-proxy-v2

View File

@ -1,9 +0,0 @@
frontend fe_443
bind :443
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }
use_backend %[req.ssl_sni,lower,map(/etc/haproxy/maps/sni_backend.map)]
default_backend bk_blackhole

View File

@ -1,5 +0,0 @@
listen stats
bind :8404
stats enable
stats uri /stats
stats refresh 5s

View File

@ -1,14 +0,0 @@
global
daemon
log /dev/log local0
maxconn 20000
nbthread 1
defaults
mode tcp
log global
option dontlognull
timeout connect 3s
timeout client 1m
timeout server 1m
timeout check 3s

View File

@ -1,10 +0,0 @@
# Main HAProxy configuration file (intentionally thin)
# 引入全局参数
include /etc/haproxy/global.cfg
# Frontends
include /etc/haproxy/frontends/*.cfg
# Backends
include /etc/haproxy/backends/**/*.cfg

View File

@ -1,3 +0,0 @@
# Map SNI to certificate filenames when terminating TLS locally.
# <sni> <certificate-file>
# example.com example-com.pem

View File

@ -1,5 +0,0 @@
cn-console.sv.plus bk_console_cn
global-console.sv.plus bk_console_global
xray-jp.svc.plus bk_xray_jp
xray-sg.svc.plus bk_xray_sg
xray-hk.svc.plus bk_xray_hk

View File

@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
CONFIG="/etc/haproxy/haproxy.cfg"
PIDFILE="/var/run/haproxy.pid"
HAPROXY_BIN="${HAPROXY_BIN:-/usr/sbin/haproxy}"
SYSTEMCTL_BIN="${SYSTEMCTL_BIN:-/bin/systemctl}"
# Validate configuration before applying
if ! "${HAPROXY_BIN}" -c -f "${CONFIG}"; then
echo "HAProxy configuration validation failed" >&2
exit 1
fi
# Reload HAProxy gracefully
if command -v "${SYSTEMCTL_BIN}" >/dev/null 2>&1; then
exec "${SYSTEMCTL_BIN}" reload haproxy
else
if [[ -r "${PIDFILE}" ]]; then
exec "${HAPROXY_BIN}" -f "${CONFIG}" -sf "$(cat "${PIDFILE}")"
else
exec "${HAPROXY_BIN}" -f "${CONFIG}"
fi
fi

View File

@ -1,4 +0,0 @@
---
- name: reload haproxy
ansible.builtin.command: "{{ haproxy_reload_cmd }}"
listen: reload haproxy

View File

@ -1,15 +0,0 @@
---
galaxy_info:
role_name: haproxy
author: gitops
description: "HAProxy role with thin main config, map-driven SNI routing, and organized backends."
min_ansible_version: "2.10"
platforms:
- name: EL
versions: [8, 9]
- name: Debian
versions: [11, 12]
- name: Ubuntu
versions: [20.04, 22.04]
dependencies: []

View File

@ -1,51 +0,0 @@
---
- name: Ensure HAProxy base directory exists
ansible.builtin.file:
path: "{{ haproxy_conf_dir }}"
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: "0755"
- name: Ensure HAProxy subdirectories exist
ansible.builtin.file:
path: "{{ haproxy_conf_dir }}/{{ item }}"
state: directory
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: "0755"
loop:
- frontends
- backends
- backends/console
- backends/xray
- backends/fallback
- maps
- certs
- scripts
- logs
- name: Deploy HAProxy configuration files
ansible.builtin.copy:
src: "etc/haproxy/{{ item.src }}"
dest: "{{ haproxy_conf_dir }}/{{ item.dest | default(item.src) }}"
owner: "{{ haproxy_user }}"
group: "{{ haproxy_group }}"
mode: "{{ item.mode | default('0644') }}"
loop:
- { src: "haproxy.cfg" }
- { src: "global.cfg" }
- { src: "frontends/fe_443.cfg" }
- { src: "frontends/fe_stats.cfg" }
- { src: "backends/console/bk_cn.cfg" }
- { src: "backends/console/bk_global.cfg" }
- { src: "backends/xray/bk_xray_jp.cfg" }
- { src: "backends/xray/bk_xray_sg.cfg" }
- { src: "backends/xray/bk_xray_hk.cfg" }
- { src: "backends/fallback/bk_blackhole.cfg" }
- { src: "maps/sni_backend.map" }
- { src: "maps/sni.map" }
- { src: "certs/.gitkeep" }
- { src: "logs/.gitkeep" }
- { src: "scripts/reload.sh", mode: "0755" }
notify: reload haproxy

View File

@ -1,16 +0,0 @@
---
# Required
cloudflare_dns_domain: ""
cloudflare_dns_rr: ""
cloudflare_dns_type: ""
cloudflare_dns_value: ""
# Optional
# Cloudflare TTL supports 1 (automatic) or a provider-dependent range.
cloudflare_dns_ttl: 1
cloudflare_dns_proxied: false
cloudflare_dns_priority: null
# Secret (pass via extra-vars / env / vault; never commit real values)
cloudflare_api_token: ""

View File

@ -1,194 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import annotations
import json
import urllib.parse
import urllib.request
from ansible.module_utils.basic import AnsibleModule
API_BASE = "https://api.cloudflare.com/client/v4"
def _request(method, path, api_token, payload=None, query=None):
url = API_BASE + path
if query:
url += "?" + urllib.parse.urlencode(query)
body = None
if payload is not None:
body = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(url=url, data=body, method=method)
req.add_header("Authorization", "Bearer " + api_token)
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req, timeout=30) as resp:
raw = resp.read().decode("utf-8")
return json.loads(raw)
def _api_ok(data):
return isinstance(data, dict) and data.get("success") is True
def _api_first_result(data):
if not _api_ok(data):
return None
res = data.get("result")
if isinstance(res, list) and res:
return res[0]
return None
def _zone_id(api_token, zone_name):
data = _request("GET", "/zones", api_token, query={"name": zone_name, "per_page": 50})
z = _api_first_result(data)
if not z:
return None
return z.get("id")
def _record_fqdn(zone, rr):
rr = rr.strip()
if rr in ("@", zone):
return zone
return rr + "." + zone
def _get_record(api_token, zone_id, record_type, fqdn):
data = _request(
"GET",
f"/zones/{zone_id}/dns_records",
api_token,
query={"type": record_type, "name": fqdn, "per_page": 50},
)
return _api_first_result(data)
def main():
module = AnsibleModule(
argument_spec=dict(
state=dict(type="str", choices=["present", "absent"], default="present"),
zone=dict(type="str", required=True),
rr=dict(type="str", required=True),
type=dict(type="str", required=True),
value=dict(type="str"),
ttl=dict(type="int", default=1),
proxied=dict(type="bool", default=False),
priority=dict(type="int", required=False),
api_token=dict(type="str", required=True, no_log=True),
),
supports_check_mode=True,
)
state = module.params["state"]
zone = module.params["zone"]
rr = module.params["rr"]
record_type = module.params["type"].upper()
value = module.params["value"]
ttl = module.params["ttl"]
proxied = module.params["proxied"]
priority = module.params.get("priority", None)
api_token = module.params["api_token"]
if state == "present" and not value:
module.fail_json(msg="value is required when state=present")
try:
zid = _zone_id(api_token, zone)
except Exception as e:
module.fail_json(msg=f"Failed to query Cloudflare zone id: {e}")
if not zid:
module.fail_json(msg=f"Cloudflare zone not found: {zone}")
fqdn = _record_fqdn(zone, rr)
try:
existing = _get_record(api_token, zid, record_type, fqdn)
except Exception as e:
module.fail_json(msg=f"Failed to query Cloudflare DNS record: {e}")
# ----------------------------
# ABSENT
# ----------------------------
if state == "absent":
if not existing:
module.exit_json(changed=False, msg="Record already absent")
if module.check_mode:
module.exit_json(changed=True)
rid = existing.get("id")
try:
data = _request("DELETE", f"/zones/{zid}/dns_records/{rid}", api_token)
except Exception as e:
module.fail_json(msg=f"Failed to delete record: {e}")
if not _api_ok(data):
module.fail_json(msg="Cloudflare API error deleting record", details=data)
module.exit_json(changed=True, msg="Record deleted", record_id=rid, fqdn=fqdn)
# ----------------------------
# PRESENT (create/update)
# ----------------------------
desired = {
"type": record_type,
"name": fqdn,
"content": value,
"ttl": ttl,
"proxied": proxied,
}
if priority is not None:
desired["priority"] = priority
if existing:
cur = {
"type": existing.get("type"),
"name": existing.get("name"),
"content": existing.get("content"),
"ttl": existing.get("ttl"),
"proxied": existing.get("proxied"),
}
if priority is not None:
cur["priority"] = existing.get("priority")
if cur == desired:
module.exit_json(
changed=False,
msg="Record already up to date",
record_id=existing.get("id"),
fqdn=fqdn,
)
if module.check_mode:
module.exit_json(changed=True)
rid = existing.get("id")
try:
data = _request("PUT", f"/zones/{zid}/dns_records/{rid}", api_token, payload=desired)
except Exception as e:
module.fail_json(msg=f"Failed to update record: {e}")
if not _api_ok(data):
module.fail_json(msg="Cloudflare API error updating record", details=data)
module.exit_json(changed=True, msg="Record updated", record_id=rid, fqdn=fqdn)
# CREATE
if module.check_mode:
module.exit_json(changed=True)
try:
data = _request("POST", f"/zones/{zid}/dns_records", api_token, payload=desired)
except Exception as e:
module.fail_json(msg=f"Failed to create record: {e}")
if not _api_ok(data):
module.fail_json(msg="Cloudflare API error creating record", details=data)
rec = data.get("result") or {}
module.exit_json(changed=True, msg="Record created", record_id=rec.get("id"), fqdn=fqdn)
if __name__ == "__main__":
main()

View File

@ -1,17 +0,0 @@
---
- name: Ensure Cloudflare DNS Record
cloudflare_dns_record:
state: present
zone: "{{ cloudflare_dns_domain }}"
rr: "{{ cloudflare_dns_rr }}"
type: "{{ cloudflare_dns_type }}"
value: "{{ cloudflare_dns_value }}"
ttl: "{{ cloudflare_dns_ttl }}"
proxied: "{{ cloudflare_dns_proxied }}"
priority: "{{ cloudflare_dns_priority }}"
api_token: "{{ cloudflare_api_token }}"
register: dns_result
- debug:
var: dns_result

View File

@ -1,8 +0,0 @@
---
cloudflare_dns_sync_domain: ""
cloudflare_dns_sync_records: []
cloudflare_dns_sync_output: "/tmp/dns_records.yaml"
# Secret: set via extra-vars / env / vault; do not commit real values.
cloudflare_api_token: ""

View File

@ -1,118 +0,0 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import os
import sys
import urllib.parse
import urllib.request
import yaml
API_BASE = "https://api.cloudflare.com/client/v4"
def _req(method: str, path: str, token: str, payload=None, query=None):
url = API_BASE + path
if query:
url += "?" + urllib.parse.urlencode(query)
body = None
if payload is not None:
body = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(url=url, data=body, method=method)
req.add_header("Authorization", "Bearer " + token)
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode("utf-8"))
def _ok(d):
return isinstance(d, dict) and d.get("success") is True
def zone_id(token: str, zone_name: str) -> str:
d = _req("GET", "/zones", token, query={"name": zone_name, "per_page": 50})
if not _ok(d) or not d.get("result"):
raise RuntimeError(f"zone not found: {zone_name}")
return d["result"][0]["id"]
def fqdn(zone: str, rr: str) -> str:
rr = rr.strip()
if rr in ("@", zone):
return zone
return rr + "." + zone
def get_record(token: str, zid: str, rtype: str, name: str):
d = _req("GET", f"/zones/{zid}/dns_records", token, query={"type": rtype, "name": name, "per_page": 50})
if not _ok(d):
raise RuntimeError("query dns_records failed")
res = d.get("result") or []
return res[0] if res else None
def ensure_record(token: str, zid: str, zone: str, rec: dict):
rr = rec["rr"]
rtype = rec["type"].upper()
value = rec["value"]
ttl = int(rec.get("ttl", 1))
proxied = bool(rec.get("proxied", False))
priority = rec.get("priority", None)
name = fqdn(zone, rr)
desired = {"type": rtype, "name": name, "content": value, "ttl": ttl, "proxied": proxied}
if priority is not None:
desired["priority"] = int(priority)
cur = get_record(token, zid, rtype, name)
if not cur:
print("CREATE:", desired)
d = _req("POST", f"/zones/{zid}/dns_records", token, payload=desired)
if not _ok(d):
raise RuntimeError("create failed: " + json.dumps(d))
return
cur_slim = {
"type": cur.get("type"),
"name": cur.get("name"),
"content": cur.get("content"),
"ttl": cur.get("ttl"),
"proxied": cur.get("proxied"),
}
if priority is not None:
cur_slim["priority"] = cur.get("priority")
if cur_slim == desired:
print("OK:", desired["name"], desired["type"])
return
print("UPDATE:", desired)
rid = cur["id"]
d = _req("PUT", f"/zones/{zid}/dns_records/{rid}", token, payload=desired)
if not _ok(d):
raise RuntimeError("update failed: " + json.dumps(d))
def main(argv: list[str]) -> int:
if len(argv) != 2:
print(f"usage: {sys.argv[0]} <dns_records.yaml>", file=sys.stderr)
return 2
fn = argv[1]
token = os.environ.get("CLOUDFLARE_API_TOKEN", "").strip()
if not token:
print("CLOUDFLARE_API_TOKEN is required", file=sys.stderr)
return 2
cfg = yaml.safe_load(open(fn, "r", encoding="utf-8")) or {}
for zone, recs in cfg.items():
zid = zone_id(token, zone)
for rec in recs or []:
ensure_record(token, zid, zone, rec)
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))

View File

@ -1,19 +0,0 @@
---
- name: Generate DNS records file from template
template:
src: dns_records.yaml.j2
dest: "{{ cloudflare_dns_sync_output }}"
- name: Upload dns_sync.py
copy:
src: dns_sync.py
dest: /tmp/dns_sync.py
mode: "0755"
- name: Sync DNS records
command: >
python3 /tmp/dns_sync.py
{{ cloudflare_dns_sync_output }}
environment:
CLOUDFLARE_API_TOKEN: "{{ cloudflare_api_token }}"

View File

@ -1,9 +0,0 @@
{{ cloudflare_dns_sync_domain }}:
{% for rec in cloudflare_dns_sync_records %}
- rr: "{{ rec.rr }}"
type: "{{ rec.type }}"
value: "{{ rec.value }}"
ttl: {{ rec.ttl | default(1) }}
proxied: {{ rec.proxied | default(false) }}
{% endfor %}