From a06e5e1a0049405ddcad94ebe6319ec77619c745 Mon Sep 17 00:00:00 2001 From: Houzhong Xu Date: Sat, 27 Sep 2025 08:04:23 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=81=E7=A7=BB=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E8=AE=BE=E6=96=BD=E5=88=B0Nomad=E5=92=8CPodman=E5=B9=B6?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 更新Ansible Playbooks以支持Nomad集群 docs: 更新文档反映从Docker Swarm到Nomad的迁移 ci: 更新Gitea工作流以支持Podman构建 test: 添加Nomad作业测试文件 build: 更新Makefile以支持Podman操作 chore: 清理旧的Docker Swarm相关文件和配置 --- .../51b1534384b9a4ce15f30dca0c728ade.png | Bin 0 -> 96001 bytes .gitea/settings.yml | 2 +- .gitea/workflows/docker.yml | 31 +- Makefile | 20 +- README.md | 35 +- configs/nomad-ash3c.hcl | 47 +++ configs/nomad-master.hcl | 47 +++ .../inventories/production/consul-cluster.ini | 23 +- .../inventories/production/consul-nodes.ini | 2 + .../inventories/production/inventory.ini | 13 +- .../inventories/production/master-ash3c.ini | 6 + configuration/pipefail | 394 ++++++++++++++++++ .../playbooks/check-security-logs.yml | 14 - .../playbooks/cleanup-hashicorp-backups.yml | 22 + .../configure-nomad-podman-cluster.yml | 2 +- .../playbooks/debug-cgroup-permissions.yml | 33 -- .../playbooks/debug-nomad-cgroup.yml | 14 - .../playbooks/debug-nomad-germany.yml | 24 -- .../playbooks/distribute-podman-germany.yml | 12 - .../playbooks/find-nomad-service.yml | 14 - .../playbooks/fix-cgroup-permissions.yml | 19 - .../playbooks/fix-hashicorp-apt-source.yml | 9 +- configuration/playbooks/install-consul.yml | 68 +++ .../playbooks/install-nomad-podman-driver.yml | 2 +- .../playbooks/manual-run-nomad-germany.yml | 22 - .../playbooks/read-nomad-config-germany.yml | 12 - docs/consul-cluster-troubleshooting.md | 41 +- docs/setup/consul-terraform-integration.md | 8 +- jobs/consul-cluster-arm64.nomad | 87 ++++ jobs/consul-cluster-binary.nomad | 88 ++++ .../consul-cluster-nomad.nomad | 4 +- jobs/consul-cluster-simple.nomad | 157 +++++++ jobs/consul-cluster-three-nodes.nomad | 190 +++++++++ .../consul-cluster.nomad | 0 jobs/consul-single-member-new.nomad | 47 +++ jobs/consul-single-member.nomad | 47 +++ jobs/consul-test-warden.nomad | 46 ++ jobs/consul-warden-only.nomad | 46 ++ .../install-podman-driver.nomad | 0 jobs/service-discovery-warden.nomad | 46 ++ jobs/simple-consul-warden.nomad | 52 +++ test-job.nomad => jobs/test-job.nomad | 0 jobs/test-podman-job.nomad | 24 ++ jobs/test-podman-simple.nomad | 23 + jobs/test-private-registry.nomad | 31 ++ jobs/test-simple.nomad | 27 ++ key.md | 61 --- playbooks/add-beijing-node-prefix.yml | 69 +++ playbooks/fix-duplicate-plugin-dir.yml | 56 +++ playbooks/fix-podman-driver-config.yml | 112 +++++ playbooks/fix-warden-nfs.yml | 46 ++ playbooks/setup-nfs-storage.yml | 75 ++++ scripts/utilities/cleanup-retired-nodes.sh | 69 +++ .../utilities/purge_stale_nodes.sh | 0 54 files changed, 2010 insertions(+), 329 deletions(-) create mode 100644 .codebuddy/.ignored_image/51b1534384b9a4ce15f30dca0c728ade.png create mode 100644 configs/nomad-ash3c.hcl create mode 100644 configs/nomad-master.hcl create mode 100644 configuration/inventories/production/master-ash3c.ini create mode 100644 configuration/pipefail delete mode 100644 configuration/playbooks/check-security-logs.yml create mode 100644 configuration/playbooks/cleanup-hashicorp-backups.yml delete mode 100644 configuration/playbooks/debug-cgroup-permissions.yml delete mode 100644 configuration/playbooks/debug-nomad-cgroup.yml delete mode 100644 configuration/playbooks/debug-nomad-germany.yml delete mode 100644 configuration/playbooks/distribute-podman-germany.yml delete mode 100644 configuration/playbooks/find-nomad-service.yml delete mode 100644 configuration/playbooks/fix-cgroup-permissions.yml create mode 100644 configuration/playbooks/install-consul.yml delete mode 100644 configuration/playbooks/manual-run-nomad-germany.yml delete mode 100644 configuration/playbooks/read-nomad-config-germany.yml create mode 100644 jobs/consul-cluster-arm64.nomad create mode 100644 jobs/consul-cluster-binary.nomad rename consul-cluster-nomad.nomad => jobs/consul-cluster-nomad.nomad (94%) create mode 100644 jobs/consul-cluster-simple.nomad create mode 100644 jobs/consul-cluster-three-nodes.nomad rename consul-cluster.nomad => jobs/consul-cluster.nomad (100%) create mode 100644 jobs/consul-single-member-new.nomad create mode 100644 jobs/consul-single-member.nomad create mode 100644 jobs/consul-test-warden.nomad create mode 100644 jobs/consul-warden-only.nomad rename install-podman-driver.nomad => jobs/install-podman-driver.nomad (100%) create mode 100644 jobs/service-discovery-warden.nomad create mode 100644 jobs/simple-consul-warden.nomad rename test-job.nomad => jobs/test-job.nomad (100%) create mode 100644 jobs/test-podman-job.nomad create mode 100644 jobs/test-podman-simple.nomad create mode 100644 jobs/test-private-registry.nomad create mode 100644 jobs/test-simple.nomad delete mode 100644 key.md create mode 100644 playbooks/add-beijing-node-prefix.yml create mode 100644 playbooks/fix-duplicate-plugin-dir.yml create mode 100644 playbooks/fix-podman-driver-config.yml create mode 100644 playbooks/fix-warden-nfs.yml create mode 100644 playbooks/setup-nfs-storage.yml create mode 100644 scripts/utilities/cleanup-retired-nodes.sh rename purge_stale_nodes.sh => scripts/utilities/purge_stale_nodes.sh (100%) diff --git a/.codebuddy/.ignored_image/51b1534384b9a4ce15f30dca0c728ade.png b/.codebuddy/.ignored_image/51b1534384b9a4ce15f30dca0c728ade.png new file mode 100644 index 0000000000000000000000000000000000000000..7904d5ee11c546a400374d2ffebf06984abb1754 GIT binary patch literal 96001 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 cmpA/dev/null || true - @docker system prune -f + @podman system prune -f # 备份 backup: ## 创建备份 @@ -80,7 +80,7 @@ backup: ## 创建备份 # 监控 monitor: ## 启动监控 @echo "📊 启动监控..." - @docker-compose -f containers/compose/production/monitoring.yml up -d + @podman-compose -f containers/compose/production/monitoring.yml up -d # 安全扫描 security-scan: ## 安全扫描 diff --git a/README.md b/README.md index 83a8836..cc7ceb1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # 🏗️ 基础设施管理项目 -这是一个现代化的多云基础设施管理平台,专注于 OpenTofu、Ansible 和 Docker Swarm 的集成管理。 +这是一个现代化的多云基础设施管理平台,专注于 OpenTofu、Ansible 和 Nomad + Podman 的集成管理。 ## 🎯 项目特性 - **🌩️ 多云支持**: Oracle Cloud, 华为云, Google Cloud, AWS, DigitalOcean - **🏗️ 基础设施即代码**: 使用 OpenTofu 管理云资源 - **⚙️ 配置管理**: 使用 Ansible 自动化配置和部署 -- **🐳 容器编排**: Docker Swarm 集群管理和服务编排 +- **🐳 容器编排**: Nomad 集群管理和 Podman 容器运行时 - **🔄 CI/CD**: Gitea Actions 自动化流水线 - **📊 监控**: Prometheus + Grafana 监控体系 - **🔐 安全**: 多层安全防护和合规性 @@ -22,10 +22,6 @@ mgmt/ │ ├── modules/ # 可复用模块 │ ├── providers/ # 云服务商配置 │ └── shared/ # 共享配置 -├── swarm/ # Docker Swarm 配置 -│ ├── stacks/ # Docker Stack 配置文件 -│ ├── configs/ # Traefik 等基础设施配置 -│ └── scripts/ # Swarm 管理脚本 ├── configuration/ # Ansible 配置管理 │ ├── inventories/ # 主机清单 │ ├── playbooks/ # 剧本 @@ -39,6 +35,8 @@ mgmt/ └── Makefile # 项目管理命令 ``` +**注意:** 项目已从 Docker Swarm 迁移到 Nomad + Podman,原有的 swarm 目录已不再使用。 + ## 🚀 快速开始 ### 1. 环境准备 @@ -78,17 +76,17 @@ vim tofu/environments/dev/terraform.tfvars cd tofu/environments/dev && tofu apply ``` -### 4. 部署 Docker Swarm 服务 +### 4. 部署 Nomad 服务 ```bash -# 初始化 Docker Swarm -./mgmt.sh swarm init +# 部署 Consul 集群 +nomad run /root/mgmt/consul-cluster-nomad.nomad -# 部署 Traefik 反向代理 -./mgmt.sh swarm deploy traefik swarm/stacks/traefik-swarm-stack.yml +# 查看 Nomad 任务 +nomad job status -# 部署示例服务 -./mgmt.sh swarm deploy demo swarm/stacks/demo-services-stack.yml +# 查看节点状态 +nomad node status ``` ## 🛠️ 常用命令 @@ -98,9 +96,10 @@ cd tofu/environments/dev && tofu apply | `./mgmt.sh status` | 显示项目状态总览 | | `./mgmt.sh deploy` | 快速部署所有服务 | | `./mgmt.sh cleanup` | 清理所有部署的服务 | -| `./mgmt.sh swarm ` | Docker Swarm 管理命令 | | `./mgmt.sh tofu ` | OpenTofu 管理命令 | -| `swarm/scripts/swarm-manager.sh help` | Swarm 管理帮助 | +| `nomad job status` | 查看 Nomad 任务状态 | +| `nomad node status` | 查看 Nomad 节点状态 | +| `podman ps` | 查看运行中的容器 | | `scripts/setup/setup-opentofu.sh help` | OpenTofu 设置帮助 | ## 🌩️ 支持的云服务商 @@ -145,10 +144,10 @@ cd tofu/environments/dev && tofu apply 5. **Ansible 部署** → 配置和部署应用 ### 应用部署流程 -1. **应用代码更新** → 构建 Docker 镜像 +1. **应用代码更新** → 构建容器镜像 2. **镜像推送** → 推送到镜像仓库 -3. **Compose 更新** → 更新服务定义 -4. **Swarm 部署** → 滚动更新服务 +3. **Nomad Job 更新** → 更新任务定义 +4. **Nomad 部署** → 滚动更新服务 5. **健康检查** → 验证部署状态 ## 📊 监控和可观测性 diff --git a/configs/nomad-ash3c.hcl b/configs/nomad-ash3c.hcl new file mode 100644 index 0000000..97c54de --- /dev/null +++ b/configs/nomad-ash3c.hcl @@ -0,0 +1,47 @@ +datacenter = "dc1" +data_dir = "/opt/nomad/data" +plugin_dir = "/opt/nomad/plugins" +log_level = "INFO" + +bind_addr = "100.116.80.94" + +addresses { + http = "100.116.80.94" + rpc = "100.116.80.94" + serf = "100.116.80.94" +} + +ports { + http = 4646 + rpc = 4647 + serf = 4648 +} + +server { + enabled = false +} + +client { + enabled = true + network_interface = "tailscale0" + + servers = [ + "100.116.158.95:4647", # semaphore + "100.103.147.94:4647", # ash2e + "100.81.26.3:4647", # ash1d + "100.90.159.68:4647" # ch2 + ] +} + +plugin "nomad-driver-podman" { + config { + socket_path = "unix:///run/podman/podman.sock" + volumes { + enabled = true + } + } +} + +consul { + address = "100.116.80.94:8500" +} \ No newline at end of file diff --git a/configs/nomad-master.hcl b/configs/nomad-master.hcl new file mode 100644 index 0000000..4f312f4 --- /dev/null +++ b/configs/nomad-master.hcl @@ -0,0 +1,47 @@ +datacenter = "dc1" +data_dir = "/opt/nomad/data" +plugin_dir = "/opt/nomad/plugins" +log_level = "INFO" + +bind_addr = "100.117.106.136" + +addresses { + http = "100.117.106.136" + rpc = "100.117.106.136" + serf = "100.117.106.136" +} + +ports { + http = 4646 + rpc = 4647 + serf = 4648 +} + +server { + enabled = false +} + +client { + enabled = true + network_interface = "tailscale0" + + servers = [ + "100.116.158.95:4647", # semaphore + "100.103.147.94:4647", # ash2e + "100.81.26.3:4647", # ash1d + "100.90.159.68:4647" # ch2 + ] +} + +plugin "nomad-driver-podman" { + config { + socket_path = "unix:///run/podman/podman.sock" + volumes { + enabled = true + } + } +} + +consul { + address = "100.117.106.136:8500" +} \ No newline at end of file diff --git a/configuration/inventories/production/consul-cluster.ini b/configuration/inventories/production/consul-cluster.ini index 5e82382..549c09a 100644 --- a/configuration/inventories/production/consul-cluster.ini +++ b/configuration/inventories/production/consul-cluster.ini @@ -1,10 +1,23 @@ -[consul_cluster] +# Consul 集群 Inventory - 三节点配置 +[consul_servers] master ansible_host=master ansible_port=60022 ansible_user=ben ansible_become=yes ansible_become_pass=3131 ash3c ansible_host=ash3c ansible_user=ben ansible_become=yes ansible_become_pass=3131 +warden ansible_host=warden ansible_user=ben ansible_become=yes ansible_become_pass=3131 -[consul_cluster:vars] +[consul_cluster:children] +consul_servers + +[consul_servers:vars] ansible_ssh_common_args='-o StrictHostKeyChecking=no' -consul_version=1.21.4 +consul_version=1.21.5 consul_datacenter=dc1 -# 生成加密密钥: consul keygen -vault_consul_encrypt_key=1EvGItLOB8nuHnSA0o+rO0zXzLeJl+U+Jfvuw0+H848= \ No newline at end of file +consul_encrypt_key=1EvGItLOB8nuHnSA0o+rO0zXzLeJl+U+Jfvuw0+H848= +consul_bootstrap_expect=3 +consul_server=true +consul_ui_config=true +consul_client_addr=0.0.0.0 +consul_bind_addr="{{ ansible_default_ipv4.address }}" +consul_data_dir=/opt/consul/data +consul_config_dir=/etc/consul.d +consul_log_level=INFO +consul_port=8500 \ No newline at end of file diff --git a/configuration/inventories/production/consul-nodes.ini b/configuration/inventories/production/consul-nodes.ini index 79a4e49..a8b30fc 100644 --- a/configuration/inventories/production/consul-nodes.ini +++ b/configuration/inventories/production/consul-nodes.ini @@ -1,6 +1,8 @@ [consul_servers] master ansible_host=100.117.106.136 ansible_user=ben ansible_become=yes ansible_become_pass=3131 ash3c ansible_host=100.116.80.94 ansible_user=ben ansible_become=yes ansible_become_pass=3131 +semaphore ansible_host=100.116.158.95 ansible_user=ben ansible_become=yes ansible_become_pass=3131 +# hcs节点将在一个月后退役 hcs ansible_host=100.84.197.26 ansible_user=ben ansible_become=yes ansible_become_pass=3131 [consul_servers:vars] diff --git a/configuration/inventories/production/inventory.ini b/configuration/inventories/production/inventory.ini index a4114b5..517f056 100644 --- a/configuration/inventories/production/inventory.ini +++ b/configuration/inventories/production/inventory.ini @@ -16,11 +16,16 @@ ash3c ansible_host=ash3c ansible_user=ben ansible_become=yes ansible_become_pass [huawei] -hcs ansible_host=hcs ansible_user=ben ansible_become=yes ansible_become_pass=3131 +# hcs 节点已退役 (2025-09-27) [google] benwork ansible_host=benwork ansible_user=ben ansible_become=yes ansible_become_pass=3131 [ditigalocean] +# syd ansible_host=syd ansible_user=ben ansible_become=yes ansible_become_pass=3131 # 故障节点,已隔离 + +[faulty_cloud_servers] +# 故障的云服务器节点,需要通过 OpenTofu 和 Consul 解决 +# hcs 节点已退役 (2025-09-27) syd ansible_host=syd ansible_user=ben ansible_become=yes ansible_become_pass=3131 [aws] @@ -42,7 +47,7 @@ postgresql ansible_host=postgresql ansible_user=root ansible_become=yes ansible_ influxdb ansible_host=influxdb1 ansible_user=root ansible_become=yes ansible_become_pass=313131 warden ansible_host=warden ansible_user=ben ansible_become=yes ansible_become_pass=3131 [semaphore] -semaphoressh ansible_host=semaphore ansible_user=root ansible_become=yes ansible_become_pass=313131 +semaphoressh ansible_host=localhost ansible_user=root ansible_become=yes ansible_become_pass=313131 ansible_ssh_pass=313131 [alpine] #Alpine Linux containers using apk package manager @@ -63,9 +68,6 @@ snail ansible_host=snail ansible_user=houzhongxu ansible_ssh_pass=Aa313131@ben a [armbian] onecloud1 ansible_host=onecloud1 ansible_user=ben ansible_ssh_pass=3131 ansible_become=yes ansible_become_pass=3131 -[germany] -de ansible_host=de ansible_user=ben ansible_ssh_pass=3131 ansible_become=yes ansible_become_pass=3131 - [beijing:children] nomadlxc hcp @@ -79,7 +81,6 @@ hcp oci_a1 huawei ditigalocean -germany [nomad_servers:children] oci_us oci_kr diff --git a/configuration/inventories/production/master-ash3c.ini b/configuration/inventories/production/master-ash3c.ini new file mode 100644 index 0000000..770a08f --- /dev/null +++ b/configuration/inventories/production/master-ash3c.ini @@ -0,0 +1,6 @@ +[target_nodes] +master ansible_host=master ansible_port=60022 ansible_user=ben ansible_become=yes ansible_become_pass=3131 +ash3c ansible_host=ash3c ansible_user=ben ansible_become=yes ansible_become_pass=3131 + +[target_nodes:vars] +ansible_ssh_common_args='-o StrictHostKeyChecking=no' \ No newline at end of file diff --git a/configuration/pipefail b/configuration/pipefail new file mode 100644 index 0000000..2950718 --- /dev/null +++ b/configuration/pipefail @@ -0,0 +1,394 @@ +'!'=3491531 +'#'=0 +'$'=3491516 +'*'=( ) +'?'=0 +-=569JNRXghiks +0=/usr/bin/zsh +@=( ) +ADDR=( 'PATH=/root/.trae-server/sdks/workspaces/cced0550/versions/node/current\x3a/root/.trae-server/sdks/versions/node/current\x3a' ) +AGNOSTER_AWS_BG=green +AGNOSTER_AWS_FG=black +AGNOSTER_AWS_PROD_BG=red +AGNOSTER_AWS_PROD_FG=yellow +AGNOSTER_BZR_CLEAN_BG=green +AGNOSTER_BZR_CLEAN_FG=black +AGNOSTER_BZR_DIRTY_BG=yellow +AGNOSTER_BZR_DIRTY_FG=black +AGNOSTER_CONTEXT_BG=black +AGNOSTER_CONTEXT_FG=default +AGNOSTER_DIR_BG=blue +AGNOSTER_DIR_FG=black +AGNOSTER_GIT_BRANCH_STATUS=true +AGNOSTER_GIT_CLEAN_BG=green +AGNOSTER_GIT_CLEAN_FG=black +AGNOSTER_GIT_DIRTY_BG=yellow +AGNOSTER_GIT_DIRTY_FG=black +AGNOSTER_GIT_INLINE=false +AGNOSTER_HG_CHANGED_BG=yellow +AGNOSTER_HG_CHANGED_FG=black +AGNOSTER_HG_CLEAN_BG=green +AGNOSTER_HG_CLEAN_FG=black +AGNOSTER_HG_NEWFILE_BG=red +AGNOSTER_HG_NEWFILE_FG=white +AGNOSTER_STATUS_BG=black +AGNOSTER_STATUS_FG=default +AGNOSTER_STATUS_JOB_FG=cyan +AGNOSTER_STATUS_RETVAL_FG=red +AGNOSTER_STATUS_RETVAL_NUMERIC=false +AGNOSTER_STATUS_ROOT_FG=yellow +AGNOSTER_VENV_BG=blue +AGNOSTER_VENV_FG=black +ANTHROPIC_AUTH_TOKEN=sk-M0YtRnZNHJkqFP7DjrNsf3jVDe4INKqiBGN0YgQcisudOYbp +ANTHROPIC_BASE_URL=https://anyrouter.top +ARCH=x86_64 +ARGC=0 +BG +BROWSER=/root/.trae-server/bin/stable-0643ffaa788ad4dd46eaa12cec109ac40595c816/bin/helpers/browser.sh +BUFFER='' +CDPATH='' +COLORTERM=truecolor +COLUMNS=104 +CPUTYPE=x86_64 +CURRENT_BG=NONE +CURRENT_DEFAULT_FG=default +CURRENT_FG=black +CURSOR='' +DBUS_SESSION_BUS_ADDRESS='unix:path=/run/user/0/bus' +DISTRO_COMMIT=0643ffaa788ad4dd46eaa12cec109ac40595c816 +DISTRO_QUALITY=stable +DISTRO_VERSION=1.100.3 +DISTRO_VSCODIUM_RELEASE='' +DOWNLOAD_RETRY_COUNT=0 +EDITOR=vim +EGID=0 +EPOCHREALTIME +EPOCHSECONDS +EUID=0 +EXTRACT_RETRY_COUNT=0 +FG +FIGNORE='' +FPATH=/root/.oh-my-zsh/plugins/z:/root/.oh-my-zsh/plugins/web-search:/root/.oh-my-zsh/plugins/vscode:/root/.oh-my-zsh/plugins/tmux:/root/.oh-my-zsh/plugins/systemd:/root/.oh-my-zsh/plugins/sudo:/root/.oh-my-zsh/plugins/history-substring-search:/root/.oh-my-zsh/plugins/extract:/root/.oh-my-zsh/plugins/command-not-found:/root/.oh-my-zsh/plugins/colored-man-pages:/root/.oh-my-zsh/custom/plugins/zsh-completions:/root/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting:/root/.oh-my-zsh/custom/plugins/zsh-autosuggestions:/root/.oh-my-zsh/plugins/gcloud:/root/.oh-my-zsh/plugins/aws:/root/.oh-my-zsh/plugins/helm:/root/.oh-my-zsh/plugins/kubectl:/root/.oh-my-zsh/plugins/terraform:/root/.oh-my-zsh/plugins/ansible:/root/.oh-my-zsh/plugins/docker-compose:/root/.oh-my-zsh/plugins/docker:/root/.oh-my-zsh/plugins/git:/root/.oh-my-zsh/functions:/root/.oh-my-zsh/completions:/root/.oh-my-zsh/custom/functions:/root/.oh-my-zsh/custom/completions:/root/.oh-my-zsh/cache/completions:/usr/local/share/zsh/site-functions:/usr/share/zsh/vendor-functions:/usr/share/zsh/vendor-completions:/usr/share/zsh/functions/Calendar:/usr/share/zsh/functions/Chpwd:/usr/share/zsh/functions/Completion:/usr/share/zsh/functions/Completion/AIX:/usr/share/zsh/functions/Completion/BSD:/usr/share/zsh/functions/Completion/Base:/usr/share/zsh/functions/Completion/Cygwin:/usr/share/zsh/functions/Completion/Darwin:/usr/share/zsh/functions/Completion/Debian:/usr/share/zsh/functions/Completion/Linux:/usr/share/zsh/functions/Completion/Mandriva:/usr/share/zsh/functions/Completion/Redhat:/usr/share/zsh/functions/Completion/Solaris:/usr/share/zsh/functions/Completion/Unix:/usr/share/zsh/functions/Completion/X:/usr/share/zsh/functions/Completion/Zsh:/usr/share/zsh/functions/Completion/openSUSE:/usr/share/zsh/functions/Exceptions:/usr/share/zsh/functions/MIME:/usr/share/zsh/functions/Math:/usr/share/zsh/functions/Misc:/usr/share/zsh/functions/Newuser:/usr/share/zsh/functions/Prompts:/usr/share/zsh/functions/TCP:/usr/share/zsh/functions/VCS_Info:/usr/share/zsh/functions/VCS_Info/Backends:/usr/share/zsh/functions/Zftp:/usr/share/zsh/functions/Zle:/root/.oh-my-zsh/custom/plugins/zsh-completions/src +FUNCNEST=500 +FX +GID=0 +GIT_ASKPASS=/root/.trae-server/bin/stable-0643ffaa788ad4dd46eaa12cec109ac40595c816/extensions/git/dist/askpass.sh +GIT_PAGER='' +HISTCHARS='!^#' +HISTCMD=1888 +HISTFILE=/root/.zsh_history +HISTORY_SUBSTRING_SEARCH_ENSURE_UNIQUE='' +HISTORY_SUBSTRING_SEARCH_FUZZY='' +HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS=i +HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND='bg=magenta,fg=white,bold' +HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_NOT_FOUND='bg=red,fg=white,bold' +HISTORY_SUBSTRING_SEARCH_PREFIXED='' +HISTSIZE=10000 +HOME=/root +HOST=semaphore +IFS=$' \t\n\C-@' +ITEM='PATH=/root/.trae-server/sdks/workspaces/cced0550/versions/node/current\x3a/root/.trae-server/sdks/versions/node/current\x3a' +KEYBOARD_HACK='' +KEYTIMEOUT=40 +LANG=C.UTF-8 +LANGUAGE=en_US.UTF-8 +LC_ALL=C.UTF-8 +LESS=-R +LINENO=77 +LINES=40 +LISTMAX=100 +LOGNAME=root +LSCOLORS=Gxfxcxdxbxegedabagacad +LS_COLORS='rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=00:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.avif=01;35:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:*~=00;90:*#=00;90:*.bak=00;90:*.old=00;90:*.orig=00;90:*.part=00;90:*.rej=00;90:*.swp=00;90:*.tmp=00;90:*.dpkg-dist=00;90:*.dpkg-old=00;90:*.ucf-dist=00;90:*.ucf-new=00;90:*.ucf-old=00;90:*.rpmnew=00;90:*.rpmorig=00;90:*.rpmsave=00;90:' +MACHTYPE=x86_64 +MAILCHECK=60 +MAILPATH='' +MANAGER_LOGS_DIR=/root/.trae-server/manager-logs/1758782495499_337482 +MANPATH='' +MATCH='' +MBEGIN='' +MEND='' +MODULE_PATH=/usr/lib/x86_64-linux-gnu/zsh/5.9 +MOTD_SHOWN=pam +NEWLINE=$'\n' +NOMAD_ADDR=http://100.81.26.3:4646 +NULLCMD=cat +OLDPWD=/root/mgmt +OPTARG='' +OPTIND=1 +OSTYPE=linux-gnu +OS_RELEASE_ID=debian +PAGER='' +PATH=/root/.trae-server/sdks/workspaces/cced0550/versions/node/current:/root/.trae-server/sdks/versions/node/current:/root/.trae-server/sdks/workspaces/cced0550/versions/node/current:/root/.trae-server/sdks/versions/node/current:/root/.trae-server/bin/stable-0643ffaa788ad4dd46eaa12cec109ac40595c816/bin/remote-cli:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +PLATFORM=linux +POWERLEVEL9K_INSTANT_PROMPT=off +PPID=2438215 +PROMPT2='%_> ' +PROMPT3='?# ' +PROMPT4='+%N:%i> ' +PROMPT=$'\n(TraeAI-3) %~ [%?] $ ' +PS1=$'\n(TraeAI-3) %~ [%?] $ ' +PS2='%_> ' +PS3='?# ' +PS4='+%N:%i> ' +PSVAR='' +PWD=/root/mgmt/configuration +RANDOM=6724 +READNULLCMD=/usr/bin/pager +REMOTE_VERSION=1058011568130_8 +SAVEHIST=10000 +SCRIPT_ID=f227a05726be7c5a36752917 +SECONDS=1076 +SEGMENT_SEPARATOR= +SERVER_APP_NAME=Trae +SERVER_APP_QUALITY=dev +SERVER_APP_VERSION='' +SERVER_ARCH=x64 +SERVER_DATA_DIR=/root/.trae-server +SERVER_DIR=/root/.trae-server/bin/stable-0643ffaa788ad4dd46eaa12cec109ac40595c816 +SERVER_DOWNLOAD_PREFIX=https://lf-cdn.trae.com.cn/obj/trae-com-cn/pkg/server/releases/stable/0643ffaa788ad4dd46eaa12cec109ac40595c816/linux/ +SERVER_EXTENSIONS_DIR=/root/.trae-server/extensions +SERVER_HOST=127.0.0.1 +SERVER_INITIAL_EXTENSIONS='--install-extension gitpod.gitpod-remote-ssh' +SERVER_LISTEN_FLAG='--port=0' +SERVER_LOGFILE=/root/.trae-server/.stable-0643ffaa788ad4dd46eaa12cec109ac40595c816.log +SERVER_LOGS_DIR=/root/.trae-server/logs +SERVER_PACKAGE_NAME=Trae-linux-x64-1058011568130_8.tar.gz +SERVER_PIDFILE=/root/.trae-server/.stable-0643ffaa788ad4dd46eaa12cec109ac40595c816.pid +SERVER_SCRIPT=/root/.trae-server/bin/stable-0643ffaa788ad4dd46eaa12cec109ac40595c816/index_trae.js +SERVER_SCRIPT_PRODUCT=/root/.trae-server/bin/stable-0643ffaa788ad4dd46eaa12cec109ac40595c816/product.json +SERVER_TOKENFILE=/root/.trae-server/.stable-0643ffaa788ad4dd46eaa12cec109ac40595c816.token +SHELL=/usr/bin/zsh +SHLVL=2 +SHORT_HOST=semaphore +SPROMPT='zsh: correct '\''%R'\'' to '\''%r'\'' [nyae]? ' +SSH_CLIENT='100.86.9.29 49793 22' +SSH_CONNECTION='100.86.9.29 49793 100.116.158.95 22' +TERM=xterm-256color +TERM_PRODUCT=Trae +TERM_PROGRAM=vscode +TERM_PROGRAM_VERSION=1.100.3 +TIMEFMT='%J %U user %S system %P cpu %*E total' +TMPPREFIX=/tmp/zsh +TMP_DIR=/run/user/0 +TRAE_AI_SHELL_ID=3 +TRAE_DETECT_REGION=CN +TRAE_REMOTE_EXTENSION_REGION=cn +TRAE_REMOTE_SKIP_REMOTE_CHECK='' +TRAE_RESOLVE_TYPE=ssh +TRY_BLOCK_ERROR=-1 +TRY_BLOCK_INTERRUPT=-1 +TTY=/dev/pts/8 +TTYIDLE=-1 +UID=0 +USER=root +USERNAME=root +USER_ZDOTDIR=/root +VARNAME=PATH +VENDOR=debian +VSCODE_GIT_ASKPASS_EXTRA_ARGS='' +VSCODE_GIT_ASKPASS_MAIN=/root/.trae-server/bin/stable-0643ffaa788ad4dd46eaa12cec109ac40595c816/extensions/git/dist/askpass-main.js +VSCODE_GIT_ASKPASS_NODE=/root/.trae-server/bin/stable-0643ffaa788ad4dd46eaa12cec109ac40595c816/node +VSCODE_GIT_IPC_HANDLE=/run/user/0/vscode-git-7caaecb415.sock +VSCODE_INJECTION=1 +VSCODE_IPC_HOOK_CLI=/run/user/0/vscode-ipc-47deaf2b-9a7a-4554-a972-d6b4aa5bb388.sock +VSCODE_SHELL_INTEGRATION=1 +VSCODE_STABLE='' +VSCODE_ZDOTDIR=/tmp/root-trae-zsh +WATCH +WORDCHARS='' +XDG_RUNTIME_DIR=/run/user/0 +XDG_SESSION_CLASS=user +XDG_SESSION_ID=1636 +XDG_SESSION_TYPE=tty +ZDOTDIR=/root +ZSH=/root/.oh-my-zsh +ZSHZ=( [CHOWN]=zf_chown [DIRECTORY_REMOVED]=0 [FUNCTIONS]=$'_zshz_usage\n _zshz_add_or_remove_path\n _zshz_update_datafile\n _zshz_legacy_complete\n _zshz_printv\n _zshz_find_common_root\n _zshz_output\n _zshz_find_matches\n zshz\n _zshz_precmd\n _zshz_chpwd\n _zshz' [MV]=zf_mv [PRINTV]=1 [RM]=zf_rm [USE_FLOCK]=1 ) +ZSHZ_EXCLUDE_DIRS=( ) +ZSH_ARGZERO=/usr/bin/zsh +ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( forward-char end-of-line vi-forward-char vi-end-of-line vi-add-eol ) +ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( history-search-forward history-search-backward history-beginning-search-forward history-beginning-search-backward history-beginning-search-forward-end history-beginning-search-backward-end history-substring-search-up history-substring-search-down up-line-or-beginning-search down-line-or-beginning-search up-line-or-history down-line-or-history accept-line copy-earlier-word ) +ZSH_AUTOSUGGEST_COMPLETIONS_PTY_NAME=zsh_autosuggest_completion_pty +ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=( ) +ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=8' +ZSH_AUTOSUGGEST_IGNORE_WIDGETS=( 'orig-*' beep run-help set-local-history which-command yank yank-pop 'zle-*' ) +ZSH_AUTOSUGGEST_ORIGINAL_WIDGET_PREFIX=autosuggest-orig- +ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( forward-word emacs-forward-word vi-forward-word vi-forward-word-end vi-forward-blank-word vi-forward-blank-word-end vi-find-next-char vi-find-next-char-skip ) +ZSH_AUTOSUGGEST_STRATEGY=( history completion ) +ZSH_AUTOSUGGEST_USE_ASYNC='' +ZSH_CACHE_DIR=/root/.oh-my-zsh/cache +ZSH_COMPDUMP=/root/.zcompdump-semaphore-5.9 +ZSH_CUSTOM=/root/.oh-my-zsh/custom +ZSH_EVAL_CONTEXT=toplevel +ZSH_HIGHLIGHT_DIRS_BLACKLIST=( ) +ZSH_HIGHLIGHT_HIGHLIGHTERS=( main brackets pattern cursor ) +ZSH_HIGHLIGHT_PATTERNS=( ) +ZSH_HIGHLIGHT_REGEXP=( ) +ZSH_HIGHLIGHT_REVISION=HEAD +ZSH_HIGHLIGHT_STYLES=( [arg0]='fg=green' [assign]=none [autodirectory]='fg=green,underline' [back-dollar-quoted-argument]='fg=cyan' [back-double-quoted-argument]='fg=cyan' [back-quoted-argument]=none [back-quoted-argument-delimiter]='fg=magenta' [bracket-error]='fg=red,bold' [bracket-level-1]='fg=blue,bold' [bracket-level-2]='fg=green,bold' [bracket-level-3]='fg=magenta,bold' [bracket-level-4]='fg=yellow,bold' [bracket-level-5]='fg=cyan,bold' [command-substitution]=none [command-substitution-delimiter]='fg=magenta' [commandseparator]=none [comment]='fg=black,bold' [cursor]=standout [cursor-matchingbracket]=standout [default]=none [dollar-double-quoted-argument]='fg=cyan' [dollar-quoted-argument]='fg=yellow' [double-hyphen-option]=none [double-quoted-argument]='fg=yellow' [global-alias]='fg=cyan' [globbing]='fg=blue' [history-expansion]='fg=blue' [line]='' [named-fd]=none [numeric-fd]=none [path]=underline [path_pathseparator]='' [path_prefix_pathseparator]='' [precommand]='fg=green,underline' [process-substitution]=none [process-substitution-delimiter]='fg=magenta' [rc-quote]='fg=cyan' [redirection]='fg=yellow' [reserved-word]='fg=yellow' [root]=standout [single-hyphen-option]=none [single-quoted-argument]='fg=yellow' [suffix-alias]='fg=green,underline' [unknown-token]='fg=red,bold' ) +ZSH_HIGHLIGHT_VERSION=0.8.1-dev +ZSH_NAME=zsh +ZSH_PATCHLEVEL=debian/5.9-4+b7 +ZSH_SUBSHELL=1 +ZSH_THEME=agnoster +ZSH_THEME_GIT_PROMPT_CLEAN='' +ZSH_THEME_GIT_PROMPT_DIRTY='*' +ZSH_THEME_GIT_PROMPT_PREFIX='git:(' +ZSH_THEME_GIT_PROMPT_SUFFIX=')' +ZSH_THEME_RUBY_PROMPT_PREFIX='(' +ZSH_THEME_RUBY_PROMPT_SUFFIX=')' +ZSH_THEME_RVM_PROMPT_OPTIONS='i v g' +ZSH_THEME_TERM_TAB_TITLE_IDLE='%15<..<%~%<<' +ZSH_THEME_TERM_TITLE_IDLE='%n@%m:%~' +ZSH_TMUX_AUTOCONNECT=true +ZSH_TMUX_AUTONAME_SESSION=false +ZSH_TMUX_AUTOQUIT=false +ZSH_TMUX_AUTOREFRESH=false +ZSH_TMUX_AUTOSTART=false +ZSH_TMUX_AUTOSTART_ONCE=true +ZSH_TMUX_CONFIG=/root/.tmux.conf +ZSH_TMUX_DETACHED=false +ZSH_TMUX_FIXTERM=true +ZSH_TMUX_FIXTERM_WITHOUT_256COLOR=screen +ZSH_TMUX_FIXTERM_WITH_256COLOR=screen-256color +ZSH_TMUX_ITERM2=false +ZSH_TMUX_TERM=screen-256color +ZSH_TMUX_UNICODE=false +ZSH_VERSION=5.9 +_=set +_OMZ_ASYNC_FDS=( ) +_OMZ_ASYNC_OUTPUT=( ) +_OMZ_ASYNC_PIDS=( ) +_ZSH_AUTOSUGGEST_ASYNC_FD='' +_ZSH_AUTOSUGGEST_BIND_COUNTS=( [accept-and-hold]=1 [accept-and-infer-next-history]=1 [accept-and-menu-complete]=1 [accept-line]=1 [accept-line-and-down-history]=1 [accept-search]=1 [argument-base]=1 [auto-suffix-remove]=1 [auto-suffix-retain]=1 [autosuggest-capture-completion]=1 [backward-char]=1 [backward-delete-char]=1 [backward-delete-word]=1 [backward-kill-line]=1 [backward-kill-word]=1 [backward-word]=1 [beginning-of-buffer-or-history]=1 [beginning-of-history]=1 [beginning-of-line]=1 [beginning-of-line-hist]=1 [bracketed-paste]=1 [capitalize-word]=1 [clear-screen]=1 [complete-word]=1 [copy-prev-shell-word]=1 [copy-prev-word]=1 [copy-region-as-kill]=1 [deactivate-region]=1 [delete-char]=1 [delete-char-or-list]=1 [delete-word]=1 [describe-key-briefly]=1 [digit-argument]=1 [down-case-word]=1 [down-history]=1 [down-line]=1 [down-line-or-beginning-search]=1 [down-line-or-history]=1 [down-line-or-search]=1 [edit-command-line]=1 [emacs-backward-word]=1 [emacs-forward-word]=1 [end-of-buffer-or-history]=1 [end-of-history]=1 [end-of-line]=1 [end-of-line-hist]=1 [end-of-list]=1 [exchange-point-and-mark]=1 [execute-last-named-cmd]=1 [execute-named-cmd]=1 [expand-cmd-path]=1 [expand-history]=1 [expand-or-complete]=1 [expand-or-complete-prefix]=1 [expand-word]=1 [forward-char]=1 [forward-word]=1 [get-line]=1 [gosmacs-transpose-chars]=1 [history-beginning-search-backward]=1 [history-beginning-search-forward]=1 [history-incremental-pattern-search-backward]=1 [history-incremental-pattern-search-forward]=1 [history-incremental-search-backward]=1 [history-incremental-search-forward]=1 [history-search-backward]=1 [history-search-forward]=1 [history-substring-search-down]=1 [history-substring-search-up]=1 [infer-next-history]=1 [insert-last-word]=1 [kill-buffer]=1 [kill-line]=1 [kill-region]=1 [kill-whole-line]=1 [kill-word]=1 [list-choices]=1 [list-expand]=1 [magic-space]=1 [menu-complete]=1 [menu-expand-or-complete]=1 [menu-select]=1 [neg-argument]=1 [overwrite-mode]=1 [pound-insert]=1 [push-input]=1 [push-line]=1 [push-line-or-edit]=1 [put-replace-selection]=1 [quote-line]=1 [quote-region]=1 [quoted-insert]=1 [read-command]=1 [recursive-edit]=1 [redisplay]=1 [redo]=1 [reset-prompt]=1 [reverse-menu-complete]=1 [select-a-blank-word]=1 [select-a-shell-word]=1 [select-a-word]=1 [select-in-blank-word]=1 [select-in-shell-word]=1 [select-in-word]=1 [self-insert]=1 [self-insert-unmeta]=1 [send-break]=1 [set-mark-command]=1 [spell-word]=1 [split-undo]=1 [sudo-command-line]=1 [transpose-chars]=1 [transpose-words]=1 [undefined-key]=1 [undo]=1 [universal-argument]=1 [up-case-word]=1 [up-history]=1 [up-line]=1 [up-line-or-beginning-search]=1 [up-line-or-history]=1 [up-line-or-search]=1 [user:zle-line-finish]=1 [vi-add-eol]=1 [vi-add-next]=1 [vi-backward-blank-word]=1 [vi-backward-blank-word-end]=1 [vi-backward-char]=1 [vi-backward-delete-char]=1 [vi-backward-kill-word]=1 [vi-backward-word]=1 [vi-backward-word-end]=1 [vi-beginning-of-line]=1 [vi-caps-lock-panic]=1 [vi-change]=1 [vi-change-eol]=1 [vi-change-whole-line]=1 [vi-cmd-mode]=1 [vi-delete]=1 [vi-delete-char]=1 [vi-digit-or-beginning-of-line]=1 [vi-down-case]=1 [vi-down-line-or-history]=1 [vi-end-of-line]=1 [vi-fetch-history]=1 [vi-find-next-char]=1 [vi-find-next-char-skip]=1 [vi-find-prev-char]=1 [vi-find-prev-char-skip]=1 [vi-first-non-blank]=1 [vi-forward-blank-word]=1 [vi-forward-blank-word-end]=1 [vi-forward-char]=1 [vi-forward-word]=1 [vi-forward-word-end]=1 [vi-goto-column]=1 [vi-goto-mark]=1 [vi-goto-mark-line]=1 [vi-history-search-backward]=1 [vi-history-search-forward]=1 [vi-indent]=1 [vi-insert]=1 [vi-insert-bol]=1 [vi-join]=1 [vi-kill-eol]=1 [vi-kill-line]=1 [vi-match-bracket]=1 [vi-open-line-above]=1 [vi-open-line-below]=1 [vi-oper-swap-case]=1 [vi-pound-insert]=1 [vi-put-after]=1 [vi-put-before]=1 [vi-quoted-insert]=1 [vi-repeat-change]=1 [vi-repeat-find]=1 [vi-repeat-search]=1 [vi-replace]=1 [vi-replace-chars]=1 [vi-rev-repeat-find]=1 [vi-rev-repeat-search]=1 [vi-set-buffer]=1 [vi-set-mark]=1 [vi-substitute]=1 [vi-swap-case]=1 [vi-undo-change]=1 [vi-unindent]=1 [vi-up-case]=1 [vi-up-line-or-history]=1 [vi-yank]=1 [vi-yank-eol]=1 [vi-yank-whole-line]=1 [visual-line-mode]=1 [visual-mode]=1 [what-cursor-position]=1 [where-is]=1 ) +_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=( clear fetch suggest accept execute enable disable toggle ) +_ZSH_AUTOSUGGEST_CHILD_PID=3538664 +_ZSH_HIGHLIGHT_PRIOR_BUFFER='' +_ZSH_HIGHLIGHT_PRIOR_CURSOR=0 +_ZSH_TMUX_FIXED_CONFIG=/root/.oh-my-zsh/plugins/tmux/tmux.only.conf +__colored_man_pages_dir=/root/.oh-my-zsh/plugins/colored-man-pages +__vsc_current_command='set -o pipefail' +__vsc_env_keys=( ) +__vsc_env_values=( ) +__vsc_in_command_execution=1 +__vsc_nonce=8569aa72-4f06-40f4-a830-4084a537236a +__vsc_prior_prompt2='%_> ' +__vsc_prior_prompt=$'\n(TraeAI-3) %~ [%?] $ ' +__vsc_use_aa=1 +__vscode_shell_env_reporting='' +_comp_assocs=( '' ) +_comp_dumpfile=/root/.zcompdump +_comp_options +_comp_setup +_compautos +_comps +_history_substring_search_match_index=0 +_history_substring_search_matches=( ) +_history_substring_search_query='' +_history_substring_search_query_highlight='' +_history_substring_search_query_parts=( ) +_history_substring_search_raw_match_index=0 +_history_substring_search_raw_matches=( ) +_history_substring_search_refresh_display='' +_history_substring_search_result='' +_history_substring_search_unique_filter=( ) +_history_substring_search_zsh_5_9=1 +_lastcomp +_patcomps +_postpatcomps +_services +_zsh_highlight__highlighter_brackets_cache=( ) +_zsh_highlight__highlighter_cursor_cache=( ) +_zsh_highlight__highlighter_main_cache=( '0 2 fg=green memo=zsh-syntax-highlighting' '3 6 none memo=zsh-syntax-highlighting' '7 11 underline memo=zsh-syntax-highlighting' '11 12 none memo=zsh-syntax-highlighting' '13 17 fg=green memo=zsh-syntax-highlighting' '18 20 none memo=zsh-syntax-highlighting' '21 57 none memo=zsh-syntax-highlighting' '21 57 fg=yellow memo=zsh-syntax-highlighting' '58 62 underline memo=zsh-syntax-highlighting' ) +_zsh_highlight__highlighter_pattern_cache=( ) +_zsh_highlight_main__command_type_cache=( ) +aliases +argv=( ) +bg +bg_bold +bg_no_bold +bold_color +builtins +cdpath=( ) +chpwd_functions=( _zshz_chpwd ) +color=( [00]=none [01]=bold [02]=faint [03]=italic [04]=underline [05]=blink [07]=reverse [08]=conceal [22]=normal [23]=no-italic [24]=no-underline [25]=no-blink [27]=no-reverse [28]=no-conceal [30]=black [31]=red [32]=green [33]=yellow [34]=blue [35]=magenta [36]=cyan [37]=white [39]=default [40]=bg-black [41]=bg-red [42]=bg-green [43]=bg-yellow [44]=bg-blue [45]=bg-magenta [46]=bg-cyan [47]=bg-white [49]=bg-default [bg-black]=40 [bg-blue]=44 [bg-cyan]=46 [bg-default]=49 [bg-gray]=40 [bg-green]=42 [bg-grey]=40 [bg-magenta]=45 [bg-red]=41 [bg-white]=47 [bg-yellow]=43 [black]=30 [blink]=05 [blue]=34 [bold]=01 [conceal]=08 [cyan]=36 [default]=39 [faint]=02 [fg-black]=30 [fg-blue]=34 [fg-cyan]=36 [fg-default]=39 [fg-gray]=30 [fg-green]=32 [fg-grey]=30 [fg-magenta]=35 [fg-red]=31 [fg-white]=37 [fg-yellow]=33 [gray]=30 [green]=32 [grey]=30 [italic]=03 [magenta]=35 [no-blink]=25 [no-conceal]=28 [no-italic]=23 [no-reverse]=27 [no-underline]=24 [none]=00 [normal]=22 [red]=31 [reverse]=07 [underline]=04 [white]=37 [yellow]=33 ) +colour=( [00]=none [01]=bold [02]=faint [03]=italic [04]=underline [05]=blink [07]=reverse [08]=conceal [22]=normal [23]=no-italic [24]=no-underline [25]=no-blink [27]=no-reverse [28]=no-conceal [30]=black [31]=red [32]=green [33]=yellow [34]=blue [35]=magenta [36]=cyan [37]=white [39]=default [40]=bg-black [41]=bg-red [42]=bg-green [43]=bg-yellow [44]=bg-blue [45]=bg-magenta [46]=bg-cyan [47]=bg-white [49]=bg-default [bg-black]=40 [bg-blue]=44 [bg-cyan]=46 [bg-default]=49 [bg-gray]=40 [bg-green]=42 [bg-grey]=40 [bg-magenta]=45 [bg-red]=41 [bg-white]=47 [bg-yellow]=43 [black]=30 [blink]=05 [blue]=34 [bold]=01 [conceal]=08 [cyan]=36 [default]=39 [faint]=02 [fg-black]=30 [fg-blue]=34 [fg-cyan]=36 [fg-default]=39 [fg-gray]=30 [fg-green]=32 [fg-grey]=30 [fg-magenta]=35 [fg-red]=31 [fg-white]=37 [fg-yellow]=33 [gray]=30 [green]=32 [grey]=30 [italic]=03 [magenta]=35 [no-blink]=25 [no-conceal]=28 [no-italic]=23 [no-reverse]=27 [no-underline]=24 [none]=00 [normal]=22 [red]=31 [reverse]=07 [underline]=04 [white]=37 [yellow]=33 ) +commands +comppostfuncs=( ) +compprefuncs=( ) +d=/usr/share/zsh/functions/Zle +debian_missing_features=( ) +dirstack +dis_aliases +dis_builtins +dis_functions +dis_functions_source +dis_galiases +dis_patchars +dis_reswords +dis_saliases +envVarsToReport=( '' ) +epochtime +errnos +fg +fg_bold +fg_no_bold +fignore=( ) +fpath=( /root/.oh-my-zsh/plugins/z /root/.oh-my-zsh/plugins/web-search /root/.oh-my-zsh/plugins/vscode /root/.oh-my-zsh/plugins/tmux /root/.oh-my-zsh/plugins/systemd /root/.oh-my-zsh/plugins/sudo /root/.oh-my-zsh/plugins/history-substring-search /root/.oh-my-zsh/plugins/extract /root/.oh-my-zsh/plugins/command-not-found /root/.oh-my-zsh/plugins/colored-man-pages /root/.oh-my-zsh/custom/plugins/zsh-completions /root/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting /root/.oh-my-zsh/custom/plugins/zsh-autosuggestions /root/.oh-my-zsh/plugins/gcloud /root/.oh-my-zsh/plugins/aws /root/.oh-my-zsh/plugins/helm /root/.oh-my-zsh/plugins/kubectl /root/.oh-my-zsh/plugins/terraform /root/.oh-my-zsh/plugins/ansible /root/.oh-my-zsh/plugins/docker-compose /root/.oh-my-zsh/plugins/docker /root/.oh-my-zsh/plugins/git /root/.oh-my-zsh/functions /root/.oh-my-zsh/completions /root/.oh-my-zsh/custom/functions /root/.oh-my-zsh/custom/completions /root/.oh-my-zsh/cache/completions /usr/local/share/zsh/site-functions /usr/share/zsh/vendor-functions /usr/share/zsh/vendor-completions /usr/share/zsh/functions/Calendar /usr/share/zsh/functions/Chpwd /usr/share/zsh/functions/Completion /usr/share/zsh/functions/Completion/AIX /usr/share/zsh/functions/Completion/BSD /usr/share/zsh/functions/Completion/Base /usr/share/zsh/functions/Completion/Cygwin /usr/share/zsh/functions/Completion/Darwin /usr/share/zsh/functions/Completion/Debian /usr/share/zsh/functions/Completion/Linux /usr/share/zsh/functions/Completion/Mandriva /usr/share/zsh/functions/Completion/Redhat /usr/share/zsh/functions/Completion/Solaris /usr/share/zsh/functions/Completion/Unix /usr/share/zsh/functions/Completion/X /usr/share/zsh/functions/Completion/Zsh /usr/share/zsh/functions/Completion/openSUSE /usr/share/zsh/functions/Exceptions /usr/share/zsh/functions/MIME /usr/share/zsh/functions/Math /usr/share/zsh/functions/Misc /usr/share/zsh/functions/Newuser /usr/share/zsh/functions/Prompts /usr/share/zsh/functions/TCP /usr/share/zsh/functions/VCS_Info /usr/share/zsh/functions/VCS_Info/Backends /usr/share/zsh/functions/Zftp /usr/share/zsh/functions/Zle /root/.oh-my-zsh/custom/plugins/zsh-completions/src ) +funcfiletrace +funcsourcetrace +funcstack +functions +functions_source +functrace +galiases +histchars='!^#' +history +historywords +jobdirs +jobstates +jobtexts +key='' +keymaps +langinfo +less_termcap +line='' +mailpath=( ) +manpath=( ) +module_path=( /usr/lib/x86_64-linux-gnu/zsh/5.9 ) +modules +nameddirs +node=hcp1 +node_id=baea7bb6 +node_name=hcp2 +options +parameters +patchars +path=( /root/.trae-server/sdks/workspaces/cced0550/versions/node/current /root/.trae-server/sdks/versions/node/current /root/.trae-server/sdks/workspaces/cced0550/versions/node/current /root/.trae-server/sdks/versions/node/current /root/.trae-server/bin/stable-0643ffaa788ad4dd46eaa12cec109ac40595c816/bin/remote-cli /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin ) +pipestatus=( 0 ) +plugins=( git docker docker-compose ansible terraform kubectl helm aws gcloud zsh-autosuggestions zsh-syntax-highlighting zsh-completions colored-man-pages command-not-found extract history-substring-search sudo systemd tmux vscode web-search z ) +podman_status=false +precmd_functions=( _omz_async_request omz_termsupport_precmd _zsh_autosuggest_start _zsh_highlight_main__precmd_hook _zshz_precmd __vsc_precmd ) +preexec_functions=( omz_termsupport_preexec _zsh_highlight_preexec_hook __vsc_preexec ) +prompt=$'\n(TraeAI-3) %~ [%?] $ ' +psvar=( ) +reset_color +reswords +ret=0 +saliases +signals=( EXIT HUP INT QUIT ILL TRAP IOT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS ZERR DEBUG ) +status=0 +sysparams +termcap +terminfo +userdirs +usergroups +vsc_aa_env=( ) +vscode_base_dir=/root/.trae-server +watch +widgets +zle_bracketed_paste=( $'\C-[[?2004h' $'\C-[[?2004l' ) +zsh_eval_context=( toplevel ) +zsh_highlight__memo_feature=1 +zsh_highlight__pat_static_bug=false +zsh_scheduled_events diff --git a/configuration/playbooks/check-security-logs.yml b/configuration/playbooks/check-security-logs.yml deleted file mode 100644 index d746b2d..0000000 --- a/configuration/playbooks/check-security-logs.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -- name: Check for AppArmor or SELinux denials - hosts: germany - become: yes - tasks: - - name: Search journalctl for AppArmor/SELinux messages - shell: 'journalctl -k | grep -i -e apparmor -e selinux -e "avc: denied"' - register: security_logs - changed_when: false - failed_when: false - - - name: Display security logs - debug: - var: security_logs.stdout_lines \ No newline at end of file diff --git a/configuration/playbooks/cleanup-hashicorp-backups.yml b/configuration/playbooks/cleanup-hashicorp-backups.yml new file mode 100644 index 0000000..7adfc92 --- /dev/null +++ b/configuration/playbooks/cleanup-hashicorp-backups.yml @@ -0,0 +1,22 @@ +--- +- name: 清理 HashiCorp APT 源备份文件 + hosts: nomad_cluster + become: yes + + tasks: + - name: 查找所有 HashiCorp 备份文件 + find: + paths: "/etc/apt/sources.list.d/" + patterns: "hashicorp.list.backup-*" + register: backup_files + + - name: 删除所有备份文件 + file: + path: "{{ item.path }}" + state: absent + loop: "{{ backup_files.files }}" + when: backup_files.files | length > 0 + + - name: 显示清理结果 + debug: + msg: "已删除 {{ backup_files.files | length }} 个备份文件" \ No newline at end of file diff --git a/configuration/playbooks/configure-nomad-podman-cluster.yml b/configuration/playbooks/configure-nomad-podman-cluster.yml index 01430dd..7a5a533 100644 --- a/configuration/playbooks/configure-nomad-podman-cluster.yml +++ b/configuration/playbooks/configure-nomad-podman-cluster.yml @@ -1,6 +1,6 @@ --- - name: Configure Podman driver for all Nomad client nodes - hosts: nomad_clients,nomad_servers + hosts: target_nodes become: yes tasks: diff --git a/configuration/playbooks/debug-cgroup-permissions.yml b/configuration/playbooks/debug-cgroup-permissions.yml deleted file mode 100644 index df51dbf..0000000 --- a/configuration/playbooks/debug-cgroup-permissions.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -- name: Debug cgroup permissions - hosts: germany - become: yes - tasks: - - name: Check permissions of /sys/fs/cgroup/cpuset/ - stat: - path: /sys/fs/cgroup/cpuset/ - register: cpuset_dir - - - name: Display cpuset dir stats - debug: - var: cpuset_dir.stat - - - name: Check for nomad subdir in cpuset - stat: - path: /sys/fs/cgroup/cpuset/nomad - register: nomad_cpuset_dir - ignore_errors: true - - - name: Display nomad cpuset dir stats - debug: - var: nomad_cpuset_dir.stat - when: nomad_cpuset_dir.stat.exists is defined and nomad_cpuset_dir.stat.exists - - - name: List contents of /sys/fs/cgroup/cpuset/ - command: ls -la /sys/fs/cgroup/cpuset/ - register: ls_cpuset - changed_when: false - - - name: Display contents of /sys/fs/cgroup/cpuset/ - debug: - var: ls_cpuset.stdout_lines \ No newline at end of file diff --git a/configuration/playbooks/debug-nomad-cgroup.yml b/configuration/playbooks/debug-nomad-cgroup.yml deleted file mode 100644 index 4524ca8..0000000 --- a/configuration/playbooks/debug-nomad-cgroup.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -- name: Debug Nomad cgroup subdirectory - hosts: germany - become: yes - tasks: - - name: List contents of /sys/fs/cgroup/cpuset/nomad/ - command: ls -la /sys/fs/cgroup/cpuset/nomad/ - register: ls_nomad_cpuset - changed_when: false - failed_when: false - - - name: Display contents of /sys/fs/cgroup/cpuset/nomad/ - debug: - var: ls_nomad_cpuset.stdout_lines \ No newline at end of file diff --git a/configuration/playbooks/debug-nomad-germany.yml b/configuration/playbooks/debug-nomad-germany.yml deleted file mode 100644 index 65956ce..0000000 --- a/configuration/playbooks/debug-nomad-germany.yml +++ /dev/null @@ -1,24 +0,0 @@ -- name: Debug Nomad service on germany - hosts: germany - gather_facts: false - tasks: - - name: Get Nomad service status - command: systemctl status nomad.service --no-pager -l - register: nomad_status - ignore_errors: true - - - name: Get Nomad service journal - command: journalctl -xeu nomad.service --no-pager -n 100 - register: nomad_journal - ignore_errors: true - - - name: Display debug information - debug: - msg: | - --- Nomad Service Status --- - {{ nomad_status.stdout }} - {{ nomad_status.stderr }} - - --- Nomad Service Journal --- - {{ nomad_journal.stdout }} - {{ nomad_journal.stderr }} \ No newline at end of file diff --git a/configuration/playbooks/distribute-podman-germany.yml b/configuration/playbooks/distribute-podman-germany.yml deleted file mode 100644 index 587dc19..0000000 --- a/configuration/playbooks/distribute-podman-germany.yml +++ /dev/null @@ -1,12 +0,0 @@ -- name: Distribute new podman binary to germany - hosts: germany - gather_facts: false - tasks: - - name: Copy new podman binary to /usr/local/bin - copy: - src: /root/mgmt/configuration/podman-remote-static-linux_amd64 - dest: /usr/local/bin/podman - owner: root - group: root - mode: '0755' - become: yes \ No newline at end of file diff --git a/configuration/playbooks/find-nomad-service.yml b/configuration/playbooks/find-nomad-service.yml deleted file mode 100644 index 4cebaed..0000000 --- a/configuration/playbooks/find-nomad-service.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- -- name: Find Nomad service - hosts: germany - become: yes - tasks: - - name: List systemd services and filter for nomad - shell: systemctl list-unit-files --type=service | grep -i nomad - register: nomad_services - changed_when: false - failed_when: false - - - name: Display found services - debug: - var: nomad_services.stdout_lines \ No newline at end of file diff --git a/configuration/playbooks/fix-cgroup-permissions.yml b/configuration/playbooks/fix-cgroup-permissions.yml deleted file mode 100644 index 717e133..0000000 --- a/configuration/playbooks/fix-cgroup-permissions.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -- name: Fix cgroup permissions for Nomad - hosts: germany - become: yes - tasks: - - name: Recursively change ownership of nomad cgroup directory - file: - path: /sys/fs/cgroup/cpuset/nomad - state: directory - owner: root - group: root - recurse: yes - - - name: Change ownership of the parent cpuset directory - file: - path: /sys/fs/cgroup/cpuset/ - state: directory - owner: root - group: root \ No newline at end of file diff --git a/configuration/playbooks/fix-hashicorp-apt-source.yml b/configuration/playbooks/fix-hashicorp-apt-source.yml index 67276f1..5f85617 100644 --- a/configuration/playbooks/fix-hashicorp-apt-source.yml +++ b/configuration/playbooks/fix-hashicorp-apt-source.yml @@ -4,16 +4,9 @@ become: yes tasks: - - name: 备份现有的 HashiCorp APT 源配置(如果存在) - copy: - src: "/etc/apt/sources.list.d/hashicorp.list" - dest: "/etc/apt/sources.list.d/hashicorp.list.backup-{{ ansible_date_time.epoch }}" - remote_src: yes - ignore_errors: yes - - name: 创建正确的 HashiCorp APT 源配置 copy: - content: "deb [trusted=yes] http://apt.releases.hashicorp.com bookworm main\n" + content: "deb [trusted=yes] http://apt.releases.hashicorp.com {{ ansible_distribution_release }} main\n" dest: "/etc/apt/sources.list.d/hashicorp.list" owner: root group: root diff --git a/configuration/playbooks/install-consul.yml b/configuration/playbooks/install-consul.yml new file mode 100644 index 0000000..e7e82dd --- /dev/null +++ b/configuration/playbooks/install-consul.yml @@ -0,0 +1,68 @@ +--- +- name: 在 master 和 ash3c 节点安装 Consul + hosts: master,ash3c + become: yes + vars: + consul_version: "1.21.5" + consul_arch: "arm64" # 因为这两个节点都是 aarch64 + + tasks: + - name: 检查节点架构 + command: uname -m + register: node_arch + changed_when: false + + - name: 显示节点架构 + debug: + msg: "节点 {{ inventory_hostname }} 架构: {{ node_arch.stdout }}" + + - name: 检查是否已安装 consul + command: which consul + register: consul_check + failed_when: false + changed_when: false + + - name: 显示当前 consul 状态 + debug: + msg: "Consul 状态: {{ 'already installed' if consul_check.rc == 0 else 'not installed' }}" + + - name: 删除错误的 consul 二进制文件(如果存在) + file: + path: /usr/local/bin/consul + state: absent + when: consul_check.rc == 0 + + - name: 更新 APT 缓存 + apt: + update_cache: yes + ignore_errors: yes + + - name: 安装 consul 通过 APT + apt: + name: consul={{ consul_version }}-1 + state: present + + - name: 验证 consul 安装 + command: consul version + register: consul_version_check + changed_when: false + + - name: 显示安装的 consul 版本 + debug: + msg: "安装的 Consul 版本: {{ consul_version_check.stdout_lines[0] }}" + + - name: 确保 consul 用户存在 + user: + name: consul + system: yes + shell: /bin/false + home: /opt/consul + create_home: no + + - name: 创建 consul 数据目录 + file: + path: /opt/consul + state: directory + owner: consul + group: consul + mode: '0755' \ No newline at end of file diff --git a/configuration/playbooks/install-nomad-podman-driver.yml b/configuration/playbooks/install-nomad-podman-driver.yml index c308872..5e3d6e7 100644 --- a/configuration/playbooks/install-nomad-podman-driver.yml +++ b/configuration/playbooks/install-nomad-podman-driver.yml @@ -1,6 +1,6 @@ --- - name: Install Nomad Podman Driver Plugin - hosts: all + hosts: target_nodes become: yes vars: nomad_user: nomad diff --git a/configuration/playbooks/manual-run-nomad-germany.yml b/configuration/playbooks/manual-run-nomad-germany.yml deleted file mode 100644 index 00240d4..0000000 --- a/configuration/playbooks/manual-run-nomad-germany.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -- name: Manually run Nomad agent for debugging - hosts: germany - become: yes - tasks: - - name: Find Nomad binary path - shell: which nomad || find /usr -name nomad 2>/dev/null | head -1 - register: nomad_binary_path - failed_when: nomad_binary_path.stdout == "" - - - name: Run nomad agent directly - command: "{{ nomad_binary_path.stdout }} agent -config=/etc/nomad.d/nomad.hcl" - register: nomad_run - failed_when: false - - - name: Display Nomad output - debug: - var: nomad_run.stdout - - - name: Display Nomad error output - debug: - var: nomad_run.stderr \ No newline at end of file diff --git a/configuration/playbooks/read-nomad-config-germany.yml b/configuration/playbooks/read-nomad-config-germany.yml deleted file mode 100644 index 66ad8c7..0000000 --- a/configuration/playbooks/read-nomad-config-germany.yml +++ /dev/null @@ -1,12 +0,0 @@ -- name: Read Nomad config on germany - hosts: germany - gather_facts: false - tasks: - - name: Read nomad.hcl - command: cat /etc/nomad.d/nomad.hcl - register: nomad_config - ignore_errors: true - - - name: Display config - debug: - msg: "{{ nomad_config.stdout }}" \ No newline at end of file diff --git a/docs/consul-cluster-troubleshooting.md b/docs/consul-cluster-troubleshooting.md index 8ba809f..2df174c 100644 --- a/docs/consul-cluster-troubleshooting.md +++ b/docs/consul-cluster-troubleshooting.md @@ -4,47 +4,24 @@ ### 发现的问题 1. **DNS 解析失败**:服务间无法通过服务名相互发现 -2. **网络连通性问题**:`ash3c` 节点网络配置异常(地址显示为 0.0.0.0) +2. **网络连通性问题**:`ash3c` 节点网络配置异常 3. **跨节点通信失败**:`no route to host` 错误 4. **集群无法形成**:持续的 "No cluster leader" 错误 ### 根本原因 -- Docker Swarm overlay 网络在跨节点环境中的服务发现机制存在问题 -- `ash3c` 节点的网络配置可能有问题 +- 网络配置问题 - 防火墙或网络策略可能阻止了 Consul 集群通信端口 ## 解决方案 -### 方案 1:单节点 Consul(临时解决方案) -**文件**: `swarm/stacks/consul-single-node.yml` -**优点**: 简单、可靠、立即可用 -**缺点**: 没有高可用性 - -```bash -docker stack deploy -c swarm/stacks/consul-single-node.yml consul -``` - -### 方案 2:使用主机网络的集群配置 -**文件**: `swarm/stacks/consul-cluster-host-network.yml` -**优点**: 绕过 overlay 网络问题 -**缺点**: 需要手动配置 IP 地址 - -### 方案 3:修复后的 overlay 网络配置 -**文件**: `swarm/stacks/consul-cluster-fixed.yml` -**优点**: 使用 Docker 原生网络 -**缺点**: 需要解决底层网络问题 - -### 方案 4:macvlan 网络配置 -**文件**: `swarm/stacks/consul-cluster-macvlan.yml` -**优点**: 直接使用物理网络 -**缺点**: 需要网络管理员权限和配置 +### 当前部署方案(使用 Nomad + Podman) +目前集群已从 Docker Swarm 迁移到 Nomad + Podman,使用 `consul-cluster-nomad.nomad` 文件部署 Consul 集群。 ## 网络诊断步骤 ### 1. 检查节点状态 ```bash -docker node ls -docker node inspect --format '{{.Status.Addr}}' +nomad node status ``` ### 2. 检查网络连通性 @@ -64,10 +41,10 @@ telnet 8301 # 8600: Consul DNS ``` -### 4. 检查 Docker Swarm 网络 +### 4. 检查 Podman 网络 ```bash -docker network ls -docker network inspect +podman network ls +podman network inspect ``` ## 推荐的修复流程 @@ -108,7 +85,7 @@ docker exec consul operator raft list-peers ## 常见问题 ### Q: 为什么服务发现不工作? -A: Docker Swarm 的 overlay 网络在某些配置下可能存在 DNS 解析问题,特别是跨节点通信时。 +A: 在之前的 Docker Swarm 架构中,overlay 网络在某些配置下可能存在 DNS 解析问题。当前的 Nomad + Podman 架构已解决了这些问题。 ### Q: 如何选择合适的网络方案? A: diff --git a/docs/setup/consul-terraform-integration.md b/docs/setup/consul-terraform-integration.md index c61434d..2f22f13 100644 --- a/docs/setup/consul-terraform-integration.md +++ b/docs/setup/consul-terraform-integration.md @@ -26,12 +26,10 @@ ### 1. 启动 Consul 集群 -```bash -# 进入 swarm 目录 -cd swarm +当前集群已从 Docker Swarm 迁移到 Nomad + Podman,请使用 Nomad 部署 Consul 集群: -# 启动 Consul 集群 -docker-compose -f configs/traefik-consul-setup.yml up -d +```bash +nomad run /root/mgmt/consul-cluster-nomad.nomad ``` ### 2. 设置 Oracle Cloud 配置 diff --git a/jobs/consul-cluster-arm64.nomad b/jobs/consul-cluster-arm64.nomad new file mode 100644 index 0000000..f02ad69 --- /dev/null +++ b/jobs/consul-cluster-arm64.nomad @@ -0,0 +1,87 @@ +job "consul-cluster-arm64" { + datacenters = ["dc1"] + type = "service" + + # 只在 ARM64 节点上运行:master 和 ash3c + constraint { + attribute = "${attr.unique.hostname}" + operator = "regexp" + value = "(master|ash3c)" + } + + group "consul" { + count = 2 + + # 确保每个节点只运行一个实例 + constraint { + operator = "distinct_hosts" + value = "true" + } + + network { + port "http" { + static = 8500 + } + port "rpc" { + static = 8400 + } + port "serf_lan" { + static = 8301 + } + port "serf_wan" { + static = 8302 + } + port "server" { + static = 8300 + } + port "dns" { + static = 8600 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = [ + "agent", + "-server", + "-bootstrap-expect=2", + "-data-dir=/tmp/consul-cluster-data", + "-bind=${NOMAD_IP_serf_lan}", + "-client=0.0.0.0", + "-retry-join=100.117.106.136", # master Tailscale IP + "-retry-join=100.116.80.94", # ash3c Tailscale IP + "-ui-config-enabled=true", + "-log-level=INFO", + "-node=${node.unique.name}-consul", + "-datacenter=dc1" + ] + } + + artifact { + source = "https://releases.hashicorp.com/consul/1.17.0/consul_1.17.0_linux_arm64.zip" + destination = "local/" + } + + resources { + cpu = 200 + memory = 256 + } + + service { + name = "consul-cluster-arm64" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "3s" + } + } + } + } +} \ No newline at end of file diff --git a/jobs/consul-cluster-binary.nomad b/jobs/consul-cluster-binary.nomad new file mode 100644 index 0000000..e1acbfa --- /dev/null +++ b/jobs/consul-cluster-binary.nomad @@ -0,0 +1,88 @@ +job "consul-cluster" { + datacenters = ["dc1"] + type = "service" + + # 在三个节点上运行:bj-warden, master, ash3c + constraint { + attribute = "${node.unique.name}" + operator = "regexp" + value = "(bj-warden|master|ash3c)" + } + + group "consul" { + count = 3 + + # 确保每个节点只运行一个实例 + constraint { + operator = "distinct_hosts" + value = "true" + } + + network { + port "http" { + static = 8500 + } + port "rpc" { + static = 8400 + } + port "serf_lan" { + static = 8301 + } + port "serf_wan" { + static = 8302 + } + port "server" { + static = 8300 + } + port "dns" { + static = 8600 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = [ + "agent", + "-server", + "-bootstrap-expect=3", + "-data-dir=/tmp/consul-cluster-data", + "-bind=${NOMAD_IP_serf_lan}", + "-client=0.0.0.0", + "-retry-join=100.122.197.112", # bj-warden Tailscale IP + "-retry-join=100.117.106.136", # master Tailscale IP + "-retry-join=100.116.80.94", # ash3c Tailscale IP + "-ui-config-enabled=true", + "-log-level=INFO", + "-node=${node.unique.name}-consul", + "-datacenter=dc1" + ] + } + + artifact { + source = "https://releases.hashicorp.com/consul/1.17.0/consul_1.17.0_linux_arm64.zip" + destination = "local/" + } + + resources { + cpu = 200 + memory = 256 + } + + service { + name = "consul-cluster" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "3s" + } + } + } + } +} \ No newline at end of file diff --git a/consul-cluster-nomad.nomad b/jobs/consul-cluster-nomad.nomad similarity index 94% rename from consul-cluster-nomad.nomad rename to jobs/consul-cluster-nomad.nomad index 6791d61..4567d24 100644 --- a/consul-cluster-nomad.nomad +++ b/jobs/consul-cluster-nomad.nomad @@ -5,7 +5,7 @@ job "consul-cluster" { constraint { attribute = "${node.unique.name}" operator = "regexp" - value = "^(master|ash3c|hcs)$" + value = "^(master|ash3c|semaphore)$" } group "consul" { @@ -59,7 +59,7 @@ job "consul-cluster" { "-client=0.0.0.0", "-retry-join=100.117.106.136", "-retry-join=100.116.80.94", - "-retry-join=100.84.197.26" + "-retry-join=100.116.158.95" ] volumes = [ diff --git a/jobs/consul-cluster-simple.nomad b/jobs/consul-cluster-simple.nomad new file mode 100644 index 0000000..8954536 --- /dev/null +++ b/jobs/consul-cluster-simple.nomad @@ -0,0 +1,157 @@ +job "consul-cluster-simple" { + datacenters = ["dc1"] + type = "service" + + group "consul-master" { + count = 1 + + constraint { + attribute = "${node.unique.name}" + value = "master" + } + + network { + port "http" { + static = 8500 + } + port "rpc" { + static = 8300 + } + port "serf_lan" { + static = 8301 + } + port "serf_wan" { + static = 8302 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = [ + "agent", + "-server", + "-bootstrap-expect=3", + "-data-dir=/opt/nomad/data/consul", + "-client=100.64.0.0/10", + "-bind=100.117.106.136", + "-advertise=100.117.106.136", + "-retry-join=100.116.80.94", + "-retry-join=100.122.197.112", + "-ui" + ] + } + + resources { + cpu = 300 + memory = 512 + } + + + } + } + + group "consul-ash3c" { + count = 1 + + constraint { + attribute = "${node.unique.name}" + value = "ash3c" + } + + network { + port "http" { + static = 8500 + } + port "rpc" { + static = 8300 + } + port "serf_lan" { + static = 8301 + } + port "serf_wan" { + static = 8302 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = [ + "agent", + "-server", + "-bootstrap-expect=3", + "-data-dir=/opt/nomad/data/consul", + "-client=100.64.0.0/10", + "-bind=100.116.80.94", + "-advertise=100.116.80.94", + "-retry-join=100.117.106.136", + "-retry-join=100.122.197.112", + "-ui" + ] + } + + resources { + cpu = 300 + memory = 512 + } + + + } + } + + group "consul-warden" { + count = 1 + + constraint { + attribute = "${node.unique.name}" + value = "bj-warden" + } + + network { + port "http" { + static = 8500 + } + port "rpc" { + static = 8300 + } + port "serf_lan" { + static = 8301 + } + port "serf_wan" { + static = 8302 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = [ + "agent", + "-server", + "-bootstrap-expect=3", + "-data-dir=/opt/nomad/data/consul", + "-client=100.64.0.0/10", + "-bind=100.122.197.112", + "-advertise=100.122.197.112", + "-retry-join=100.117.106.136", + "-retry-join=100.116.80.94", + "-ui" + ] + } + + resources { + cpu = 300 + memory = 512 + } + + + } + } +} \ No newline at end of file diff --git a/jobs/consul-cluster-three-nodes.nomad b/jobs/consul-cluster-three-nodes.nomad new file mode 100644 index 0000000..1b54047 --- /dev/null +++ b/jobs/consul-cluster-three-nodes.nomad @@ -0,0 +1,190 @@ +job "consul-cluster-three-nodes" { + datacenters = ["dc1"] + type = "service" + + group "consul-master" { + count = 1 + + constraint { + attribute = "${node.unique.name}" + value = "master" + } + + network { + port "http" { + static = 8500 + } + port "rpc" { + static = 8300 + } + port "serf_lan" { + static = 8301 + } + port "serf_wan" { + static = 8302 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = [ + "agent", + "-server", + "-bootstrap-expect=3", + "-data-dir=/opt/nomad/data/consul", + "-client=0.0.0.0", + "-bind=100.117.106.136", + "-advertise=100.117.106.136", + "-retry-join=100.116.80.94", + "-retry-join=100.122.197.112", + "-ui-config-enabled=true" + ] + } + + resources { + cpu = 300 + memory = 512 + } + + service { + name = "consul-master" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "3s" + } + } + } + } + + group "consul-ash3c" { + count = 1 + + constraint { + attribute = "${node.unique.name}" + value = "ash3c" + } + + network { + port "http" { + static = 8500 + } + port "rpc" { + static = 8300 + } + port "serf_lan" { + static = 8301 + } + port "serf_wan" { + static = 8302 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = [ + "agent", + "-server", + "-bootstrap-expect=3", + "-data-dir=/opt/nomad/data/consul", + "-client=0.0.0.0", + "-bind=100.116.80.94", + "-advertise=100.116.80.94", + "-retry-join=100.117.106.136", + "-retry-join=100.122.197.112", + "-ui-config-enabled=true" + ] + } + + resources { + cpu = 300 + memory = 512 + } + + service { + name = "consul-ash3c" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "3s" + } + } + } + } + + group "consul-warden" { + count = 1 + + constraint { + attribute = "${node.unique.name}" + value = "bj-warden" + } + + network { + port "http" { + static = 8500 + } + port "rpc" { + static = 8300 + } + port "serf_lan" { + static = 8301 + } + port "serf_wan" { + static = 8302 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = [ + "agent", + "-server", + "-bootstrap-expect=3", + "-data-dir=/opt/nomad/data/consul", + "-client=0.0.0.0", + "-bind=100.122.197.112", + "-advertise=100.122.197.112", + "-retry-join=100.117.106.136", + "-retry-join=100.116.80.94", + "-ui-config-enabled=true" + ] + } + + resources { + cpu = 300 + memory = 512 + } + + service { + name = "consul-warden" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "3s" + } + } + } + } +} \ No newline at end of file diff --git a/consul-cluster.nomad b/jobs/consul-cluster.nomad similarity index 100% rename from consul-cluster.nomad rename to jobs/consul-cluster.nomad diff --git a/jobs/consul-single-member-new.nomad b/jobs/consul-single-member-new.nomad new file mode 100644 index 0000000..fabc4bb --- /dev/null +++ b/jobs/consul-single-member-new.nomad @@ -0,0 +1,47 @@ +job "consul-single-member" { + datacenters = ["dc1"] + type = "service" + priority = 50 + + constraint { + attribute = "${node.unique.name}" + value = "warden" + } + + group "consul" { + count = 1 + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = ["agent", "-dev", "-client=0.0.0.0", "-data-dir=/tmp/consul-data"] + } + + resources { + cpu = 200 + memory = 256 + network { + mbits = 10 + port "http" { + static = 8500 + } + } + } + + service { + name = "consul" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "2s" + } + } + } + } +} \ No newline at end of file diff --git a/jobs/consul-single-member.nomad b/jobs/consul-single-member.nomad new file mode 100644 index 0000000..fabc4bb --- /dev/null +++ b/jobs/consul-single-member.nomad @@ -0,0 +1,47 @@ +job "consul-single-member" { + datacenters = ["dc1"] + type = "service" + priority = 50 + + constraint { + attribute = "${node.unique.name}" + value = "warden" + } + + group "consul" { + count = 1 + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = ["agent", "-dev", "-client=0.0.0.0", "-data-dir=/tmp/consul-data"] + } + + resources { + cpu = 200 + memory = 256 + network { + mbits = 10 + port "http" { + static = 8500 + } + } + } + + service { + name = "consul" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "2s" + } + } + } + } +} \ No newline at end of file diff --git a/jobs/consul-test-warden.nomad b/jobs/consul-test-warden.nomad new file mode 100644 index 0000000..08c3887 --- /dev/null +++ b/jobs/consul-test-warden.nomad @@ -0,0 +1,46 @@ +job "consul-test-warden" { + datacenters = ["dc1"] + type = "service" + + constraint { + attribute = "${node.unique.name}" + value = "bj-warden" + } + + group "consul" { + count = 1 + + network { + port "http" { + static = 8500 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = ["agent", "-dev", "-client=0.0.0.0", "-data-dir=/tmp/consul-test"] + } + + resources { + cpu = 200 + memory = 256 + } + + service { + name = "consul-test" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "2s" + } + } + } + } +} \ No newline at end of file diff --git a/jobs/consul-warden-only.nomad b/jobs/consul-warden-only.nomad new file mode 100644 index 0000000..cc7f4b8 --- /dev/null +++ b/jobs/consul-warden-only.nomad @@ -0,0 +1,46 @@ +job "consul-warden" { + datacenters = ["dc1"] + type = "service" + priority = 50 + + constraint { + attribute = "${node.unique.name}" + value = "warden" + } + + group "consul" { + count = 1 + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = ["agent", "-dev", "-client=0.0.0.0", "-data-dir=/tmp/consul-data"] + } + + resources { + cpu = 200 + memory = 256 + network { + port "http" { + static = 8500 + } + } + } + + service { + name = "consul" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "2s" + } + } + } + } +} \ No newline at end of file diff --git a/install-podman-driver.nomad b/jobs/install-podman-driver.nomad similarity index 100% rename from install-podman-driver.nomad rename to jobs/install-podman-driver.nomad diff --git a/jobs/service-discovery-warden.nomad b/jobs/service-discovery-warden.nomad new file mode 100644 index 0000000..fe36d86 --- /dev/null +++ b/jobs/service-discovery-warden.nomad @@ -0,0 +1,46 @@ +job "service-discovery-warden" { + datacenters = ["dc1"] + type = "service" + + constraint { + attribute = "${node.unique.name}" + value = "warden" + } + + group "discovery" { + count = 1 + + network { + port "http" { + static = 8500 + } + } + + task "discovery" { + driver = "exec" + + config { + command = "consul" + args = ["agent", "-dev", "-client=0.0.0.0", "-data-dir=/tmp/discovery-data"] + } + + resources { + cpu = 200 + memory = 256 + } + + service { + name = "discovery-service" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "2s" + } + } + } + } +} \ No newline at end of file diff --git a/jobs/simple-consul-warden.nomad b/jobs/simple-consul-warden.nomad new file mode 100644 index 0000000..fb35a87 --- /dev/null +++ b/jobs/simple-consul-warden.nomad @@ -0,0 +1,52 @@ +job "simple-consul-test" { + datacenters = ["dc1"] + type = "service" + + constraint { + attribute = "${node.unique.name}" + value = "warden" + } + + group "consul" { + count = 1 + + network { + port "http" { + static = 8500 + } + } + + task "consul" { + driver = "exec" + + config { + command = "consul" + args = [ + "agent", + "-dev", + "-client=0.0.0.0", + "-bind=100.122.197.112", + "-data-dir=/tmp/consul-test-data" + ] + } + + resources { + cpu = 200 + memory = 256 + } + + service { + name = "consul-test" + port = "http" + + check { + type = "http" + path = "/v1/status/leader" + port = "http" + interval = "10s" + timeout = "2s" + } + } + } + } +} \ No newline at end of file diff --git a/test-job.nomad b/jobs/test-job.nomad similarity index 100% rename from test-job.nomad rename to jobs/test-job.nomad diff --git a/jobs/test-podman-job.nomad b/jobs/test-podman-job.nomad new file mode 100644 index 0000000..3392296 --- /dev/null +++ b/jobs/test-podman-job.nomad @@ -0,0 +1,24 @@ +job "test-podman" { + datacenters = ["dc1"] + type = "batch" + + group "test" { + count = 1 + + task "hello" { + driver = "podman" + + config { + image = "docker.io/library/hello-world:latest" + logging = { + driver = "journald" + } + } + + resources { + cpu = 100 + memory = 128 + } + } + } +} \ No newline at end of file diff --git a/jobs/test-podman-simple.nomad b/jobs/test-podman-simple.nomad new file mode 100644 index 0000000..3674e05 --- /dev/null +++ b/jobs/test-podman-simple.nomad @@ -0,0 +1,23 @@ +job "test-podman-simple" { + datacenters = ["dc1"] + type = "batch" + + group "test" { + count = 1 + + task "hello" { + driver = "podman" + + config { + image = "alpine:latest" + command = "echo" + args = ["Hello from Podman!"] + } + + resources { + cpu = 100 + memory = 64 + } + } + } +} \ No newline at end of file diff --git a/jobs/test-private-registry.nomad b/jobs/test-private-registry.nomad new file mode 100644 index 0000000..4b31f37 --- /dev/null +++ b/jobs/test-private-registry.nomad @@ -0,0 +1,31 @@ +job "test-private-registry" { + datacenters = ["dc1"] + type = "batch" + + group "test" { + count = 1 + + # 指定运行在北京节点上 + constraint { + attribute = "${node.unique.name}" + operator = "regexp" + value = "bj-.*" + } + + task "hello" { + driver = "podman" + + config { + image = "hello-world:latest" + logging = { + driver = "journald" + } + } + + resources { + cpu = 100 + memory = 64 + } + } + } +} \ No newline at end of file diff --git a/jobs/test-simple.nomad b/jobs/test-simple.nomad new file mode 100644 index 0000000..a1327b3 --- /dev/null +++ b/jobs/test-simple.nomad @@ -0,0 +1,27 @@ +job "test-simple" { + datacenters = ["dc1"] + type = "service" + + constraint { + attribute = "${node.unique.name}" + value = "warden" + } + + group "test" { + count = 1 + + task "hello" { + driver = "exec" + + config { + command = "echo" + args = ["Hello from warden node!"] + } + + resources { + cpu = 100 + memory = 64 + } + } + } +} \ No newline at end of file diff --git a/key.md b/key.md deleted file mode 100644 index 557173c..0000000 --- a/key.md +++ /dev/null @@ -1,61 +0,0 @@ -oci usa ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDiDwCO56943GNv -rg2WYQCZTpdgA1YfdDcM6AXugzwGm6zQwuhuDdTABwJj0PfB7q5s0MZFqwpVW+MS -IkTQsk8r+8gBo9FBtH4nneBfXgqjiKhlHhcgqHWALXm8AzDq6MJ+lCDwjgi5PsID -5jbnBUBXRpVXlkEDMZj5yTNiRfLMlFZiqc4mv1j1RSIQeuptt7l2AnXhKSuW2FL3 -aQNWspUylIs9IDXuC490r04/fZmX0Iw7Crb4yWWFem1e1x0g6qDiAizwpE3FF0No -AqkH+p+y3Qe7Pew2/UUS4VNRyoGBstpbBJMQ8dXRER9M4KTEdLnSG2EzdM5IGupj -Gwo9PnPPAgMBAAECggEAChaP+HtPvLMJH6NtfnfXEQBi3P6zyd+OfV2gC3mBJO0E -P09ovqXmB/5ywDBD05G/6EyWLJG/ek5eycu3CnaKoJ8x2RuNwRg5m7GooQOPXKZC -mDtJiO7mSia9YgM6caFFh2SQ5mtQPwQVSxtA+U+mBBRlocJWJbsBj/7HSOwaM8BC -wl19kZiW0aOkoGidxvjlJfkPiNer/jTy5RMNKruDpaF8PsF7xIMLwuxT5VQ/gyYA -frXsWfQp+sve/XfUg9/RGP9jJQHNppL56YWYPa8XusC2nJCym9RLDlK56jF9jhYM -iQThksG3TzOXjdGM7MP5Q/SfNckQWy0KTOu7h+N98QKBgQD9l7SFypX5Mgn2PC1A -U3lwiLCvviaKSNbzNXc6pnijbGEEvNpUGRyGwmjXItGLiEoAky0eu42Ipult6PB+ -WsjCIGTGI0UBObrjbWfaj+vt6zCcI653tgvrQO97t+F6xTHiQyGRDqIlFQcE9ZKO -EJ+wuC+MbBFGPSc/Zw43/twpzQKBgQDkNGHiuoGvoSuRPpMIB82IsMmtOlzymk8B -ZZMNzHfxFyf7a/1NUhc3UvmZdE67MS4cmoY0LCY4HBW2zxxsnX0cA+vRNLFneJMC -oH2XgQs+mi1Dgem+N6EYO/5PJZqeyUJ5x5WFGrULvsL2GKcSeSp2o2nkFyKSHfhD -7zWSQyNICwKBgQD1lDZD4n3+BxFSndAMnUnbSuQgLPrRq9xNRpeh+piVWl1R4zlj -e7X+YsJ4pMVcZK2VhPGK84IKtekUgSJ0mqIULJ6qqnkmyKtNlyOdqwaFLt+yNXO9 -hlRgjE/e9aGr7M90GCKngQ5Q7t4PVWmJnlunHZceW4EXDh217qz8WRkIeQKBgGV0 -JFB4Ok+qh4P7HcLkNSwf7Ilm+QuiLp2gWtA3pts4QD42tFY7uLaP3Qer/ZSbOLTe -vetT9WnckorDaQ+gtI5P7/cCRhyKLlFsqGlCpY0fXiA1EYXPlX8ArP7i6OrO7w7U -/FRAm1ytYl+mdiBwXcCAxgLxhh0P1d/d6SMtVfIhAoGBALdQ9tPSkFsJiDJi+d1u -SwPauzmr8O0geaLuey9WxVAxm5DR7eTJTUswZJGqadAZlnDr/H+7YU6lEBRI224Q -0ApgIIeGpMsTZFetRsOq+TUoNQcGVCtspOckCbElW6NMSzuAlf7aI+VqKM3uwlAn -FiTDYcmZIE1yNjYi+uaD8vkU ------END PRIVATE KEY----- -OCI_API_KEY - -oci kr ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCXpRaddq1RzS0J -cBAGHh94DJyqOBti+ASs6Abv1v3gyXu0ssNSe/g/ZBe+aDIIBisL/5nRLDZfeNZg -S5azKgPFPPlukTKVAfatQrgsaCC3uNm4D0NfGSINY90HcpZwX5GCZbUIFZuBZBac -33ze+RXoEo/QJFqniiUh/19A1R6mO2oJchgcXYv3+Fe8db35Fb9HDt6tg8+maO5R -8W149SpQUlU9JWCWuRV2cdKSXP1/z3JH3bq7hezD5qkZrcwt+4e+Pj9moYyA+O8i -n9Z9yQUeipp+HR3SdM++pzUhc1E/NNrgNTKQJi3OvrUx41jc00/5KwZUYyOtvJa3 -/GBzycKNAgMBAAECggEAFiooGw3cmWc+3PFHNk2y1c4qG+slfZq4vDkRwn6PDwsE -DM5QJD9AcquDmO4L2gZkxlUuu1cV/3BfDSYfOcK7WFnoL1QDq6nkz0BAQSVbGt9m -2zNH6p92zbQ5+zuxZ21gjEmnYy4dU5U4hOdZjhGkNQ55fLfDlFdpxAVae9RqrWsp -8/9SiuD1G+fgNKbe8x+ASd8Y4rP9gjItDUCqmi2rnJJsR8i1PHMZiLC0S3FbcUo6 -R7kwO0dGaXYFWtPUlMB/OPx172HM6yfE+lDmT6gKfWJ2dOKvu7mOtXTGSBIGMREV -MpC9ZhYBsk3jvZKlEju0sEATg3tdDBeX3xRNOkPFFQKBgQDVrmuAOS/Xrz5ZGzwl -nRzgtR02EjdQ+wJrfyJoufmJWZQlC6cAe3RyCBs8c7YiSyvTL773ZW/v9RoVln0R -WDaEdw7L+YLjdv51Dzy3KVY9A4HX1s+xhP5zG2C24Rp5ALwfS6Qf2JDGtAlm821J -z5kwEcY2sSOxGqL8MTA9gysmqwKBgQC1rXBNsW4BVhbqOXMC6ZpwQ/n6BEdHGl8g -1nNonznnZ6ECI/G/TBYA5Pb23FslpQAaFaYgg6pY1FisnIVUpjk23xKd193zY7P4 -8Ah7D/gcAtloXeJ6sknaWjkBBYRRyZhlG3M428kKoIgxQveokGZiLvF4xB9/NBM3 -oCepbq6bpwKBgQC8KWFEghcdCJYQhSkLvjQls5bLfHL1fnN9EXDNY6bXSehoTsB6 -bjv2BillrEcgH62xxAOXet19IgocJG5xjYpET0raVxbpEmmzzv0aFO55v9Lgq6os -mf4ugldB8ysKjpkZvdQCrwOd1f/JhmYgbwxoBd7TXl0doWUQSog+QnkHDQKBgDMd -jDZf0GqR1TqrVT+hiDFD/uYoJAHOWqt7itcJzZnc30Eh6dd/ycUQpqeIEiECTogI -RUhqoxgBDr3p/910My7MDonYfXsIN0+4ATrWoGEJMDAcEiehWAQWVGmEKtl0FeuE -kKOTuvnBdvAdPl7v2c6QFKJ807vPZATHi8ExAfGLAoGBALuFCz/9Xlw5legmGJxP -IgbhcmSFCw9OmGRA6KdRZUN+Zsb6FVj9eCcF1My13iw359xFaYdhBD6hnbPIe3XS -bzVMczdiuRAI9LijXhzGWmw5hlkumaVDqZI3+Sy5lohLhOsV4Erss1vqL3R40zjk -fk2tnbktORYd6/Q0i0FJdO/H ------END PRIVATE KEY----- -OCI_API_KEY \ No newline at end of file diff --git a/playbooks/add-beijing-node-prefix.yml b/playbooks/add-beijing-node-prefix.yml new file mode 100644 index 0000000..4cb6f7a --- /dev/null +++ b/playbooks/add-beijing-node-prefix.yml @@ -0,0 +1,69 @@ +--- +- name: Add Beijing prefix to LXC node names in Nomad configuration + hosts: beijing + become: yes + + vars: + node_prefixes: + influxdb: "bj-influxdb" + warden: "bj-warden" + hcp1: "bj-hcp1" + hcp2: "bj-hcp2" + tailscale_ips: + influxdb: "100.100.7.4" + warden: "100.122.197.112" + hcp1: "100.97.62.111" + hcp2: "100.116.112.45" + + tasks: + - name: Stop Nomad service + systemd: + name: nomad + state: stopped + + - name: Get current node name from inventory + set_fact: + current_node_name: "{{ inventory_hostname }}" + new_node_name: "{{ node_prefixes[inventory_hostname] }}" + tailscale_ip: "{{ tailscale_ips[inventory_hostname] }}" + + - name: Display node name change + debug: + msg: "Changing node name from {{ current_node_name }} to {{ new_node_name }}, using Tailscale IP {{ tailscale_ip }}" + + - name: Update node name in Nomad configuration + lineinfile: + path: /etc/nomad.d/nomad.hcl + regexp: '^name\s*=' + line: 'name = "{{ new_node_name }}"' + insertafter: 'datacenter = "dc1"' + state: present + + - name: Validate Nomad configuration + shell: nomad config validate /etc/nomad.d/nomad.hcl + register: config_validation + failed_when: config_validation.rc != 0 + + - name: Start Nomad service + systemd: + name: nomad + state: started + + - name: Wait for Nomad to be ready on Tailscale IP + wait_for: + port: 4646 + host: "{{ tailscale_ip }}" + delay: 10 + timeout: 60 + + - name: Wait for node registration + pause: + seconds: 15 + + - name: Display new configuration + shell: cat /etc/nomad.d/nomad.hcl | grep -E "^(datacenter|name|bind_addr)\s*=" + register: nomad_config_check + + - name: Show updated configuration + debug: + var: nomad_config_check.stdout_lines \ No newline at end of file diff --git a/playbooks/fix-duplicate-plugin-dir.yml b/playbooks/fix-duplicate-plugin-dir.yml new file mode 100644 index 0000000..6e73d96 --- /dev/null +++ b/playbooks/fix-duplicate-plugin-dir.yml @@ -0,0 +1,56 @@ +--- +- name: Fix duplicate plugin_dir configuration + hosts: nomadlxc,hcp + become: yes + + tasks: + - name: Stop Nomad service + systemd: + name: nomad + state: stopped + + - name: Remove duplicate plugin_dir lines + lineinfile: + path: /etc/nomad.d/nomad.hcl + regexp: '^plugin_dir = "/opt/nomad/plugins"' + state: absent + + - name: Ensure only one plugin_dir configuration exists + lineinfile: + path: /etc/nomad.d/nomad.hcl + regexp: '^plugin_dir = "/opt/nomad/data/plugins"' + line: 'plugin_dir = "/opt/nomad/data/plugins"' + insertafter: 'data_dir = "/opt/nomad/data"' + state: present + + - name: Validate Nomad configuration + shell: nomad config validate /etc/nomad.d/nomad.hcl + register: config_validation + failed_when: config_validation.rc != 0 + + - name: Start Nomad service + systemd: + name: nomad + state: started + + - name: Wait for Nomad to be ready + wait_for: + port: 4646 + host: localhost + delay: 10 + timeout: 60 + + - name: Wait for plugins to load + pause: + seconds: 15 + + - name: Check driver status + shell: | + export NOMAD_ADDR=http://localhost:4646 + nomad node status -self | grep -A 10 "Driver Status" + register: driver_status + failed_when: false + + - name: Display driver status + debug: + var: driver_status.stdout_lines \ No newline at end of file diff --git a/playbooks/fix-podman-driver-config.yml b/playbooks/fix-podman-driver-config.yml new file mode 100644 index 0000000..5e6e1d5 --- /dev/null +++ b/playbooks/fix-podman-driver-config.yml @@ -0,0 +1,112 @@ +--- +- name: Fix Nomad Podman Driver Configuration + hosts: nomadlxc,hcp + become: yes + vars: + nomad_user: nomad + + tasks: + - name: Stop Nomad service + systemd: + name: nomad + state: stopped + + - name: Install Podman driver plugin if missing + block: + - name: Check if plugin exists + stat: + path: /opt/nomad/data/plugins/nomad-driver-podman + register: plugin_exists + + - name: Download and install Podman driver plugin + block: + - name: Download Nomad Podman driver + get_url: + url: "https://releases.hashicorp.com/nomad-driver-podman/0.6.1/nomad-driver-podman_0.6.1_linux_amd64.zip" + dest: "/tmp/nomad-driver-podman.zip" + mode: '0644' + + - name: Extract Podman driver + unarchive: + src: "/tmp/nomad-driver-podman.zip" + dest: "/tmp" + remote_src: yes + + - name: Install Podman driver + copy: + src: "/tmp/nomad-driver-podman" + dest: "/opt/nomad/data/plugins/nomad-driver-podman" + owner: "{{ nomad_user }}" + group: "{{ nomad_user }}" + mode: '0755' + remote_src: yes + + - name: Clean up temporary files + file: + path: "{{ item }}" + state: absent + loop: + - "/tmp/nomad-driver-podman.zip" + - "/tmp/nomad-driver-podman" + when: not plugin_exists.stat.exists + + - name: Update Nomad configuration with correct plugin name and socket path + replace: + path: /etc/nomad.d/nomad.hcl + regexp: 'plugin "podman" \{' + replace: 'plugin "nomad-driver-podman" {' + + - name: Update socket path to system socket + replace: + path: /etc/nomad.d/nomad.hcl + regexp: 'socket_path = "unix:///run/user/1001/podman/podman.sock"' + replace: 'socket_path = "unix:///run/podman/podman.sock"' + + - name: Add plugin_dir configuration if missing + lineinfile: + path: /etc/nomad.d/nomad.hcl + line: 'plugin_dir = "/opt/nomad/data/plugins"' + insertafter: 'data_dir = "/opt/nomad/data"' + state: present + + - name: Ensure Podman socket is enabled and running + systemd: + name: podman.socket + enabled: yes + state: started + + - name: Start Nomad service + systemd: + name: nomad + state: started + + - name: Wait for Nomad to be ready + wait_for: + port: 4646 + host: localhost + delay: 10 + timeout: 60 + + - name: Wait for plugins to load + pause: + seconds: 20 + + - name: Check driver status + shell: | + export NOMAD_ADDR=http://localhost:4646 + nomad node status -self | grep -A 10 "Driver Status" + register: driver_status + failed_when: false + + - name: Display driver status + debug: + var: driver_status.stdout_lines + + - name: Check for Podman driver in logs + shell: journalctl -u nomad -n 30 --no-pager | grep -E "(podman|plugin)" | tail -10 + register: plugin_logs + failed_when: false + + - name: Display plugin logs + debug: + var: plugin_logs.stdout_lines \ No newline at end of file diff --git a/playbooks/fix-warden-nfs.yml b/playbooks/fix-warden-nfs.yml new file mode 100644 index 0000000..0e4372a --- /dev/null +++ b/playbooks/fix-warden-nfs.yml @@ -0,0 +1,46 @@ +--- +- name: Fix NFS mounting on warden node + hosts: warden + become: yes + tasks: + - name: Ensure rpcbind is running + systemd: + name: rpcbind + state: started + enabled: yes + + - name: Ensure nfs-client.target is active + systemd: + name: nfs-client.target + state: started + enabled: yes + + - name: Create consul-shared directory + file: + path: /opt/consul-shared + state: directory + mode: '0755' + + - name: Mount NFS share + mount: + path: /opt/consul-shared + src: snail:/fs/1000/nfs + fstype: nfs + opts: rw,sync,vers=3 + state: mounted + + - name: Add to fstab for persistence + mount: + path: /opt/consul-shared + src: snail:/fs/1000/nfs + fstype: nfs + opts: rw,sync,vers=3 + state: present + + - name: Verify mount + command: df -h /opt/consul-shared + register: mount_result + + - name: Display mount result + debug: + var: mount_result.stdout \ No newline at end of file diff --git a/playbooks/setup-nfs-storage.yml b/playbooks/setup-nfs-storage.yml new file mode 100644 index 0000000..51deb80 --- /dev/null +++ b/playbooks/setup-nfs-storage.yml @@ -0,0 +1,75 @@ +--- +- name: Setup NFS Storage for Consul Cluster + hosts: localhost + gather_facts: false + vars: + nfs_server: snail + nfs_export_path: /fs/1000/nfs + nfs_mount_path: /opt/consul-shared + + tasks: + - name: Install NFS client and mount on master + ansible.builtin.shell: | + ssh -o StrictHostKeyChecking=no -p 60022 ben@master ' + echo "3131" | sudo -S apt update && + echo "3131" | sudo -S apt install -y nfs-common && + echo "3131" | sudo -S mkdir -p {{ nfs_mount_path }} && + echo "3131" | sudo -S mount -t nfs {{ nfs_server }}:{{ nfs_export_path }} {{ nfs_mount_path }} && + echo "{{ nfs_server }}:{{ nfs_export_path }} {{ nfs_mount_path }} nfs defaults 0 0" | echo "3131" | sudo -S tee -a /etc/fstab + ' + delegate_to: localhost + register: master_result + + - name: Install NFS client and mount on ash3c + ansible.builtin.shell: | + ssh -o StrictHostKeyChecking=no ben@ash3c ' + echo "3131" | sudo -S apt update && + echo "3131" | sudo -S apt install -y nfs-common && + echo "3131" | sudo -S mkdir -p {{ nfs_mount_path }} && + echo "3131" | sudo -S mount -t nfs {{ nfs_server }}:{{ nfs_export_path }} {{ nfs_mount_path }} && + echo "{{ nfs_server }}:{{ nfs_export_path }} {{ nfs_mount_path }} nfs defaults 0 0" | echo "3131" | sudo -S tee -a /etc/fstab + ' + delegate_to: localhost + register: ash3c_result + + - name: Install NFS client and mount on warden + ansible.builtin.shell: | + ssh -o StrictHostKeyChecking=no ben@warden ' + echo "3131" | sudo -S apt update && + echo "3131" | sudo -S apt install -y nfs-common && + echo "3131" | sudo -S mkdir -p {{ nfs_mount_path }} && + echo "3131" | sudo -S mount -t nfs {{ nfs_server }}:{{ nfs_export_path }} {{ nfs_mount_path }} && + echo "{{ nfs_server }}:{{ nfs_export_path }} {{ nfs_mount_path }} nfs defaults 0 0" | echo "3131" | sudo -S tee -a /etc/fstab + ' + delegate_to: localhost + register: warden_result + + - name: Test NFS connectivity on all nodes + ansible.builtin.shell: | + ssh -o StrictHostKeyChecking=no -p 60022 ben@master 'echo "3131" | sudo -S touch {{ nfs_mount_path }}/test-master-$(date +%s) && ls -la {{ nfs_mount_path }}/' + ssh -o StrictHostKeyChecking=no ben@ash3c 'echo "3131" | sudo -S touch {{ nfs_mount_path }}/test-ash3c-$(date +%s) && ls -la {{ nfs_mount_path }}/' + ssh -o StrictHostKeyChecking=no ben@warden 'echo "3131" | sudo -S touch {{ nfs_mount_path }}/test-warden-$(date +%s) && ls -la {{ nfs_mount_path }}/' + delegate_to: localhost + register: nfs_test_result + + - name: Display NFS test results + ansible.builtin.debug: + var: nfs_test_result.stdout_lines + + - name: Create Consul data directories on NFS + ansible.builtin.shell: | + ssh -o StrictHostKeyChecking=no -p 60022 ben@master 'echo "3131" | sudo -S mkdir -p {{ nfs_mount_path }}/consul-master' + ssh -o StrictHostKeyChecking=no ben@ash3c 'echo "3131" | sudo -S mkdir -p {{ nfs_mount_path }}/consul-ash3c' + ssh -o StrictHostKeyChecking=no ben@warden 'echo "3131" | sudo -S mkdir -p {{ nfs_mount_path }}/consul-warden' + delegate_to: localhost + register: consul_dirs_result + + - name: Display setup completion + ansible.builtin.debug: + msg: + - "NFS setup completed successfully!" + - "NFS mount point: {{ nfs_mount_path }}" + - "Consul data directories created:" + - " - {{ nfs_mount_path }}/consul-master" + - " - {{ nfs_mount_path }}/consul-ash3c" + - " - {{ nfs_mount_path }}/consul-warden" \ No newline at end of file diff --git a/scripts/utilities/cleanup-retired-nodes.sh b/scripts/utilities/cleanup-retired-nodes.sh new file mode 100644 index 0000000..d2f3cde --- /dev/null +++ b/scripts/utilities/cleanup-retired-nodes.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# 清理退役节点脚本 +# 创建日期: 2025-09-27 +# 执行日期: 2025-10-27 (一个月后) + +set -e + +NOMAD_ADDR=${NOMAD_ADDR:-"http://100.116.158.95:4646"} + +echo "=== 清理退役节点脚本 ===" +echo "执行时间: $(date)" +echo "Nomad 地址: $NOMAD_ADDR" +echo "" + +# 退役节点列表 +RETIRED_NODES=( + "583f1b77:semaphore:已转为纯server" + "06bb8a3a:hcs:华为云节点退役" +) + +echo "准备清理以下退役节点:" +for node_info in "${RETIRED_NODES[@]}"; do + IFS=':' read -r node_id node_name reason <<< "$node_info" + echo " - $node_name ($node_id): $reason" +done +echo "" + +read -p "确认要清理这些节点吗? (y/N): " confirm +if [[ $confirm != [yY] ]]; then + echo "操作已取消" + exit 0 +fi + +echo "开始清理退役节点..." + +for node_info in "${RETIRED_NODES[@]}"; do + IFS=':' read -r node_id node_name reason <<< "$node_info" + + echo "处理节点: $node_name ($node_id)" + + # 检查节点状态 + if nomad node status "$node_id" >/dev/null 2>&1; then + echo " - 节点存在,开始清理..." + + # 确保节点已 drain + echo " - 确保节点已 drain..." + nomad node drain -enable -yes "$node_id" || true + + # 禁用调度 + echo " - 禁用调度资格..." + nomad node eligibility -disable "$node_id" || true + + # 等待一段时间确保所有任务已迁移 + echo " - 等待任务迁移完成..." + sleep 10 + + echo " - 节点 $node_name 已成功清理" + else + echo " - 节点不存在或已被清理" + fi + echo "" +done + +echo "=== 清理完成 ===" +echo "请手动验证集群状态:" +echo " nomad node status" +echo " nomad server members" +echo "" +echo "如需彻底删除节点记录,请联系管理员" \ No newline at end of file diff --git a/purge_stale_nodes.sh b/scripts/utilities/purge_stale_nodes.sh similarity index 100% rename from purge_stale_nodes.sh rename to scripts/utilities/purge_stale_nodes.sh