chore(gitops): sync workspace state
This commit is contained in:
parent
5224b82b7d
commit
e4815a8c97
106
README.md
106
README.md
@ -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).
|
||||
Additional documentation is stored under the `docs/` folder.
|
||||
## Scope
|
||||
|
||||
## 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 进行容器化管理。
|
||||
2. playbooks/roles/charts:面向大规模的 Kubernetes 集群,使用 Helm 和标准化 Chart 部署模式进行高可用和可扩展的管理。
|
||||
3. playbooks/roles/vhosts:传统的非容器化部署方式,通常涉及手动配置服务器和虚拟主机,适用于不使用容器的应用场景。
|
||||
## Layout
|
||||
|
||||
- `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
|
||||
|
||||
| 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.
|
||||
|
||||
For a quick structure overview, see [docs/repo-structure.md](docs/repo-structure.md).
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -21,13 +21,12 @@ This `docs/` directory now has a bilingual canonical layer for the current repos
|
||||
|
||||
## Current Repo Context / 当前仓库背景
|
||||
|
||||
- Root README: `ansible-playbook`
|
||||
- Root README: `Cloud-Neutral Toolkit GitOps`
|
||||
- Previous docs index: `Documentation`
|
||||
- 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 / 需要继续归并的现有文档
|
||||
|
||||
- `gpu-k8s-role.md`
|
||||
- `repo-structure.md`
|
||||
- `stackflow/README.md`
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
# 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
|
||||
|
||||
- Root README title: `ansible-playbook`
|
||||
- Root README title: `Cloud-Neutral Toolkit GitOps`
|
||||
- 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
|
||||
|
||||
## Canonical pages
|
||||
@ -20,6 +20,5 @@ This repository organizes GitOps assets, playbooks, and operational roles for in
|
||||
|
||||
## Legacy docs to fold in
|
||||
|
||||
- `gpu-k8s-role.md`
|
||||
- `repo-structure.md`
|
||||
- `stackflow/README.md`
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
|
||||
@ -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.
|
||||
@ -1,19 +1,12 @@
|
||||
# Repository Structure
|
||||
|
||||
This repository combines Ansible playbooks with Kubernetes manifests and
|
||||
automation scripts. Below is a short overview of the key directories.
|
||||
This repository contains declarative GitOps assets only. Below is a short overview of the key directories.
|
||||
|
||||
| Directory | Purpose |
|
||||
|-----------|---------|
|
||||
| `playbooks` | Ansible playbooks and role definitions. |
|
||||
| `apps` | Flux HelmRelease and Kustomize files for applications. |
|
||||
| `clusters` | Kustomize overlays for different clusters referencing the `apps` definitions. |
|
||||
| `helmfiles` | Sample [helmfile](https://github.com/helmfile/helmfile) declarations. |
|
||||
| `helm` | Local Helm charts used in some playbooks. |
|
||||
| `inventory` | Example inventories and group variables for Ansible. |
|
||||
| `scripts` | Utility scripts such as cluster setup or secret management. |
|
||||
| `sync` | Tasks for local host setup and testing. |
|
||||
| `infra` | Platform and infrastructure declarations managed by Flux. |
|
||||
| `scripts` | Utility scripts that support validation or operational workflows. |
|
||||
| `config` | Non-sensitive configuration references and examples. |
|
||||
| `docs` | Additional documentation. |
|
||||
|
||||
See `docs/gpu-k8s-role.md` for an example walkthrough deploying a GPU-enabled
|
||||
Kubernetes cluster.
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
# StackFlow (GitOps YAML Flow)
|
||||
|
||||
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:
|
||||
- `playbooks/` (Ansible provisioning for vhosts/docker/k3s)
|
||||
- `iac-template/` (Terraform reference templates)
|
||||
- `.github/workflows/` (bootstrap workflows)
|
||||
|
||||
@ -27,7 +26,7 @@ Top-level:
|
||||
- `kind`: `StackFlow`
|
||||
- `metadata.name`: stack id
|
||||
- `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`
|
||||
- `targets[]`: list of deployable targets
|
||||
|
||||
@ -52,7 +51,7 @@ Planned phases:
|
||||
- `dns-plan`: output required DNS records (no apply)
|
||||
- `dns-apply`: apply DNS changes (provider-specific)
|
||||
- `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
|
||||
|
||||
Today we only ship `validate` + `dns-plan` as the first step.
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
# GitOps 运维仓库 文档
|
||||
|
||||
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色。
|
||||
该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付。
|
||||
|
||||
## 当前状态快照
|
||||
|
||||
- 根 README 标题: `ansible-playbook`
|
||||
- 根 README 标题: `Cloud-Neutral Toolkit GitOps`
|
||||
- 构建与运行时证据: repository structure and scripts only
|
||||
- 自动识别的主要目录: `scripts/`, `StackFlow/`, `config/`
|
||||
- 自动识别的主要目录: `infra/`, `apps/`, `clusters/`, `config/`, `scripts/`
|
||||
- 现有文档数量: 3
|
||||
|
||||
## 核心双语文档
|
||||
@ -20,6 +20,5 @@
|
||||
|
||||
## 待归并的历史文档
|
||||
|
||||
- `gpu-k8s-role.md`
|
||||
- `repo-structure.md`
|
||||
- `stackflow/README.md`
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 架构
|
||||
|
||||
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色。
|
||||
该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付。
|
||||
|
||||
本页作为系统边界、核心组件与仓库职责的双语总览入口。
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 部署
|
||||
|
||||
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色。
|
||||
该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付。
|
||||
|
||||
本页用于统一部署前提、支持的拓扑、运维检查项与回滚注意事项。
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 设计
|
||||
|
||||
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色。
|
||||
该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付。
|
||||
|
||||
本页用于汇总设计决策、类似 ADR 的权衡记录,以及与路线图相关的实现说明。
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 开发手册
|
||||
|
||||
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色。
|
||||
该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付。
|
||||
|
||||
本页用于记录本地开发环境、项目结构、测试面与贴合当前代码库的贡献约定。
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 使用手册
|
||||
|
||||
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色。
|
||||
该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付。
|
||||
|
||||
本页用于记录主要用户或运维角色的日常任务、常见流程,以及现有操作文档入口。
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Vibe Coding 参考
|
||||
|
||||
该仓库组织 GitOps 资产、Playbook 与基础设施交付所需的运维角色。
|
||||
该仓库仅组织声明式 GitOps 资产,用于基础设施与应用交付。
|
||||
|
||||
本页用于统一 AI 辅助开发提示词、仓库边界、安全编辑规则与文档同步要求。
|
||||
|
||||
|
||||
@ -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: ""
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,4 +1,4 @@
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: accounts-env
|
||||
@ -13,4 +13,3 @@ spec:
|
||||
dataFrom:
|
||||
- extract:
|
||||
key: core/pre/accounts
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: accounts-env
|
||||
@ -13,4 +13,3 @@ spec:
|
||||
dataFrom:
|
||||
- extract:
|
||||
key: core/prod/accounts
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: console-env
|
||||
@ -13,4 +13,3 @@ spec:
|
||||
dataFrom:
|
||||
- extract:
|
||||
key: core/pre/console
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: console-env
|
||||
@ -13,4 +13,3 @@ spec:
|
||||
dataFrom:
|
||||
- extract:
|
||||
key: core/prod/console
|
||||
|
||||
|
||||
@ -13,4 +13,4 @@ spec:
|
||||
name: platform-config
|
||||
path: ./infra/infrastructure
|
||||
dependsOn:
|
||||
- name: platform-secrets-stack
|
||||
- name: platform-stack
|
||||
|
||||
@ -3,8 +3,6 @@ kind: Kustomization
|
||||
resources:
|
||||
- namespaces.yaml
|
||||
- platform-kustomization.yaml
|
||||
- platform-secrets-kustomization.yaml
|
||||
- platform-services-kustomization.yaml
|
||||
- infrastructure-kustomization.yaml
|
||||
- observability-kustomization.yaml
|
||||
- console-prod-kustomization.yaml
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,4 +1,4 @@
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: postgresql-auth
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: postgresql-stunnel-server
|
||||
|
||||
@ -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
|
||||
@ -1,4 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- clustersecretstore.yaml
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -1,7 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: platform
|
||||
resources:
|
||||
- externalsecret.yaml
|
||||
- helmrelease.yaml
|
||||
|
||||
@ -7,7 +7,7 @@ spec:
|
||||
interval: 10m0s
|
||||
url: oci://ghcr.io/x-evor/k3s-platform-chart
|
||||
ref:
|
||||
semver: "0.1.3"
|
||||
semver: "0.1.4"
|
||||
layerSelector:
|
||||
mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
|
||||
operation: copy
|
||||
|
||||
@ -73,6 +73,59 @@ components:
|
||||
type: roundrobin
|
||||
nodes:
|
||||
"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:
|
||||
enabled: true
|
||||
releaseName: vault
|
||||
|
||||
@ -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
|
||||
@ -1,5 +0,0 @@
|
||||
ansible_port: 22
|
||||
ansible_ssh_user: ubuntu
|
||||
ansible_ssh_private_key_file: ~/.ssh/id_rsa
|
||||
ansible_host_key_checking: False
|
||||
|
||||
@ -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
|
||||
@ -1,2 +0,0 @@
|
||||
[vpn-gateway]
|
||||
xproxy.onwalk.net ansible_host=43.206.158.21
|
||||
@ -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
|
||||
@ -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.
|
||||
@ -1,5 +0,0 @@
|
||||
---
|
||||
haproxy_conf_dir: /etc/haproxy
|
||||
haproxy_user: root
|
||||
haproxy_group: root
|
||||
haproxy_reload_cmd: /etc/haproxy/scripts/reload.sh
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,2 +0,0 @@
|
||||
backend bk_blackhole
|
||||
server reject 127.0.0.1:1
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,5 +0,0 @@
|
||||
listen stats
|
||||
bind :8404
|
||||
stats enable
|
||||
stats uri /stats
|
||||
stats refresh 5s
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,3 +0,0 @@
|
||||
# Map SNI to certificate filenames when terminating TLS locally.
|
||||
# <sni> <certificate-file>
|
||||
# example.com example-com.pem
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,4 +0,0 @@
|
||||
---
|
||||
- name: reload haproxy
|
||||
ansible.builtin.command: "{{ haproxy_reload_cmd }}"
|
||||
listen: reload haproxy
|
||||
@ -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: []
|
||||
@ -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
|
||||
@ -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: ""
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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: ""
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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 }}"
|
||||
|
||||
@ -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 %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user