[add] add skill-L10n
This commit is contained in:
@@ -1 +1,560 @@
|
||||
Your SKILL.md content goes here.
|
||||
---
|
||||
名称:helm-chart-scaffolding
|
||||
描述:设计、组织和管理 Helm 图表,用于对 Kubernetes 应用程序进行模板化和打包,同时使用可重复使用的配置。在创建 Helm 图表、打包 Kubernetes 应用程序或实现模板化部署时使用。
|
||||
---
|
||||
|
||||
|
||||
# Helm 图表模板生成
|
||||
|
||||
有关为打包和部署 Kubernetes 应用程序创建、组织和管理 Helm 图表的全面指南。
|
||||
|
||||
## 目的
|
||||
|
||||
此技能提供了构建可投入生产的 Helm 图表的分步说明,包括图表结构、模板模式、值管理以及验证策略。
|
||||
|
||||
## 何时使用此技能
|
||||
|
||||
在您需要以下情况时使用此技能:
|
||||
|
||||
- 从零开始创建新的 Helm 图表
|
||||
- 将 Kubernetes 应用程序打包以进行分发
|
||||
- 使用 Helm 管理多环境部署
|
||||
- 为可重用的 Kubernetes 清单实现模板化
|
||||
- 设置 Helm 图表仓库
|
||||
- 遵循 Helm 最佳实践和约定
|
||||
|
||||
## Helm 概述
|
||||
|
||||
**Helm** 是 Kubernetes 的包管理器:
|
||||
|
||||
- 为可重用性创建 Kubernetes 清单模板
|
||||
- 管理应用程序的发布和回滚
|
||||
- 处理图表之间的依赖关系
|
||||
- 为部署提供版本控制
|
||||
- 简化跨环境的配置管理
|
||||
|
||||
## 分步工作流程
|
||||
|
||||
### 1. 初始化图表结构
|
||||
|
||||
**创建新图表:**
|
||||
|
||||
```bash
|
||||
helm create my-app
|
||||
```
|
||||
|
||||
**标准图表结构:**
|
||||
|
||||
```
|
||||
my-app/
|
||||
├── Chart.yaml # 图表元数据
|
||||
├── values.yaml # 默认配置值
|
||||
├── charts/ # 图表依赖
|
||||
├── templates/ # Kubernetes 清单模板
|
||||
│ ├── NOTES.txt # 安装后说明
|
||||
│ ├── _helpers.tpl # 模板辅助函数
|
||||
│ ├── deployment.yaml
|
||||
│ ├── service.yaml
|
||||
│ ├── ingress.yaml
|
||||
│ ├── serviceaccount.yaml
|
||||
│ ├── hpa.yaml
|
||||
│ └── tests/
|
||||
│ └── test-connection.yaml
|
||||
└── .helmignore # 忽略的文件
|
||||
```
|
||||
|
||||
### 2. 配置 Chart.yaml
|
||||
|
||||
**图表元数据定义了包:**
|
||||
|
||||
```yaml
|
||||
apiVersion: v2
|
||||
name: my-app
|
||||
description: 一个用于我的应用程序的 Helm 图表
|
||||
type: application
|
||||
version: 1.0.0 # 图表版本
|
||||
appVersion: "2.1.0" # 应用程序版本
|
||||
|
||||
# 图表发现关键字
|
||||
keywords:
|
||||
- web
|
||||
- api
|
||||
- backend
|
||||
|
||||
# 维护者信息
|
||||
maintainers:
|
||||
- name: yanghuajun336
|
||||
email: yanghuajun336@example.com
|
||||
url: https://github.com/yanghuajun336/my-app
|
||||
|
||||
# 源码仓库
|
||||
sources:
|
||||
- https://github.com/yanghuajun336/my-app
|
||||
|
||||
# 主页
|
||||
home: https://github.com/yanghuajun336
|
||||
|
||||
# 图表图标
|
||||
icon: https://example.com/icon.png
|
||||
|
||||
# 依赖
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: "12.0.0"
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
condition: postgresql.enabled
|
||||
- name: redis
|
||||
version: "17.0.0"
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
condition: redis.enabled
|
||||
```
|
||||
|
||||
**参考:** 请参阅 `assets/Chart.yaml.template` 获取完整示例
|
||||
|
||||
### 3. 设计 values.yaml 结构
|
||||
|
||||
**按层次组织值:**
|
||||
```yaml
|
||||
# 镜像配置
|
||||
image:
|
||||
repository: myapp
|
||||
tag: "1.0.0"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
# 副本数量
|
||||
replicaCount: 3
|
||||
|
||||
# Service 配置
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
|
||||
# Ingress 配置
|
||||
ingress:
|
||||
enabled: false
|
||||
className: nginx
|
||||
hosts:
|
||||
- host: app.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
|
||||
# 资源限制
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# 自动扩缩容
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
targetCPUUtilizationPercentage: 80
|
||||
|
||||
# 环境变量
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: "info"
|
||||
|
||||
# ConfigMap 数据
|
||||
configMap:
|
||||
data:
|
||||
APP_MODE: production
|
||||
|
||||
# 依赖
|
||||
postgresql:
|
||||
enabled: true
|
||||
auth:
|
||||
database: myapp
|
||||
username: myapp
|
||||
|
||||
redis:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
**参考:** 请参阅 `assets/values.yaml.template` 获取完整结构
|
||||
|
||||
### 4. 创建模板文件
|
||||
|
||||
**使用 Go 模板语法结合 Helm 函数:**
|
||||
|
||||
**templates/deployment.yaml:**
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "my-app.fullname" . }}
|
||||
labels:
|
||||
{{- include "my-app.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not .Values.autoscaling.enabled }}
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "my-app.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "my-app.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.targetPort }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
env:
|
||||
{{- toYaml .Values.env | nindent 12 }}
|
||||
```
|
||||
|
||||
### 5. 创建模板辅助函数
|
||||
|
||||
**templates/\_helpers.tpl:**
|
||||
|
||||
```yaml
|
||||
{{/*
|
||||
展开图表名称。
|
||||
*/}}
|
||||
{{- define "my-app.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
创建默认的完整限定应用名称。
|
||||
*/}}
|
||||
{{- define "my-app.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
通用标签
|
||||
*/}}
|
||||
{{- define "my-app.labels" -}}
|
||||
helm.sh/chart: {{ include "my-app.chart" . }}
|
||||
{{ include "my-app.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
选择器标签
|
||||
*/}}
|
||||
{{- define "my-app.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "my-app.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
### 6. 管理依赖
|
||||
|
||||
**在 Chart.yaml 中添加依赖:**
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
- name: postgresql
|
||||
version: "12.0.0"
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
condition: postgresql.enabled
|
||||
```
|
||||
|
||||
**更新依赖:**
|
||||
|
||||
```bash
|
||||
helm dependency update
|
||||
helm dependency build
|
||||
```
|
||||
|
||||
**覆盖依赖的配置值:**
|
||||
|
||||
```yaml
|
||||
# values.yaml
|
||||
postgresql:
|
||||
enabled: true
|
||||
auth:
|
||||
database: myapp
|
||||
username: myapp
|
||||
password: changeme
|
||||
primary:
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 10Gi
|
||||
```
|
||||
|
||||
### 7. 测试与验证
|
||||
|
||||
**验证命令:**
|
||||
|
||||
```bash
|
||||
# 检查图表语法
|
||||
helm lint my-app/
|
||||
|
||||
# 模拟安装(不实际部署)
|
||||
helm install my-app ./my-app --dry-run --debug
|
||||
|
||||
# 渲染模板
|
||||
helm template my-app ./my-app
|
||||
|
||||
# 使用指定值文件渲染模板
|
||||
helm template my-app ./my-app -f values-prod.yaml
|
||||
|
||||
# 显示计算后的值
|
||||
helm show values ./my-app
|
||||
```
|
||||
|
||||
**验证脚本:**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "正在检查图表语法..."
|
||||
helm lint .
|
||||
|
||||
echo "正在测试模板渲染..."
|
||||
helm template test-release . --dry-run
|
||||
|
||||
echo "正在检查必填值..."
|
||||
helm template test-release . --validate
|
||||
|
||||
echo "所有验证均已通过!"
|
||||
```
|
||||
|
||||
**参考:** 请参阅 `scripts/validate-chart.sh`
|
||||
|
||||
### 8. 打包与分发
|
||||
|
||||
**打包图表:**
|
||||
|
||||
```bash
|
||||
helm package my-app/
|
||||
# 生成文件:my-app-1.0.0.tgz
|
||||
```
|
||||
|
||||
**创建图表仓库:**
|
||||
|
||||
```bash
|
||||
# 生成索引文件
|
||||
helm repo index .
|
||||
|
||||
# 上传到仓库
|
||||
# AWS S3 示例
|
||||
aws s3 sync . s3://my-helm-charts/ --exclude "*" --include "*.tgz" --include "index.yaml"
|
||||
```
|
||||
|
||||
**使用图表:**
|
||||
|
||||
```bash
|
||||
helm repo add my-repo https://charts.example.com
|
||||
helm repo update
|
||||
helm install my-app my-repo/my-app
|
||||
```
|
||||
|
||||
### 9. 多环境配置
|
||||
|
||||
**针对不同环境的值文件:**
|
||||
|
||||
```
|
||||
my-app/
|
||||
├── values.yaml # 默认配置
|
||||
├── values-dev.yaml # 开发环境
|
||||
├── values-staging.yaml # 预发布环境
|
||||
└── values-prod.yaml # 生产环境
|
||||
```
|
||||
|
||||
**values-prod.yaml:**
|
||||
|
||||
```yaml
|
||||
replicaCount: 5
|
||||
|
||||
image:
|
||||
tag: "2.1.0"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 3
|
||||
maxReplicas: 20
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
hosts:
|
||||
- host: app.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
|
||||
postgresql:
|
||||
enabled: true
|
||||
primary:
|
||||
persistence:
|
||||
size: 100Gi
|
||||
```
|
||||
|
||||
**使用环境值文件安装:**
|
||||
|
||||
```bash
|
||||
helm install my-app ./my-app -f values-prod.yaml --namespace production
|
||||
```
|
||||
|
||||
### 10. 实现 Hook 与测试
|
||||
|
||||
**安装前 Hook:**
|
||||
|
||||
```yaml
|
||||
# templates/pre-install-job.yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ include "my-app.fullname" . }}-db-setup
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install
|
||||
"helm.sh/hook-weight": "-5"
|
||||
"helm.sh/hook-delete-policy": hook-succeeded
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: db-setup
|
||||
image: postgres:15
|
||||
command: ["psql", "-c", "CREATE DATABASE myapp"]
|
||||
restartPolicy: Never
|
||||
```
|
||||
|
||||
**测试连接:**
|
||||
|
||||
```yaml
|
||||
# templates/tests/test-connection.yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "my-app.fullname" . }}-test-connection"
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "my-app.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
||||
```
|
||||
|
||||
**运行测试:**
|
||||
|
||||
```bash
|
||||
helm test my-app
|
||||
```
|
||||
|
||||
## 常用模式
|
||||
|
||||
### 模式 1:条件化资源
|
||||
|
||||
```yaml
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "my-app.fullname" . }}
|
||||
spec:
|
||||
# ...
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
### 模式 2:遍历列表
|
||||
|
||||
```yaml
|
||||
env:
|
||||
{{- range .Values.env }}
|
||||
- name: {{ .name }}
|
||||
value: {{ .value | quote }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
### 模式 3:引入外部文件
|
||||
|
||||
```yaml
|
||||
data:
|
||||
config.yaml: |
|
||||
{{- .Files.Get "config/application.yaml" | nindent 4 }}
|
||||
```
|
||||
|
||||
### 模式 4:全局值
|
||||
|
||||
```yaml
|
||||
global:
|
||||
imageRegistry: docker.io
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
|
||||
# 在模板中使用:
|
||||
image: {{ .Values.global.imageRegistry }}/{{ .Values.image.repository }}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **使用语义化版本号** 管理图表和应用版本
|
||||
2. **为所有配置项添加注释** 在 values.yaml 中
|
||||
3. **使用模板辅助函数** 处理重复逻辑
|
||||
4. **打包前验证图表** 确保无误
|
||||
5. **明确固定依赖版本号**
|
||||
6. **为可选资源使用条件判断**
|
||||
7. **遵循命名规范**(小写字母,连字符分隔)
|
||||
8. **在 NOTES.txt 中** 提供使用说明
|
||||
9. **通过辅助函数** 统一添加标签
|
||||
10. **在所有环境中** 测试安装流程
|
||||
|
||||
## 故障排查
|
||||
|
||||
**模板渲染错误:**
|
||||
|
||||
```bash
|
||||
helm template my-app ./my-app --debug
|
||||
```
|
||||
|
||||
**依赖问题:**
|
||||
|
||||
```bash
|
||||
helm dependency update
|
||||
helm dependency list
|
||||
```
|
||||
|
||||
**安装失败:**
|
||||
|
||||
```bash
|
||||
helm install my-app ./my-app --dry-run --debug
|
||||
kubectl get events --sort-by='.lastTimestamp'
|
||||
```
|
||||
|
||||
|
||||
## 相关技能
|
||||
|
||||
- `k8s-manifest-generator` - 用于创建基础 Kubernetes 清单
|
||||
- `gitops-workflow` - 用于自动化 Helm 图表部署
|
||||
|
||||
79
skills/yanghuajun336/skill-L10n/SKILL.md
Normal file
79
skills/yanghuajun336/skill-L10n/SKILL.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: skill-L10n
|
||||
description: skill-L10n 是一个面向 Agent Skill 的语境感知本地化工具,主要用于将英文的 Agent skill 文档(尤其 `SKILL.md`)和参考文件翻译为中文,同时按需翻译脚本中的注释。
|
||||
---
|
||||
|
||||
|
||||
# skill-L10n
|
||||
|
||||
## 简介
|
||||
----
|
||||
skill-L10n 是面向 Agent Skill 的语境感知本地化工具。它以 SKILL.md 为优先目标,结合上下文判断哪些段落需要翻译,并对 references 中的说明与 scripts 目录下的脚本注释进行有选择性的翻译。目标是尽量保留代码示例、参数名与命令行示例不被误翻,便于审阅与回滚。
|
||||
|
||||
## 主要特性
|
||||
---
|
||||
- 针对 Markdown / SKILL.md:段落级上下文判断(前后段落一并作为上下文),仅翻译模型判定为“用户可读/说明性文本”的段落。保留 code fence、表格、frontmatter、inline code。
|
||||
- 针对脚本/代码文件:只处理注释行;默认保留原注释并在其下追加一行翻译(与原注释使用相同的注释前缀和缩进)。
|
||||
- 可配置证书校验:通过环境变量 `SKILL_L10N_VERIFY` 控制 http 客户端的 SSL 验证(默认 false,以兼容企业自签/代理环境)。
|
||||
- 简单缓存:避免对完全相同文本重复调用翻译 API,节省调用成本。
|
||||
- 变更报告:为每个被修改的文件生成 unified diff(.diff)报告,便于审阅与回滚。
|
||||
- 错误容忍:翻译失败时脚本会跳过并保留原文,同时在报告中标注(不会中断批量处理)。
|
||||
|
||||
## 快速开始
|
||||
---
|
||||
1. 安装依赖(示例):
|
||||
```bash
|
||||
python3 -m pip install --upgrade openai httpx
|
||||
```
|
||||
|
||||
2. 设置环境变量(必须):
|
||||
```bash
|
||||
export DEEPSEEK_API_TOKEN="your_token_here"
|
||||
# 默认为 false,若在安全公网环境或已配<E5B7B2><E9858D>受信根证书可设为 true
|
||||
export SKILL_L10N_VERIFY=false
|
||||
```
|
||||
|
||||
3. 运行(示例):
|
||||
```bash
|
||||
# 在 skill-L10n 目录下对某个 skill 进行本地化并把 diff 报告写入 ./reports
|
||||
python3 scripts/skill_l10n.py ./skills/your-skill ./reports
|
||||
|
||||
# 或使用包装脚本
|
||||
scripts/run.sh ./skills/your-skill ./reports
|
||||
```
|
||||
|
||||
## CLI 参数与模式
|
||||
---------------
|
||||
- `--src`:源语言(默认 `auto`)
|
||||
- `--tgt`:目标语言(默认 `zh`)
|
||||
- `--mode`:全局 Markdown 模式:`replace`(替换,默认)或 `append`(在原文后追加译文)
|
||||
- 注意:代码文件注释的默认处理遵循 `--preserve-original-for-code` 设置(见下)。
|
||||
- `--preserve-original-for-code`:`yes`(默认)或 `no`。`yes` 时在原注释下追加翻译行;`no` 时直接替换注释。
|
||||
- 环境变量 `SKILL_L10N_VERIFY`:`true` 或 `false`(默认 false)。控制 httpx.Client(verify=... )。
|
||||
|
||||
## 翻译策略与细节
|
||||
----------------
|
||||
- Markdown / SKILL.md:
|
||||
- 脚本会把正文按段落拆分,隐藏并保留 code fence,再对每个 prose 段落连同前后上下文询问模型“是否翻译 + 翻译<E7BFBB><E8AF91>果”。
|
||||
- inline code(如 `my_func()`)、参数名、命令行示例与表格项通常被保护不翻译。
|
||||
- 默认行为:替换(即不保留英文原文)。可通过 `--mode append` 改为在原文后追加译文。
|
||||
|
||||
- 脚本与代码文件:
|
||||
- 仅对注释行翻译决策。若模型判定需要翻译且 `--preserve-original-for-code=yes`(默认),则保留原注释并在下一行添加同样注释前缀的译文(保持缩进)。
|
||||
- 示例:
|
||||
```python
|
||||
# This function adds two numbers
|
||||
# 此函数用于将两个数字相加
|
||||
def add(a, b):
|
||||
return a + b
|
||||
```
|
||||
|
||||
## 缓存与成本控制
|
||||
---
|
||||
- 内存级缓存用于避免相同文本的重复请求。对于大规模仓库建议先在小规模 demo 上跑通并查看 diff,再批量执行,控制 API 成本。
|
||||
|
||||
## 安全与注意事项
|
||||
---
|
||||
- 默认 `SKILL_L10N_VERIFY=false`,以兼容一些企业环境(自签或代理)。强烈建议在公网或生产环境将 `SKILL_L10N_VERIFY=true` 并确保系统根证书链完整。
|
||||
- 请勿在代码中明文保存 API Token,使用环境变量(DEEPSEEK_API_TOKEN)。
|
||||
- 脚本会原地修改目标目录下文件,请在 git 仓库中使用分支/暂存区进行变更,或先备份目标目录。每个修改会在 `report_dir` 生成 `.diff` 报告,便于审阅与回滚。
|
||||
@@ -0,0 +1,71 @@
|
||||
# skills-translator Examples
|
||||
|
||||
## 1. 简单 Python 注释
|
||||
### Before
|
||||
```python
|
||||
# This function adds two numbers
|
||||
def add(a, b):
|
||||
return a + b
|
||||
```
|
||||
### After
|
||||
```python
|
||||
# This function adds two numbers
|
||||
# 此函数用于将两个数字相加
|
||||
def add(a, b):
|
||||
return a + b
|
||||
```
|
||||
|
||||
## 2. YAML 注释(前后对比)
|
||||
### Before
|
||||
```yaml
|
||||
# Default values for my chart.
|
||||
replicaCount: 1
|
||||
```
|
||||
### After
|
||||
```yaml
|
||||
# Default values for my chart.
|
||||
# 我的 chart 的默认值。
|
||||
replicaCount: 1
|
||||
```
|
||||
|
||||
## 3. SKILL.md 多段落示例(上下文判断)
|
||||
### Before
|
||||
```markdown
|
||||
# My Skill
|
||||
|
||||
This skill fetches data from the API and returns a parsed object.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
curl -X GET "https://api.example.com/data?limit=10"
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Keep your API key secret.
|
||||
```
|
||||
### After (示例)
|
||||
```markdown
|
||||
# My Skill
|
||||
|
||||
此技能从 API 获取数据并返回解析后的对象。
|
||||
|
||||
Example:
|
||||
```bash
|
||||
curl -X GET "https://api.example.com/data?limit=10"
|
||||
```
|
||||
|
||||
Notes:
|
||||
- 将你的 API key 保管好。
|
||||
```
|
||||
|
||||
## 4. 多段落上下文敏感 (演示保持命名与代码不翻译)
|
||||
### Before
|
||||
```markdown
|
||||
### Usage
|
||||
Call `my_skill.run()` to execute the skill. The parameter `user_id` accepts integer IDs.
|
||||
```
|
||||
### After
|
||||
```markdown
|
||||
### Usage
|
||||
调用 `my_skill.run()` 来执行该 skill。参数 `user_id` 接受整数 ID。
|
||||
```
|
||||
9
skills/yanghuajun336/skill-L10n/script/run.sh
Normal file
9
skills/yanghuajun336/skill-L10n/script/run.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# run.sh - wrapper for skill_l10n.py
|
||||
# Usage: ./run.sh <target_dir> <report_dir> [--src en] [--tgt zh]
|
||||
set -e
|
||||
TARGET="$1"
|
||||
REPORT="$2"
|
||||
shift 2 || true
|
||||
export SKILL_L10N_VERIFY=${SKILL_L10N_VERIFY:-false}
|
||||
python3 "$(dirname "$0")/skill_l10n.py" "$TARGET" "$REPORT" "$@"
|
||||
304
skills/yanghuajun336/skill-L10n/script/skill_l10n.py
Normal file
304
skills/yanghuajun336/skill-L10n/script/skill_l10n.py
Normal file
@@ -0,0 +1,304 @@
|
||||
#!/usr/bin/env python3
|
||||
# name=skill_l10n.py
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
skill_l10n.py
|
||||
Context-aware localization tool for Agent Skills (SKILL.md + references + scripts).
|
||||
Usage:
|
||||
python skill_l10n.py <target_dir> <report_dir> [--src LANG] [--tgt LANG] [--mode replace|append] [--preserve-original-for-code yes|no]
|
||||
|
||||
Environment:
|
||||
DEEPSEEK_API_TOKEN - required
|
||||
SKILL_L10N_VERIFY - 'true' or 'false' (default 'false')
|
||||
Dependencies:
|
||||
pip install openai httpx
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import argparse
|
||||
import difflib
|
||||
from pathlib import Path
|
||||
from typing import Dict, Tuple, Optional
|
||||
|
||||
from openai import OpenAI
|
||||
import httpx
|
||||
|
||||
# -----------------------
|
||||
# Configuration defaults
|
||||
# -----------------------
|
||||
DEFAULT_MODEL = "deepseek-chat"
|
||||
CACHE_TTL = 24 * 3600 # 1 day
|
||||
|
||||
# -----------------------
|
||||
# Translator wrapper
|
||||
# -----------------------
|
||||
class SmartTranslator:
|
||||
def __init__(self, token_env="DEEPSEEK_API_TOKEN", verify_ssl: bool = False, model=DEFAULT_MODEL):
|
||||
self.token = os.getenv(token_env, "")
|
||||
if not self.token:
|
||||
raise ValueError(f"Please set environment variable {token_env}")
|
||||
self.verify = verify_ssl
|
||||
self._httpx_client = httpx.Client(verify=self.verify)
|
||||
self._client = OpenAI(base_url="https://api.deepseek.com/v1", api_key=self.token, http_client=self._httpx_client)
|
||||
self.model = model
|
||||
self.cache: Dict[str, Tuple[float, dict]] = {}
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
self._httpx_client.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _cache_get(self, key: str) -> Optional[dict]:
|
||||
ent = self.cache.get(key)
|
||||
if not ent:
|
||||
return None
|
||||
ts, val = ent
|
||||
if time.time() - ts > CACHE_TTL:
|
||||
del self.cache[key]
|
||||
return None
|
||||
return val
|
||||
|
||||
def _cache_set(self, key: str, val: dict):
|
||||
self.cache[key] = (time.time(), val)
|
||||
|
||||
def decide_and_translate(self, paragraph: str, context: str = "", source_language="auto", target_language="zh") -> dict:
|
||||
"""
|
||||
Ask model to decide whether to translate and return translation if should_translate.
|
||||
Return dict:
|
||||
{ should_translate: bool, translated_text: str, reason: str }
|
||||
"""
|
||||
key = f"decide::{source_language}::{target_language}::{paragraph}::ctx::{context}"
|
||||
cached = self._cache_get(key)
|
||||
if cached:
|
||||
return cached
|
||||
system_msg = (
|
||||
"You are a careful translator and content reviewer. "
|
||||
"Given a paragraph and surrounding context, return a JSON object with keys: "
|
||||
"should_translate (boolean), translated_text (string), reason (string). "
|
||||
"Do NOT translate code, CLI examples, file names, parameter names, or inline code. "
|
||||
"If you translate, preserve technical tokens and inline code unchanged."
|
||||
)
|
||||
user_msg = f"Context:\n{context}\n\nParagraph:\n{paragraph}\n\nSource: {source_language}\nTarget: {target_language}"
|
||||
try:
|
||||
completion = self._client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=[
|
||||
{"role": "system", "content": system_msg},
|
||||
{"role": "user", "content": user_msg},
|
||||
],
|
||||
)
|
||||
raw = completion.choices[0].message.content.strip()
|
||||
# try extract JSON
|
||||
first = raw.find("{")
|
||||
last = raw.rfind("}")
|
||||
json_text = raw[first:last+1] if first != -1 and last != -1 else raw
|
||||
data = json.loads(json_text)
|
||||
result = {
|
||||
"should_translate": bool(data.get("should_translate")),
|
||||
"translated_text": data.get("translated_text", "") or "",
|
||||
"reason": data.get("reason", "") or "",
|
||||
}
|
||||
except Exception as e:
|
||||
result = {"should_translate": False, "translated_text": "", "reason": f"api_error:{e}"}
|
||||
self._cache_set(key, result)
|
||||
return result
|
||||
|
||||
# -----------------------
|
||||
# File utilities
|
||||
# -----------------------
|
||||
MD_FENCE_RE = re.compile(r'```[\s\S]*?```', flags=re.MULTILINE)
|
||||
INLINE_CODE_RE = re.compile(r'`[^`]+`')
|
||||
FRONTMATTER_RE = re.compile(r'^(---\n[\s\S]*?\n---\n)', flags=re.MULTILINE)
|
||||
COMMENT_PATTERNS = [
|
||||
re.compile(r'^[ \t]*#'),
|
||||
re.compile(r'^[ \t]*//'),
|
||||
re.compile(r'^[ \t]*/\*'),
|
||||
re.compile(r'^[ \t]*\*'),
|
||||
re.compile(r'^[ \t]*--'),
|
||||
re.compile(r'^[ \t]*;'),
|
||||
re.compile(r'^[ \t]*REM ', re.I),
|
||||
re.compile(r'^[ \t]*<!--'),
|
||||
re.compile(r'^[ \t]*"""'),
|
||||
re.compile(r"^[ \t]*'''"),
|
||||
]
|
||||
MD_SUFFIXES = {'.md'}
|
||||
CODE_SUFFIXES = {'.py', '.js', '.ts', '.go', '.java', '.sh', '.yaml', '.yml'}
|
||||
SPECIAL_FILES = {'Dockerfile', 'SKILL.md', 'SKILL.MD'}
|
||||
|
||||
def is_comment_line(line: str) -> bool:
|
||||
return any(pat.match(line) for pat in COMMENT_PATTERNS)
|
||||
|
||||
def safe_report_name(path: Path) -> str:
|
||||
s = str(path).lstrip(os.sep)
|
||||
return s.replace(os.sep, '-')
|
||||
|
||||
def write_diff_report(path: Path, orig_lines, new_lines, report_root: Path):
|
||||
report_root.mkdir(parents=True, exist_ok=True)
|
||||
rp = report_root / (safe_report_name(path) + ".diff")
|
||||
diff = difflib.unified_diff(orig_lines, new_lines, fromfile="original", tofile="localized", lineterm="")
|
||||
rp.write_text("\n".join(diff), encoding='utf-8')
|
||||
|
||||
# -----------------------
|
||||
# Markdown: paragraph-level smart translation
|
||||
# -----------------------
|
||||
def process_markdown(path: Path, translator: SmartTranslator, report_root: Path, src='auto', tgt='zh', mode='replace'):
|
||||
text = path.read_text(encoding='utf-8')
|
||||
front = ""
|
||||
fm = FRONTMATTER_RE.match(text)
|
||||
if fm:
|
||||
front = fm.group(1)
|
||||
body = text[len(front):]
|
||||
else:
|
||||
body = text
|
||||
# hide code fences
|
||||
fences = {}
|
||||
def fence_repl(m):
|
||||
key = f"__FENCE_{len(fences)}__"
|
||||
fences[key] = m.group(0)
|
||||
return key
|
||||
body_ph = MD_FENCE_RE.sub(fence_repl, body)
|
||||
# split into paragraphs (keeping separators)
|
||||
parts = re.split(r'(\n\s*\n)', body_ph)
|
||||
new_parts = []
|
||||
for i, part in enumerate(parts):
|
||||
if part.strip() == "" or part.startswith("__FENCE_"):
|
||||
new_parts.append(part)
|
||||
continue
|
||||
# remove inline code
|
||||
inlines = INLINE_CODE_RE.findall(part)
|
||||
placeholders = {}
|
||||
for j, c in enumerate(inlines):
|
||||
ph = f"__INLINE_{j}__"
|
||||
placeholders[ph] = c
|
||||
part = part.replace(c, ph, 1)
|
||||
# context: prev + next non-empty
|
||||
prev_ctx = ""
|
||||
next_ctx = ""
|
||||
j = i-1
|
||||
while j >= 0:
|
||||
if parts[j].strip():
|
||||
prev_ctx = parts[j].strip()
|
||||
break
|
||||
j -= 1
|
||||
k = i+1
|
||||
while k < len(parts):
|
||||
if parts[k].strip():
|
||||
next_ctx = parts[k].strip()
|
||||
break
|
||||
k += 1
|
||||
ctx = "\n\n".join([c for c in (prev_ctx, next_ctx) if c])
|
||||
decision = translator.decide_and_translate(part.strip(), context=ctx, source_language=src, target_language=tgt)
|
||||
if decision.get("should_translate"):
|
||||
translated = decision.get("translated_text", "").strip()
|
||||
# restore inline placeholders
|
||||
for ph, c in placeholders.items():
|
||||
translated = translated.replace(ph, c)
|
||||
# how to insert depends on mode
|
||||
if mode == 'append':
|
||||
new_parts.append(part + "\n\n" + translated + "\n\n")
|
||||
else:
|
||||
new_parts.append(translated + "\n\n")
|
||||
else:
|
||||
new_parts.append(part)
|
||||
rebuilt = "".join(new_parts)
|
||||
# restore fences
|
||||
for k, v in fences.items():
|
||||
rebuilt = rebuilt.replace(k, v)
|
||||
final_text = front + rebuilt
|
||||
orig_lines = text.splitlines(keepends=True)
|
||||
new_lines = final_text.splitlines(keepends=True)
|
||||
if orig_lines != new_lines:
|
||||
path.write_text(final_text, encoding='utf-8')
|
||||
write_diff_report(path, orig_lines, new_lines, report_root)
|
||||
|
||||
# -----------------------
|
||||
# Code files: translate comment lines only
|
||||
# -----------------------
|
||||
def process_code_file(path: Path, translator: SmartTranslator, report_root: Path, src='auto', tgt='zh', preserve_original_for_code=True):
|
||||
text = path.read_text(encoding='utf-8')
|
||||
lines = text.splitlines(keepends=True)
|
||||
new_lines = []
|
||||
modified = False
|
||||
for idx, line in enumerate(lines):
|
||||
if is_comment_line(line) and line.strip():
|
||||
prev_ctx = "".join(lines[max(0, idx-3):idx]).strip()
|
||||
next_ctx = "".join(lines[idx+1: idx+4]).strip()
|
||||
ctx = "\n".join([c for c in (prev_ctx, next_ctx) if c])
|
||||
decision = translator.decide_and_translate(line.strip(), context=ctx, source_language=src, target_language=tgt)
|
||||
if decision.get("should_translate"):
|
||||
translated = decision.get("translated_text", "").strip()
|
||||
# preserve indent and comment prefix
|
||||
m = re.match(r'^([ \t]*)([#\/\*]+[ \t]*)?(.*)$', line)
|
||||
indent = m.group(1) or ""
|
||||
prefix = m.group(2) or ""
|
||||
if preserve_original_for_code:
|
||||
new_lines.append(line)
|
||||
# add translated comment with same prefix
|
||||
new_lines.append(f"{indent}{prefix}{translated}\n")
|
||||
else:
|
||||
new_lines.append(f"{indent}{prefix}{translated}\n")
|
||||
modified = True
|
||||
else:
|
||||
new_lines.append(line)
|
||||
else:
|
||||
new_lines.append(line)
|
||||
if modified:
|
||||
write_diff_report(path, lines, new_lines, report_root)
|
||||
path.write_text("".join(new_lines), encoding='utf-8')
|
||||
|
||||
# -----------------------
|
||||
# Walk files
|
||||
# -----------------------
|
||||
def find_target_files(root: Path):
|
||||
files = []
|
||||
for p in root.rglob('*'):
|
||||
if p.is_file():
|
||||
if p.name in SPECIAL_FILES or p.suffix in MD_SUFFIXES or p.suffix in CODE_SUFFIXES:
|
||||
files.append(p)
|
||||
return files
|
||||
|
||||
# -----------------------
|
||||
# CLI
|
||||
# -----------------------
|
||||
def parse_args():
|
||||
p = argparse.ArgumentParser(description="skill-L10n: context-aware localization for Agent skills")
|
||||
p.add_argument("target_dir", help="target directory to localize")
|
||||
p.add_argument("report_dir", help="directory to write diff reports")
|
||||
p.add_argument("--src", default="auto", help="source language (default auto)")
|
||||
p.add_argument("--tgt", default="zh", help="target language (default zh)")
|
||||
p.add_argument("--mode", choices=["replace", "append"], default=None, help="global mode for markdown. code files follow preserve flag")
|
||||
p.add_argument("--preserve-original-for-code", choices=["yes", "no"], default=None,
|
||||
help="whether to keep original comments for code files (default yes)")
|
||||
return p.parse_args()
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
target = Path(args.target_dir)
|
||||
report = Path(args.report_dir)
|
||||
report.mkdir(parents=True, exist_ok=True)
|
||||
verify_env = os.getenv("SKILL_L10N_VERIFY", "false").lower() == "true"
|
||||
preserve_code = True if (args.preserve_original_for_code is None) else (args.preserve_original_for_code == "yes")
|
||||
markdown_mode = "replace" if args.mode is None else args.mode
|
||||
|
||||
translator = SmartTranslator(verify_ssl=verify_env)
|
||||
try:
|
||||
files = find_target_files(target)
|
||||
for f in files:
|
||||
try:
|
||||
# treat SKILL.md or *.md as markdown
|
||||
if f.name.lower() == "skill.md" or f.suffix.lower() == ".md":
|
||||
mode = markdown_mode
|
||||
process_markdown(f, translator, report, src=args.src, tgt=args.tgt, mode=mode)
|
||||
else:
|
||||
process_code_file(f, translator, report, src=args.src, tgt=args.tgt, preserve_original_for_code=preserve_code)
|
||||
except Exception as fe:
|
||||
print(f"[WARN] failed file {f}: {fe}", file=sys.stderr)
|
||||
finally:
|
||||
translator.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
269
skills/yanghuajun336/tencentcloud-terraform/SKILL.md
Normal file
269
skills/yanghuajun336/tencentcloud-terraform/SKILL.md
Normal file
@@ -0,0 +1,269 @@
|
||||
---
|
||||
name: tencentcloud-terraform
|
||||
description: 腾讯云 Terraform IaC 管理。当用户说"创建/管理腾讯云资源"、"用 Terraform 写 tf 文件"、"terraform init/plan/apply"、"搭建腾讯云 VPC/CVM/TKE/COS/CLB/MySQL"、"多环境 IaC 管理"、"State 管理"等场景时激活本 skill。
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# 腾讯云 Terraform IaC 管理
|
||||
|
||||
使用官方 Provider [`tencentcloudstack/tencentcloud`](https://github.com/tencentcloudstack/terraform-provider-tencentcloud) 对腾讯云基础设施进行完整的代码化管理。
|
||||
|
||||
## 使用前检查
|
||||
|
||||
执行任何操作前先收集环境状态:
|
||||
|
||||
```bash
|
||||
# 1. 检查 Terraform 版本(需要 >= 1.5.0)
|
||||
terraform version
|
||||
|
||||
# 2. 检查腾讯云凭证
|
||||
printenv | grep TENCENTCLOUD
|
||||
|
||||
# 3. 当前目录是否已有 tf 项目
|
||||
ls -la *.tf 2>/dev/null && ls -la .terraform 2>/dev/null || echo "新项目"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1:认证配置
|
||||
|
||||
参考:[📋 Provider 配置](./reference/provider.md)
|
||||
|
||||
### 优先使用环境变量(推荐所有环境)
|
||||
|
||||
```bash
|
||||
export TENCENTCLOUD_SECRET_ID="AKIDxxxxxxxxxxxxxxxxx"
|
||||
export TENCENTCLOUD_SECRET_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
export TENCENTCLOUD_REGION="ap-guangzhou"
|
||||
```
|
||||
|
||||
### Provider 块(`versions.tf`,必须)
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
required_providers {
|
||||
tencentcloud = {
|
||||
source = "tencentcloudstack/tencentcloud"
|
||||
version = "~> 1.81.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "tencentcloud" {
|
||||
region = var.region
|
||||
# secret_id / secret_key 通过环境变量注入,不写入代码
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ **绝对禁止**将 `secret_id`/`secret_key` 明文写入 `.tf` 文件并提交 Git。
|
||||
|
||||
---
|
||||
|
||||
## Phase 2:项目结构
|
||||
|
||||
### 标准单环境结构
|
||||
|
||||
```
|
||||
project/
|
||||
├── versions.tf # Provider 版本锁定
|
||||
├── backend.tf # 远端状态(COS)
|
||||
├── main.tf # 主资源
|
||||
├── variables.tf # 变量声明
|
||||
├── outputs.tf # 输出值
|
||||
├── locals.tf # 本地计算值 & 公共 Tags
|
||||
├── terraform.tfvars # 变量值(加入 .gitignore)
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
### 多环境推荐结构
|
||||
|
||||
```
|
||||
project/
|
||||
├── modules/
|
||||
│ ├── vpc/ # VPC + 子网模块
|
||||
│ ├── cvm/ # CVM 实例模块
|
||||
│ ├── tke/ # TKE 集群模块
|
||||
│ └── cos/ # COS Bucket 模块
|
||||
└── envs/
|
||||
├── dev/
|
||||
│ ├── main.tf
|
||||
│ ├── backend.tf
|
||||
│ └── terraform.tfvars
|
||||
├── staging/
|
||||
└── prod/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3:核心资源编写
|
||||
|
||||
根据需要加载对应参考文档:
|
||||
|
||||
- [🌐 VPC & 网络](./reference/vpc-networking.md):VPC、子网、安全组、路由表、NAT、EIP
|
||||
- [💻 计算 CVM](./reference/compute.md):CVM 实例、镜像、密钥对、弹性伸缩
|
||||
- [📦 存储 COS](./reference/storage.md):COS Bucket、生命周期、CORS、CDN
|
||||
- [☸️ 容器 TKE](./reference/container-tke.md):TKE 集群、节点池、托管 Master
|
||||
- [🗄️ 数据库](./reference/database.md):CynosDB MySQL、Redis、MongoDB
|
||||
- [🌐 dns](./reference/dnspod.md):CynosDB MySQL、Redis、MongoDB
|
||||
### 公共 Tags(`locals.tf`)
|
||||
|
||||
```hcl
|
||||
locals {
|
||||
common_tags = {
|
||||
"Project" = var.project_name
|
||||
"Environment" = var.environment
|
||||
"ManagedBy" = "terraform"
|
||||
"Owner" = var.owner
|
||||
"CostCenter" = var.cost_center
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
所有资源必须携带 `tags = local.common_tags`,可用 `merge()` 追加资源级 Tag:
|
||||
|
||||
```hcl
|
||||
tags = merge(local.common_tags, { "Role" = "web" })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4:State 管理
|
||||
|
||||
参考:[🔒 State 管理](./reference/state-management.md)
|
||||
|
||||
### COS 远端 Backend(团队必须,`backend.tf`)
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
backend "cos" {
|
||||
region = "ap-guangzhou"
|
||||
bucket = "tfstate-<appid>-1234567890" # 替换为真实 bucket
|
||||
prefix = "terraform/state/<project>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**初始化 Backend:**
|
||||
|
||||
```bash
|
||||
# 首次初始化或切换 backend
|
||||
terraform init -reconfigure
|
||||
```
|
||||
|
||||
**多环境 State 隔离:**
|
||||
|
||||
```
|
||||
prefix = "terraform/state/dev/<project>" # dev
|
||||
prefix = "terraform/state/prod/<project>" # prod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5:标准工作流
|
||||
|
||||
```bash
|
||||
# 1. 初始化(首次或新增 provider/module 后<><E5908E><EFBFBD>
|
||||
terraform init
|
||||
|
||||
# 2. 格式化(提交前必须)
|
||||
terraform fmt -recursive
|
||||
|
||||
# 3. 语法检查
|
||||
terraform validate
|
||||
|
||||
# 4. 规划(查看变更,不执行)
|
||||
terraform plan -var-file=terraform.tfvars
|
||||
|
||||
# 5. 应用
|
||||
terraform apply -var-file=terraform.tfvars
|
||||
|
||||
# 6. CI/CD 中使用
|
||||
terraform plan -out=tfplan
|
||||
terraform apply tfplan
|
||||
|
||||
# 7. 销毁(危险!先 plan 确认)
|
||||
terraform plan -destroy
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6:State 操作
|
||||
|
||||
```bash
|
||||
# 查看已管理的资源列表
|
||||
terraform state list
|
||||
|
||||
# 查看资源详情
|
||||
terraform state show tencentcloud_vpc.main
|
||||
|
||||
# 导入已有腾讯云资源(不重新创建)
|
||||
terraform import tencentcloud_vpc.main vpc-xxxxxxxx
|
||||
terraform import tencentcloud_instance.web[0] ins-xxxxxxxx
|
||||
|
||||
# 重命名/移动资源(重构时)
|
||||
terraform state mv tencentcloud_instance.old tencentcloud_instance.new
|
||||
|
||||
# 从 State 移除(不删除真实资源)
|
||||
terraform state rm tencentcloud_instance.web[2]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 7:安全规范
|
||||
|
||||
1. **`.gitignore` 必须包含:**
|
||||
```gitignore
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
.terraform/
|
||||
*.tfvars
|
||||
!example.tfvars
|
||||
tfplan
|
||||
*.tfplan
|
||||
.terraform.lock.hcl # 可选,建议提交以锁定版本
|
||||
```
|
||||
|
||||
2. **Provider 版本锁定:** 提交 `.terraform.lock.hcl`
|
||||
3. **生产资源防误删:**
|
||||
```hcl
|
||||
lifecycle { prevent_destroy = true }
|
||||
```
|
||||
4. **敏感输出:**
|
||||
```hcl
|
||||
output "db_password" {
|
||||
value = random_password.db.result
|
||||
sensitive = true
|
||||
}
|
||||
```
|
||||
5. **最小权限:** 为 Terraform 子账号仅授予所需 CAM 策略
|
||||
|
||||
---
|
||||
|
||||
## 常见错误排查
|
||||
|
||||
| 错误 | 原因 | 解决 |
|
||||
|------|------|------|
|
||||
| `AuthFailure` | Secret ID/Key 错误或权限不足 | 检查环境变量和 CAM 权限 |
|
||||
| `ResourceExists` | 资源已存在于控制台 | `terraform import` 导入 |
|
||||
| `InvalidParameterValue` | 参数值不合法(如 az 名称) | 查阅 Provider 文档确认合法值 |
|
||||
| State Lock | 并发操作冲突 | `terraform force-unlock <ID>` |
|
||||
| `init` 失败 | Provider 下载超时 | 设置代理 `export HTTPS_PROXY=...` |
|
||||
y
|
||||
---
|
||||
|
||||
## 腾讯云地域速查
|
||||
|
||||
| 代码 | 说明 |
|
||||
|------|------|
|
||||
| `ap-guangzhou` | 华南-广州 |
|
||||
| `ap-beijing` | 华北-北京 |
|
||||
| `ap-shanghai` | 华东-上海 |
|
||||
| `ap-chengdu` | 西南-成都 |
|
||||
| `ap-nanjing` | 华东-南京 |
|
||||
| `ap-hongkong` | 港澳台-香港 |
|
||||
| `ap-singapore` | 东南亚-新加坡 |
|
||||
| `na-siliconvalley` | 美西-硅谷 |
|
||||
|
||||
官方 Provider 文档:https://registry.terraform.io/providers/tencentcloudstack/tencentcloud/latest/docs
|
||||
326
skills/yanghuajun336/tencentcloud-terraform/best-practics.md
Normal file
326
skills/yanghuajun336/tencentcloud-terraform/best-practics.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# 最佳实践(多环境 / 模块化 / CI-CD / 安全)
|
||||
|
||||
> 目录
|
||||
> - [项目目录结构](#项目目录结构)
|
||||
> - [变量规范](#变量规范)
|
||||
> - [locals 与公共 Tags](#locals-与公共-tags)
|
||||
> - [for_each vs count 选型](#for_each-vs-count)
|
||||
> - [模块化设计](#模块化设计)
|
||||
> - [多环境管理](#多环境管理)
|
||||
> - [安全规范清单](#安全规范清单)
|
||||
> - [CI/CD 集成(GitHub Actions)](#cicd-github-actions)
|
||||
> - [.gitignore 模板](#gitignore-模板)
|
||||
> - [代码风格约定](#代码风格约定)
|
||||
|
||||
---
|
||||
|
||||
## 项目目录结构
|
||||
|
||||
### 单环境(快速启动)
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── versions.tf # Provider 版本锁定(必须)
|
||||
├── backend.tf # COS 远端 State(必须)
|
||||
├── main.tf # 主资源入口
|
||||
├── locals.tf # 本地变量(Tags 等)
|
||||
├── variables.tf # 输入变量声明
|
||||
├── outputs.tf # 输出值
|
||||
├── terraform.tfvars # 变量值(加入 .gitignore)
|
||||
├── example.tfvars # 示例(可提交 Git,无敏感信息)
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
### 多环境(推荐生产)
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── modules/
|
||||
│ ├── network/ # VPC/子网/安全组/NAT
|
||||
│ │ ├── main.tf
|
||||
│ │ ├── variables.tf
|
||||
│ │ └── outputs.tf
|
||||
│ ├── compute/ # CVM/AS 伸缩组
|
||||
│ ├── database/ # MySQL/Redis
|
||||
│ └── tke/ # TKE 集群
|
||||
│
|
||||
├── envs/
|
||||
│ ├── dev/
|
||||
│ │ ├── main.tf # 调用 modules
|
||||
│ │ ├── backend.tf # dev 专用 backend prefix
|
||||
│ │ ├── versions.tf # 同根目录版本约束
|
||||
│ │ ├── variables.tf
|
||||
│ │ ├── outputs.tf
|
||||
│ │ └── terraform.tfvars # 不提交 Git
|
||||
│ ├── staging/
|
||||
│ └── prod/
|
||||
│
|
||||
└── .github/
|
||||
└── workflows/
|
||||
└── terraform.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 变量规范
|
||||
|
||||
```hcl
|
||||
# variables.tf 完整示例
|
||||
|
||||
# ── 必填变量(无 default)────────────────────────────────────
|
||||
variable "project_name" {
|
||||
description = "项目名称,用于所有资源命名前缀和 Tag。格式:小写字母+数字+连字符"
|
||||
type = string
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-z0-9][a-z0-9-]{1,18}[a-z0-9]$", var.project_name))
|
||||
error_message = "project_name 只能包含小写字母、数字和连字符,长度 3-20 位。"
|
||||
}
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "部署环境标识符"
|
||||
type = string
|
||||
|
||||
validation {
|
||||
condition = contains(["dev", "staging", "prod"], var.environment)
|
||||
error_message = "environment 必须是 dev、staging 或 prod。"
|
||||
}
|
||||
}
|
||||
|
||||
# ── 有默认值的变量 ────────────────────────────────────────────
|
||||
variable "region" {
|
||||
description = "腾讯云地域代码"
|
||||
type = string
|
||||
default = "ap-guangzhou"
|
||||
}
|
||||
|
||||
variable "owner" {
|
||||
description = "资源负责人(用于 Tag),通常是团队名或工单号"
|
||||
type = string
|
||||
default = "platform-team"
|
||||
}
|
||||
|
||||
variable "cost_center" {
|
||||
description = "成本中心(用于 Tag 和费用分摊)"
|
||||
type = string
|
||||
default = "engineering"
|
||||
}
|
||||
|
||||
# ── 敏感变量 ──────────────────────────────────────────────────
|
||||
variable "mysql_password" {
|
||||
description = "MySQL root 密码。通过 TF_VAR_mysql_password 注入,禁止写入 .tfvars 文件"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "ssh_public_key" {
|
||||
description = "SSH 公钥内容(ssh-rsa AAAA...),通过 TF_VAR_ssh_public_key 注入"
|
||||
type = string
|
||||
sensitive = false
|
||||
}
|
||||
|
||||
# ── 复杂类型变量 ──────────────────────────────────────────────
|
||||
variable "availability_zones" {
|
||||
description = "使用的可用区列表"
|
||||
type = list(string)
|
||||
default = ["ap-guangzhou-1", "ap-guangzhou-2"]
|
||||
}
|
||||
|
||||
variable "instance_count_by_env" {
|
||||
description = "各环境的实例数量"
|
||||
type = map(number)
|
||||
default = {
|
||||
dev = 1
|
||||
staging = 2
|
||||
prod = 4
|
||||
}
|
||||
}
|
||||
|
||||
# 使用:var.instance_count_by_env[var.environment]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## locals 与公共 Tags
|
||||
|
||||
```hcl
|
||||
# locals.tf
|
||||
|
||||
locals {
|
||||
# ── 公共 Tags(所有资源必须携带)──────────────────────────
|
||||
common_tags = {
|
||||
"Project" = var.project_name
|
||||
"Environment" = var.environment
|
||||
"ManagedBy" = "terraform"
|
||||
"Owner" = var.owner
|
||||
"CostCenter" = var.cost_center
|
||||
# "CreatedAt" = formatdate("YYYY-MM-DD", timestamp())
|
||||
# ⚠️ 不要用 timestamp(),每次 plan 都会产生 diff
|
||||
}
|
||||
|
||||
# ── 命名前缀(统一格式)──────────────────────────────────
|
||||
name_prefix = "${var.project_name}-${var.environment}"
|
||||
|
||||
# ── 环境相关计算 ────────<E29480><E29480><EFBFBD>───────────────────────────────
|
||||
is_prod = var.environment == "prod"
|
||||
instance_count = var.instance_count_by_env[var.environment]
|
||||
|
||||
# ── 可用区处理 ─────────────────────────────────────────
|
||||
az_count = length(var.availability_zones)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## for_each vs count
|
||||
|
||||
**选择原则:**
|
||||
|
||||
- 需要**稳定标识符**(删除/添加元素不影响其他)→ `for_each`
|
||||
- 纯**同质资源**,仅数量不同 → `count`
|
||||
|
||||
```hcl
|
||||
# ✅ 推荐:for_each,删除 web-02 不影响 web-01 和 web-03
|
||||
resource "tencentcloud_instance" "web" {
|
||||
for_each = toset(["web-01", "web-02", "web-03"])
|
||||
|
||||
instance_name = "${local.name_prefix}-${each.key}"
|
||||
# ...
|
||||
}
|
||||
|
||||
# ⚠️ 谨慎:count,删除索引 1(web-02)会导致 web-03 重建
|
||||
resource "tencentcloud_instance" "web" {
|
||||
count = 3
|
||||
|
||||
instance_name = "${local.name_prefix}-web-${format("%02d", count.index + 1)}"
|
||||
# ...
|
||||
}
|
||||
|
||||
# ✅ 子网 for_each(map 形式,包含元数据)
|
||||
variable "subnets" {
|
||||
default = {
|
||||
"pub-gz1" = { cidr = "10.0.1.0/24", az = "ap-guangzhou-1" }
|
||||
"pub-gz2" = { cidr = "10.0.2.0/24", az = "ap-guangzhou-2" }
|
||||
"priv-gz1" = { cidr = "10.0.10.0/24", az = "ap-guangzhou-1" }
|
||||
}
|
||||
}
|
||||
|
||||
resource "tencentcloud_subnet" "all" {
|
||||
for_each = var.subnets
|
||||
name = "${local.name_prefix}-${each.key}"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
cidr_block = each.value.cidr
|
||||
availability_zone = each.value.az
|
||||
tags = local.common_tags
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模块化设计
|
||||
|
||||
### 模块编写规范
|
||||
|
||||
```hcl
|
||||
# modules/network/main.tf
|
||||
resource "tencentcloud_vpc" "this" {
|
||||
name = var.name
|
||||
cidr_block = var.cidr_block
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
resource "tencentcloud_subnet" "this" {
|
||||
for_each = var.subnets
|
||||
name = "${var.name}-${each.key}"
|
||||
vpc_id = tencentcloud_vpc.this.id
|
||||
cidr_block = each.value.cidr
|
||||
availability_zone = each.value.az
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
# modules/network/variables.tf
|
||||
variable "name" { type = string; description = "VPC 名称前缀" }
|
||||
variable "cidr_block" { type = string; description = "VPC CIDR" }
|
||||
variable "tags" { type = map(string); default = {} }
|
||||
variable "subnets" {
|
||||
type = map(object({
|
||||
cidr = string
|
||||
az = string
|
||||
}))
|
||||
description = "子网配置 map"
|
||||
default = {}
|
||||
}
|
||||
|
||||
# modules/network/outputs.tf
|
||||
output "vpc_id" { value = tencentcloud_vpc.this.id }
|
||||
output "subnet_ids" { value = { for k, v in tencentcloud_subnet.this : k => v.id } }
|
||||
```
|
||||
|
||||
### 调用模块
|
||||
|
||||
```hcl
|
||||
# envs/prod/main.tf
|
||||
module "network" {
|
||||
source = "../../modules/network"
|
||||
|
||||
name = "${var.project_name}-${var.environment}"
|
||||
cidr_block = "10.2.0.0/16"
|
||||
tags = local.common_tags
|
||||
|
||||
subnets = {
|
||||
"public-a" = { cidr = "10.2.1.0/24", az = "ap-guangzhou-1" }
|
||||
"public-b" = { cidr = "10.2.2.0/24", az = "ap-guangzhou-2" }
|
||||
"private-a" = { cidr = "10.2.10.0/24", az = "ap-guangzhou-1" }
|
||||
"private-b" = { cidr = "10.2.11.0/24", az = "ap-guangzhou-2" }
|
||||
}
|
||||
}
|
||||
|
||||
# 使用模块输出
|
||||
module "compute" {
|
||||
source = "../../modules/compute"
|
||||
|
||||
vpc_id = module.network.vpc_id
|
||||
subnet_id = module.network.subnet_ids["private-a"]
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 多环境管理
|
||||
|
||||
### terraform.tfvars 示例
|
||||
|
||||
```hcl
|
||||
# envs/dev/terraform.tfvars(不提交 Git)
|
||||
project_name = "myapp"
|
||||
environment = "dev"
|
||||
region = "ap-guangzhou"
|
||||
owner = "dev-team"
|
||||
|
||||
instance_type = "SA3.MEDIUM4"
|
||||
worker_count = 1
|
||||
```
|
||||
|
||||
```hcl
|
||||
# envs/prod/terraform.tfvars(不提交 Git)
|
||||
project_name = "myapp"
|
||||
environment = "prod"
|
||||
region = "ap-guangzhou"
|
||||
owner = "platform-team"
|
||||
|
||||
instance_type = "SA3.LARGE8"
|
||||
worker_count = 4
|
||||
```
|
||||
|
||||
### 环境变量注入敏感值
|
||||
|
||||
```bash
|
||||
# dev
|
||||
export TF_VAR_mysql_password="Dev@123456"
|
||||
export TF_VAR_redis_password="Dev@123456"
|
||||
terraform -chdir=envs/dev apply
|
||||
|
||||
# prod(CI/CD 中从 Secrets 注入)
|
||||
export TF
|
||||
@@ -0,0 +1,371 @@
|
||||
# 计算资源参考(CVM / 密钥对 / 弹性伸缩)
|
||||
|
||||
> 目录
|
||||
> - [查询镜像](#查询镜像)
|
||||
> - [查询实例规格](#查询实例规格)
|
||||
> - [CVM 实例(单个)](#cvm-实例)
|
||||
> - [CVM 实例(多个,count vs for_each)](#多实例部署)
|
||||
> - [SSH 密钥对](#ssh-密钥对)
|
||||
> - [云盘(CBS)](#云盘)
|
||||
> - [弹性伸缩 AS](#弹性伸缩-as)
|
||||
> - [instance_type 规格速查](#instance_type-规格速查)
|
||||
|
||||
---
|
||||
|
||||
## 查询镜像
|
||||
|
||||
```hcl
|
||||
# 查询腾讯云公共镜像(Ubuntu 22.04)
|
||||
data "tencentcloud_images" "ubuntu22" {
|
||||
image_type = ["PUBLIC_IMAGE"]
|
||||
os_name = "ubuntu"
|
||||
image_name_regex = "Ubuntu Server 22.04 LTS"
|
||||
# 不指定 image_name_regex 会返回所有 ubuntu 镜像,取 .0 为最新
|
||||
}
|
||||
|
||||
# 腾讯 TLinux(生产推荐,性能更好)
|
||||
data "tencentcloud_images" "tlinux" {
|
||||
image_type = ["PUBLIC_IMAGE"]
|
||||
os_name = "tlinux"
|
||||
image_name_regex = "TencentOS Server 3.1"
|
||||
}
|
||||
|
||||
# CentOS 7(不推荐新项目,已 EOL)
|
||||
data "tencentcloud_images" "centos7" {
|
||||
image_type = ["PUBLIC_IMAGE"]
|
||||
os_name = "centos"
|
||||
image_name_regex = "CentOS 7.9"
|
||||
}
|
||||
|
||||
# 输出镜像 ID(调试用)
|
||||
output "ubuntu_image_id" {
|
||||
value = data.tencentcloud_images.ubuntu22.images.0.image_id
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 查询实例规格
|
||||
|
||||
```hcl
|
||||
# 查询指定可用区支持的实例规格
|
||||
data "tencentcloud_instance_types" "available" {
|
||||
# 按 CPU/内存过滤
|
||||
cpu_core_count = 4
|
||||
memory_size = 8
|
||||
|
||||
# 按可用区过滤
|
||||
availability_zone = "${var.region}-1"
|
||||
|
||||
# 排除不支持的
|
||||
exclude_sold_out = true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CVM 实例
|
||||
|
||||
```hcl
|
||||
resource "tencentcloud_instance" "web" {
|
||||
# ── 基本信息 ──────────────────────────────────────────────
|
||||
instance_name = "${var.project_name}-${var.environment}-web"
|
||||
availability_zone = "${var.region}-1"
|
||||
image_id = data.tencentcloud_images.ubuntu22.images.0.image_id
|
||||
instance_type = var.instance_type # 例:SA3.MEDIUM4
|
||||
|
||||
# ── 网络 ─────────────────────────────────────────────────
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
subnet_id = tencentcloud_subnet.private_a.id
|
||||
|
||||
# 公网带宽(0 = 不分配公网 IP,生产推荐用 CLB,CVM 不分配公网)
|
||||
internet_max_bandwidth_out = 0
|
||||
# internet_charge_type = "TRAFFIC_POSTPAID_BY_HOUR" # 需要公网时设置
|
||||
|
||||
# ── 安全组 ────────────────────────────────────────────────
|
||||
security_groups = [tencentcloud_security_group.web.id]
|
||||
|
||||
# ── 磁盘 ──────────────────────────────────────────────────
|
||||
system_disk_type = "CLOUD_PREMIUM" # CLOUD_PREMIUM / CLOUD_SSD / CLOUD_HSSD
|
||||
system_disk_size = 50 # GB
|
||||
|
||||
# 数据盘(可选,多个)
|
||||
data_disks {
|
||||
data_disk_type = "CLOUD_SSD"
|
||||
data_disk_size = 100
|
||||
# data_disk_snapshot_id = "" # 从快照恢复
|
||||
delete_with_instance = true # 随实例销毁
|
||||
}
|
||||
|
||||
# ── 登录认证(key_ids 和 password 二选一,强烈推荐 key_ids)──
|
||||
key_ids = [tencentcloud_key_pair.deploy.id]
|
||||
# password = var.instance_password # 敏感变量,不推荐
|
||||
|
||||
# ── 初始化脚本(user_data)────────────────────────────────
|
||||
user_data = base64encode(<<-EOF
|
||||
#!/bin/bash
|
||||
set -e
|
||||
apt-get update -y
|
||||
apt-get install -y nginx
|
||||
systemctl enable nginx
|
||||
systemctl start nginx
|
||||
echo "instance: ${var.project_name}-${var.environment}" > /var/www/html/index.html
|
||||
EOF
|
||||
)
|
||||
|
||||
# ── 实例计费 ──────────────────────────────────────────────
|
||||
# instance_charge_type = "POSTPAID_BY_HOUR" # 默认,按量
|
||||
# instance_charge_type = "PREPAID" # 包年包月
|
||||
# instance_charge_type_prepaid_period = 1 # 月数
|
||||
# instance_charge_type_prepaid_renew_flag = "NOTIFY_AND_AUTO_RENEW"
|
||||
|
||||
# ── 标签 ──────────────────────────────────────────────────
|
||||
tags = merge(local.common_tags, {
|
||||
"Role" = "web"
|
||||
})
|
||||
|
||||
# ── 生命周期 ──────────────────────────────────────────────
|
||||
lifecycle {
|
||||
# 镜像更新时不触发重建(通常不希望因镜像 ID 变化重建生产实例)
|
||||
ignore_changes = [image_id, user_data]
|
||||
# 生产环境防止误 destroy
|
||||
prevent_destroy = var.environment == "prod" ? true : false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 多实例部署
|
||||
|
||||
### 推荐:for_each(稳定标识符,删除中间元素不影响其他)
|
||||
|
||||
```hcl
|
||||
variable "web_instances" {
|
||||
type = map(object({
|
||||
az = string
|
||||
subnet = string
|
||||
}))
|
||||
default = {
|
||||
"web-01" = { az = "ap-guangzhou-1", subnet = "private_a" }
|
||||
"web-02" = { az = "ap-guangzhou-2", subnet = "private_b" }
|
||||
"web-03" = { az = "ap-guangzhou-3", subnet = "private_a" }
|
||||
}
|
||||
}
|
||||
|
||||
resource "tencentcloud_instance" "web" {
|
||||
for_each = var.web_instances
|
||||
|
||||
instance_name = "${var.project_name}-${var.environment}-${each.key}"
|
||||
availability_zone = each.value.az
|
||||
image_id = data.tencentcloud_images.ubuntu22.images.0.image_id
|
||||
instance_type = var.instance_type
|
||||
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
subnet_id = tencentcloud_subnet.app[each.value.subnet].id
|
||||
|
||||
system_disk_type = "CLOUD_PREMIUM"
|
||||
system_disk_size = 50
|
||||
key_ids = [tencentcloud_key_pair.deploy.id]
|
||||
security_groups = [tencentcloud_security_group.web.id]
|
||||
internet_max_bandwidth_out = 0
|
||||
|
||||
tags = merge(local.common_tags, { "Name" = each.key })
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = [image_id]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### count(同质资源,简单场景)
|
||||
|
||||
```hcl
|
||||
resource "tencentcloud_instance" "worker" {
|
||||
count = var.worker_count # 例:3
|
||||
|
||||
instance_name = format("%s-%s-worker-%02d", var.project_name, var.environment, count.index + 1)
|
||||
# 跨可用区分布:count.index % length(var.availability_zones)
|
||||
availability_zone = var.availability_zones[count.index % length(var.availability_zones)]
|
||||
image_id = data.tencentcloud_images.ubuntu22.images.0.image_id
|
||||
instance_type = var.worker_instance_type
|
||||
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
subnet_id = tencentcloud_subnet.app[count.index % 2 == 0 ? "a" : "b"].id
|
||||
|
||||
system_disk_type = "CLOUD_PREMIUM"
|
||||
system_disk_size = 100
|
||||
key_ids = [tencentcloud_key_pair.deploy.id]
|
||||
security_groups = [tencentcloud_security_group.worker.id]
|
||||
internet_max_bandwidth_out = 0
|
||||
|
||||
tags = merge(local.common_tags, { "Index" = tostring(count.index) })
|
||||
|
||||
lifecycle {
|
||||
ignore_changes = [image_id]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SSH 密钥对
|
||||
|
||||
```hcl
|
||||
# 方式一:使用本地已有公钥
|
||||
resource "tencentcloud_key_pair" "deploy" {
|
||||
key_name = "${var.project_name}-${var.environment}-deploy"
|
||||
public_key = var.ssh_public_key # 通过变量传入,不硬编码
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# 方式二:读取文件(本地 terraform apply 时)
|
||||
resource "tencentcloud_key_pair" "deploy" {
|
||||
key_name = "${var.project_name}-${var.environment}-deploy"
|
||||
public_key = file("~/.ssh/id_rsa.pub")
|
||||
}
|
||||
|
||||
# CI/CD 中:通过环境变量传入
|
||||
# export TF_VAR_ssh_public_key="ssh-rsa AAAA..."
|
||||
variable "ssh_public_key" {
|
||||
description = "SSH 公钥,通过 TF_VAR_ssh_public_key 环境变量注入"
|
||||
type = string
|
||||
sensitive = false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 云盘(CBS)
|
||||
|
||||
单独管理的云盘(与实例分离,适合数据盘):
|
||||
|
||||
```hcl
|
||||
# 创建独立云盘
|
||||
resource "tencentcloud_cbs_storage" "data" {
|
||||
storage_name = "${var.project_name}-${var.environment}-data-disk"
|
||||
storage_type = "CLOUD_SSD"
|
||||
storage_size = 200 # GB
|
||||
availability_zone = "${var.region}-1"
|
||||
project_id = 0
|
||||
encrypt = false
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# 挂载到实例
|
||||
resource "tencentcloud_cbs_storage_attachment" "data" {
|
||||
storage_id = tencentcloud_cbs_storage.data.id
|
||||
instance_id = tencentcloud_instance.web.id
|
||||
}
|
||||
|
||||
# 云盘快照
|
||||
resource "tencentcloud_snapshot" "data_backup" {
|
||||
storage_id = tencentcloud_cbs_storage.data.id
|
||||
snapshot_name = "${var.project_name}-data-snapshot"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 弹性伸缩 AS
|
||||
|
||||
```hcl
|
||||
# 启动配置(相当于 AWS Launch Template)
|
||||
resource "tencentcloud_as_scaling_config" "web" {
|
||||
configuration_name = "${var.project_name}-${var.environment}-web-lc"
|
||||
image_id = data.tencentcloud_images.ubuntu22.images.0.image_id
|
||||
instance_types = [var.instance_type]
|
||||
project_id = 0
|
||||
|
||||
system_disk {
|
||||
disk_type = "CLOUD_PREMIUM"
|
||||
disk_size = 50
|
||||
}
|
||||
|
||||
data_disk {
|
||||
disk_type = "CLOUD_PREMIUM"
|
||||
disk_size = 100
|
||||
}
|
||||
|
||||
key_ids = [tencentcloud_key_pair.deploy.id]
|
||||
security_group_ids = [tencentcloud_security_group.web.id]
|
||||
|
||||
internet_charge_type = "TRAFFIC_POSTPAID_BY_HOUR"
|
||||
internet_max_bandwidth_out = 0 # 不分配公网 IP,通过 CLB 访问
|
||||
|
||||
# 实例初始化脚本
|
||||
user_data = base64encode(file("${path.module}/scripts/init.sh"))
|
||||
|
||||
# 多实例类型(按顺序尝试,前一种不可用时使用下一种)
|
||||
instance_types = ["SA3.MEDIUM4", "SA3.LARGE8", "S5.MEDIUM4"]
|
||||
}
|
||||
|
||||
# 伸缩组
|
||||
resource "tencentcloud_as_scaling_group" "web" {
|
||||
scaling_group_name = "${var.project_name}-${var.environment}-web-asg"
|
||||
configuration_id = tencentcloud_as_scaling_config.web.id
|
||||
max_size = var.asg_max_size # 例:10
|
||||
min_size = var.asg_min_size # 例:2
|
||||
desired_capacity = var.asg_desired # 例:3
|
||||
project_id = 0
|
||||
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
subnet_ids = [
|
||||
tencentcloud_subnet.app["a"].id,
|
||||
tencentcloud_subnet.app["b"].id,
|
||||
]
|
||||
|
||||
# 绑定 CLB
|
||||
load_balancer_ids = [tencentcloud_clb_instance.web.id]
|
||||
|
||||
# 健康检查宽限期(实例启动后多少秒开始健康检查)
|
||||
health_check_type = "CLB" # EC2 / CLB
|
||||
lb_health_check_grace_period = 300
|
||||
|
||||
# 扩缩容策略
|
||||
retry_policy = "INCREMENTAL_INTERVALS"
|
||||
termination_policies = ["NEWEST_INSTANCE"]
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# 扩容策略:CPU > 70% 触发
|
||||
resource "tencentcloud_as_scale_out_attachment" "cpu_high" {
|
||||
scaling_group_id = tencentcloud_as_scaling_group.web.id
|
||||
|
||||
# 使用告警触发策略
|
||||
}
|
||||
|
||||
# 定时任务(业务低峰期缩容)
|
||||
resource "tencentcloud_as_schedule" "scale_down_night" {
|
||||
scaling_group_id = tencentcloud_as_scaling_group.web.id
|
||||
schedule_action_name = "scale-down-night"
|
||||
max_size = 4
|
||||
min_size = 1
|
||||
desired_capacity = 1
|
||||
start_time = "2026-01-01T22:00:00+08:00"
|
||||
recurrence = "0 22 * * *" # 每天 22:00
|
||||
end_time = "2030-12-31T22:00:00+08:00"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## instance_type 规格速查
|
||||
|
||||
| 规格族 | 示例规格 | vCPU | 内存 | 适用场景 |
|
||||
|--------|---------|------|------|---------|
|
||||
| 标准型 SA3 | `SA3.SMALL1` | 1 | 1G | 极低负载 |
|
||||
| 标准型 SA3 | `SA3.MEDIUM4` | 2 | 4G | Web/API |
|
||||
| 标准型 SA3 | `SA3.LARGE8` | 4 | 8G | 中等负载 |
|
||||
| 标准型 SA3 | `SA3.2XLARGE16` | 8 | 16G | 高负载 |
|
||||
| 标准型 SA3 | `SA3.4XLARGE32` | 16 | 32G | 大型服务 |
|
||||
| 内存型 M6 | `M6.MEDIUM16` | 4 | 16G | Redis/DB |
|
||||
| 内存型 M6 | `M6.LARGE32` | 8 | 32G | 内存密集 |
|
||||
| 计算型 C3 | `C3.LARGE8` | 4 | 8G | 计算密集 |
|
||||
| 高 IO 型 IT5 | `IT5.LARGE8` | 4 | 8G | 本地 SSD |
|
||||
| GPU 型 GN10X | `GN10X.2XLARGE40` | 8 | 40G | AI/ML 推理 |
|
||||
|
||||
完整规格列表:https://cloud.tencent.com/document/product/213/11518
|
||||
@@ -0,0 +1,86 @@
|
||||
## 🌐 DNSPod 腾讯云 DNS 解析
|
||||
|
||||
### 支持的资源
|
||||
|
||||
| 资源名 | 说明 |
|
||||
|---|---|
|
||||
| `tencentcloud_dnspod_domain_instance` | 创建域名 |
|
||||
| `tencentcloud_dnspod_record` | 添加 DNS 解析记录 |
|
||||
| `tencentcloud_dnspod_record_group` | 创建记录分组 |
|
||||
| `tencentcloud_dnspod_custom_line` | 自定义线路 |
|
||||
| `tencentcloud_dnspod_line_group` | 线路分组 |
|
||||
| `tencentcloud_dnspod_domain_alias` | 域名别名 |
|
||||
| `tencentcloud_dnspod_domain_lock` | 域名锁定 |
|
||||
| `tencentcloud_dnspod_snapshot_config` | 快照配置 |
|
||||
| `tencentcloud_dnspod_package_order` | 套餐购买 |
|
||||
| `tencentcloud_dnspod_modify_domain_owner_operation` | 转移域名 |
|
||||
|
||||
### 典型示例
|
||||
|
||||
```hcl
|
||||
# 添加域名
|
||||
resource "tencentcloud_dnspod_domain_instance" "example" {
|
||||
domain = "example.com"
|
||||
remark = "my main domain"
|
||||
status = "ENABLE"
|
||||
}
|
||||
|
||||
# 添加 A 记录
|
||||
resource "tencentcloud_dnspod_record" "a_record" {
|
||||
domain = tencentcloud_dnspod_domain_instance.example.domain
|
||||
sub_domain = "www"
|
||||
record_type = "A"
|
||||
record_line = "默认"
|
||||
value = "1.2.3.4"
|
||||
ttl = 600
|
||||
}
|
||||
|
||||
# 添加 CNAME 记录
|
||||
resource "tencentcloud_dnspod_record" "cname_record" {
|
||||
domain = tencentcloud_dnspod_domain_instance.example.domain
|
||||
sub_domain = "cdn"
|
||||
record_type = "CNAME"
|
||||
record_line = "默认"
|
||||
value = "example.cdn.dnsv1.com"
|
||||
ttl = 600
|
||||
}
|
||||
|
||||
# 添加 MX 记录
|
||||
resource "tencentcloud_dnspod_record" "mx_record" {
|
||||
domain = tencentcloud_dnspod_domain_instance.example.domain
|
||||
sub_domain = "@"
|
||||
record_type = "MX"
|
||||
record_line = "默认"
|
||||
value = "mail.example.com"
|
||||
mx = 10
|
||||
ttl = 600
|
||||
}
|
||||
|
||||
# 自定义线路(按 IP 段分流)
|
||||
resource "tencentcloud_dnspod_custom_line" "example" {
|
||||
domain = tencentcloud_dnspod_domain_instance.example.domain
|
||||
name = "office-network"
|
||||
area = "192.168.0.0/24"
|
||||
}
|
||||
|
||||
# 记录分组
|
||||
resource "tencentcloud_dnspod_record_group" "example" {
|
||||
domain = tencentcloud_dnspod_domain_instance.example.domain
|
||||
group_name = "production"
|
||||
}
|
||||
|
||||
# 域名快照配置
|
||||
resource "tencentcloud_dnspod_snapshot_config" "example" {
|
||||
domain = tencentcloud_dnspod_domain_instance.example.domain
|
||||
period = "weekly"
|
||||
}
|
||||
```
|
||||
|
||||
### 激活场景关键词
|
||||
- DNSPod / DNS 解析
|
||||
- 添加 A 记录 / CNAME / MX / TXT 记录
|
||||
- 域名解析管理
|
||||
- 自定义线路 / 分区解析
|
||||
- dnspod_record / dnspod_domain
|
||||
|
||||
---
|
||||
@@ -0,0 +1,238 @@
|
||||
# Provider 配置参考
|
||||
|
||||
> 目录
|
||||
> - [完整 Provider 配置](#完整-provider-配置)
|
||||
> - [versions.tf 模板](#versionstf-模板)
|
||||
> - [多 Region(Provider Alias)](#多-region)
|
||||
> - [跨账号 AssumeRole](#跨账号-assumerole)
|
||||
> - [企业代理配置](#企业代理配置)
|
||||
> - [环境变量一览](#环境变量一览)
|
||||
> - [Provider 版本锁定策略](#provider-版本锁定策略)
|
||||
|
||||
---
|
||||
|
||||
## 完整 Provider 配置
|
||||
|
||||
`versions.tf`(每个项目必须有此文件):
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
tencentcloud = {
|
||||
source = "tencentcloudstack/tencentcloud"
|
||||
version = "~> 1.81.0"
|
||||
# ~> 1.81.0 表示允许 1.81.x 的 patch 更新,不允许 minor/major 升级
|
||||
# 团队协作必须锁定版本,避免成员间 provider 版本不一致
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "tencentcloud" {
|
||||
region = var.region
|
||||
|
||||
# 认证优先级(从高到低):
|
||||
# 1. provider 块内 secret_id / secret_key 字段(不推荐,禁止提交 Git)
|
||||
# 2. 环境变量 TENCENTCLOUD_SECRET_ID / TENCENTCLOUD_SECRET_KEY
|
||||
# 3. CAM 角色(在腾讯云 CVM / TKE 节点上运行时自动获取临时凭证)
|
||||
#
|
||||
# 生产环境统一使用环境变量,本地开发也用环境变量
|
||||
}
|
||||
```
|
||||
|
||||
配套 `variables.tf` 中的 region 变量:
|
||||
|
||||
```hcl
|
||||
variable "region" {
|
||||
description = "腾讯云地域,例如 ap-guangzhou、ap-beijing、ap-shanghai"
|
||||
type = string
|
||||
default = "ap-guangzhou"
|
||||
|
||||
validation {
|
||||
condition = contains([
|
||||
"ap-guangzhou", "ap-beijing", "ap-shanghai", "ap-chengdu",
|
||||
"ap-nanjing", "ap-hongkong", "ap-singapore", "ap-bangkok",
|
||||
"ap-jakarta", "ap-seoul", "ap-tokyo", "na-siliconvalley",
|
||||
"na-ashburn", "eu-frankfurt", "eu-moscow"
|
||||
], var.region)
|
||||
error_message = "请使用合法的腾讯云地域代码。"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 多 Region
|
||||
|
||||
需要同时在多个地域创建资源时使用 Provider Alias:
|
||||
|
||||
```hcl
|
||||
# versions.tf 中的主 provider 配置保持不变
|
||||
provider "tencentcloud" {
|
||||
region = "ap-guangzhou"
|
||||
}
|
||||
|
||||
# 声明别名 provider
|
||||
provider "tencentcloud" {
|
||||
alias = "beijing"
|
||||
region = "ap-beijing"
|
||||
}
|
||||
|
||||
provider "tencentcloud" {
|
||||
alias = "singapore"
|
||||
region = "ap-singapore"
|
||||
}
|
||||
|
||||
# 资源指定 provider
|
||||
resource "tencentcloud_vpc" "gz_vpc" {
|
||||
# 使用默认 provider(ap-guangzhou)
|
||||
name = "gz-vpc"
|
||||
cidr_block = "10.0.0.0/16"
|
||||
}
|
||||
|
||||
resource "tencentcloud_vpc" "bj_vpc" {
|
||||
provider = tencentcloud.beijing # 显式指定
|
||||
name = "bj-vpc"
|
||||
cidr_block = "10.1.0.0/16"
|
||||
}
|
||||
|
||||
# 模块也可以传入 provider
|
||||
module "beijing_network" {
|
||||
source = "./modules/network"
|
||||
providers = {
|
||||
tencentcloud = tencentcloud.beijing
|
||||
}
|
||||
cidr_block = "10.1.0.0/16"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 跨账号 AssumeRole
|
||||
|
||||
企业多账号管理场景,主账号扮演子账号角色:
|
||||
|
||||
```hcl
|
||||
# 方式一:provider 块中配置
|
||||
provider "tencentcloud" {
|
||||
region = "ap-guangzhou"
|
||||
# 主账号凭证通过环境变量注入
|
||||
|
||||
assume_role {
|
||||
role_arn = "qcs::cam::uin/123456789:roleName/TerraformRole"
|
||||
session_name = "terraform-${var.environment}"
|
||||
session_duration = 7200 # 秒,最长 43200(12小时)
|
||||
# policy = "" # 可选:进一步限制临时凭证权限
|
||||
}
|
||||
}
|
||||
|
||||
# 方式二:多账号场景,不同 provider alias 对应不同子账号
|
||||
provider "tencentcloud" {
|
||||
alias = "prod_account"
|
||||
region = "ap-guangzhou"
|
||||
|
||||
assume_role {
|
||||
role_arn = "qcs::cam::uin/PROD_ACCOUNT_UIN:roleName/TerraformRole"
|
||||
session_name = "terraform-prod"
|
||||
}
|
||||
}
|
||||
|
||||
provider "tencentcloud" {
|
||||
alias = "dev_account"
|
||||
region = "ap-guangzhou"
|
||||
|
||||
assume_role {
|
||||
role_arn = "qcs::cam::uin/DEV_ACCOUNT_UIN:roleName/TerraformRole"
|
||||
session_name = "terraform-dev"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
CAM 角色信任策略配置(在腾讯云控制台设置):
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2.0",
|
||||
"statement": [
|
||||
{
|
||||
"effect": "allow",
|
||||
"principal": {
|
||||
"qcs": ["qcs::cam::uin/<主账号UIN>:root"]
|
||||
},
|
||||
"action": ["name/sts:AssumeRole"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 企业代理配置
|
||||
|
||||
在有网络代理的企业环境中:
|
||||
|
||||
```bash
|
||||
# 全局代理(推荐)
|
||||
export HTTPS_PROXY="http://proxy.company.com:8080"
|
||||
export HTTP_PROXY="http://proxy.company.com:8080"
|
||||
export NO_PROXY="localhost,127.0.0.1,169.254.169.254" # 保留元数据服务
|
||||
|
||||
# 或在 Provider 中指定(仅影响 tencentcloud API 调用)
|
||||
```
|
||||
|
||||
```hcl
|
||||
provider "tencentcloud" {
|
||||
region = var.region
|
||||
protocol = "https"
|
||||
# 腾讯云 Provider 会自动读取 HTTPS_PROXY 环境变量
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 环境变量一览
|
||||
|
||||
| 变量名 | 说明 | 必填 | 示例 |
|
||||
|--------|------|------|------|
|
||||
| `TENCENTCLOUD_SECRET_ID` | CAM 密钥 ID | ✅ | `AKID9HH4OpqLJ5f6LPr4iIm5GF2s` |
|
||||
| `TENCENTCLOUD_SECRET_KEY` | CAM 密钥 Key | ✅ | `72pQp14tWKUglrnX5RbaNEtN` |
|
||||
| `TENCENTCLOUD_REGION` | 默认地域 | ✅ | `ap-guangzhou` |
|
||||
| `TENCENTCLOUD_APPID` | 账号 AppId | COS 必须 | `1234567890` |
|
||||
| `TENCENTCLOUD_SECURITY_TOKEN` | 临时会话 Token | STS 时必须 | - |
|
||||
| `TF_LOG` | Terraform 日志级别 | 调试用 | `DEBUG`/`INFO`/`WARN` |
|
||||
| `TF_LOG_PATH` | 日志写入文件 | 调试用 | `/tmp/tf.log` |
|
||||
| `TF_VAR_<name>` | 覆盖同名变量 | 按需 | `TF_VAR_db_password=xxx` |
|
||||
| `HTTPS_PROXY` | 企业代理 | 企业网络 | `http://proxy:8080` |
|
||||
|
||||
---
|
||||
|
||||
## Provider 版本锁定策略
|
||||
|
||||
```hcl
|
||||
# 版本约束语法说明
|
||||
version = "= 1.81.5" # 精确锁定,最严格
|
||||
version = "~> 1.81.0" # 允许 1.81.x,推荐
|
||||
version = "~> 1.81" # 允许 1.81.x 和 1.82.x,较宽松
|
||||
version = ">= 1.81.0" # 下限约束,不推荐(可能引入 breaking change)
|
||||
```
|
||||
|
||||
**最佳实践:** 使用 `~> 1.81.0`,并将 `.terraform.lock.hcl` 提交到 Git,确保所有团队成员和 CI/CD 使用完全一致的 Provider 版本。
|
||||
|
||||
升级 Provider:
|
||||
|
||||
```bash
|
||||
# 查看当前版本
|
||||
terraform version
|
||||
|
||||
# 升级到约束范围内的最新版
|
||||
terraform init -upgrade
|
||||
|
||||
# 查看 lock 文件中的实际版本
|
||||
cat .terraform.lock.hcl
|
||||
|
||||
# 验证升级后无 breaking change
|
||||
terraform plan # 应无意外变更
|
||||
```
|
||||
|
||||
检查新版本 changelog:https://github.com/tencentcloudstack/terraform-provider-tencentcloud/releases
|
||||
@@ -0,0 +1,323 @@
|
||||
# State 管理参考(COS Backend / import / 迁移)
|
||||
|
||||
> 目录
|
||||
> - [COS Backend 完整配置](#cos-backend-完整配置)
|
||||
> - [Backend 初始化与迁移](#backend-初始化与迁移)
|
||||
> - [多环境 State 隔离](#多环境-state-隔离)
|
||||
> - [terraform import 详细指南](#terraform-import)
|
||||
> - [state mv / rm / push / pull](#state-操作命令)
|
||||
> - [跨模块引用 State](#跨模块引用-state)
|
||||
> - [State 锁机制](#state-锁机制)
|
||||
> - [常见 State 问题排查](#state-问题排查)
|
||||
|
||||
---
|
||||
|
||||
## COS Backend 完整配置
|
||||
|
||||
`backend.tf`:
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
backend "cos" {
|
||||
# ── 必填 ──────────────────────────────────────────────
|
||||
region = "ap-guangzhou"
|
||||
bucket = "tfstate-1234567890" # 格式:<name>-<appid>
|
||||
prefix = "terraform/state/myproject/prod" # State 文件路径前缀
|
||||
|
||||
# ── 可选 ──────────────────────────────────────────────
|
||||
# 服务端加密(推荐生产开启)
|
||||
# encrypt = true
|
||||
|
||||
# 加速域名(跨地域 CI/CD 时使用)
|
||||
# accelerate = true
|
||||
|
||||
# 自定义 endpoint
|
||||
# endpoint = "cos.ap-guangzhou.myqcloud.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **注意:** `backend` 块内不支持变量插值(`${var.xxx}` 不可用)。
|
||||
> 多环境使用不同 `prefix`,或通过 `-backend-config` 动态注入:
|
||||
>
|
||||
> ```bash
|
||||
> terraform init -backend-config="prefix=terraform/state/${ENV}/myproject"
|
||||
> ```
|
||||
|
||||
**提前创建 COS Bucket(仅需一次):**
|
||||
|
||||
```bash
|
||||
# 用腾讯云 CLI 创建(也可在控制台手动创建)
|
||||
tccli cos create-bucket \
|
||||
--Bucket "tfstate-1234567890" \
|
||||
--Region "ap-guangzhou"
|
||||
|
||||
# 强烈推荐:开启版本控制,防止 state 文件意外覆盖
|
||||
tccli cos put-bucket-versioning \
|
||||
--Bucket "tfstate-1234567890" \
|
||||
--Region "ap-guangzhou" \
|
||||
--Status "Enabled"
|
||||
|
||||
# 可选:开启服务端加密
|
||||
tccli cos put-bucket-encryption \
|
||||
--Bucket "tfstate-1234567890" \
|
||||
--Region "ap-guangzhou"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backend 初始化与迁移
|
||||
|
||||
```bash
|
||||
# 首次初始化(下载 provider + 配置 backend)
|
||||
terraform init
|
||||
|
||||
# 修改 backend 配置后重新初始化
|
||||
terraform init -reconfigure
|
||||
|
||||
# 本地 state → COS(添加 backend.tf 后执行)
|
||||
# Terraform 会询问是否迁移,选择 yes
|
||||
terraform init -migrate-state
|
||||
|
||||
# 验证迁移成功
|
||||
terraform state list # 应能正常列出资源
|
||||
terraform plan # 应显示 No changes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 多环境 State 隔离
|
||||
|
||||
### 方案 A:不同 prefix,同一 bucket(推荐小团队)
|
||||
|
||||
```
|
||||
tfstate-1234567890/
|
||||
├── terraform/state/dev/myproject/ # dev 环境
|
||||
├── terraform/state/staging/myproject/
|
||||
└── terraform/state/prod/myproject/
|
||||
```
|
||||
|
||||
```bash
|
||||
# dev 初始化
|
||||
cd envs/dev
|
||||
terraform init -backend-config="prefix=terraform/state/dev/myproject"
|
||||
|
||||
# prod 初始化
|
||||
cd envs/prod
|
||||
terraform init -backend-config="prefix=terraform/state/prod/myproject"
|
||||
```
|
||||
|
||||
### 方案 B:不同 bucket,强隔离(推荐大团队/生产与非<E4B88E><E99D9E>产隔离)
|
||||
|
||||
```hcl
|
||||
# envs/prod/backend.tf
|
||||
terraform {
|
||||
backend "cos" {
|
||||
region = "ap-guangzhou"
|
||||
bucket = "tfstate-prod-1234567890" # prod 专用 bucket,严格 IAM 控制
|
||||
prefix = "terraform/state/myproject"
|
||||
}
|
||||
}
|
||||
|
||||
# envs/dev/backend.tf
|
||||
terraform {
|
||||
backend "cos" {
|
||||
region = "ap-guangzhou"
|
||||
bucket = "tfstate-nonprod-1234567890" # 非生产共用
|
||||
prefix = "terraform/state/dev/myproject"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## terraform import
|
||||
|
||||
将已存在于腾讯云控制台(手动创建或通过其他工具创建)的资源导入 Terraform State:
|
||||
|
||||
### 常用资源导入 ID 格式
|
||||
|
||||
```bash
|
||||
# VPC
|
||||
terraform import tencentcloud_vpc.main vpc-xxxxxxxx
|
||||
|
||||
# 子网
|
||||
terraform import tencentcloud_subnet.public_a subnet-xxxxxxxx
|
||||
|
||||
# 安全组
|
||||
terraform import tencentcloud_security_group.web sg-xxxxxxxx
|
||||
|
||||
# CVM 实例
|
||||
terraform import tencentcloud_instance.web ins-xxxxxxxx
|
||||
|
||||
# CVM(count 语法,导入到数组第 0 个)
|
||||
terraform import 'tencentcloud_instance.web[0]' ins-xxxxxxxx
|
||||
|
||||
# CVM(for_each 语法)
|
||||
terraform import 'tencentcloud_instance.web["web-01"]' ins-xxxxxxxx
|
||||
|
||||
# COS Bucket
|
||||
terraform import tencentcloud_cos_bucket.assets myproject-dev-assets-1234567890
|
||||
|
||||
# TKE 集群
|
||||
terraform import tencentcloud_kubernetes_cluster.main cls-xxxxxxxx
|
||||
|
||||
# TKE 节点池
|
||||
terraform import tencentcloud_kubernetes_node_pool.default cls-xxxxxxxx#np-xxxxxxxx
|
||||
|
||||
# CynosDB 集群
|
||||
terraform import tencentcloud_cynosdb_cluster.main cynosdbmysql-xxxxxxxx
|
||||
|
||||
# Redis 实例
|
||||
terraform import tencentcloud_redis_instance.main crs-xxxxxxxx
|
||||
|
||||
# CLB 实例
|
||||
terraform import tencentcloud_clb_instance.web lb-xxxxxxxx
|
||||
|
||||
# NAT 网关
|
||||
terraform import tencentcloud_nat_gateway.main nat-xxxxxxxx
|
||||
|
||||
# EIP
|
||||
terraform import tencentcloud_eip.nat eip-xxxxxxxx
|
||||
|
||||
# 密钥对
|
||||
terraform import tencentcloud_key_pair.deploy skey-xxxxxxxx
|
||||
|
||||
# 路由表
|
||||
terraform import tencentcloud_route_table.private rtb-xxxxxxxx
|
||||
```
|
||||
|
||||
### 批量导入工作流
|
||||
|
||||
```bash
|
||||
# 1. 先写好 .tf 资源定义(即使参数不全也没关系)
|
||||
# 2. 执行 import
|
||||
terraform import tencentcloud_vpc.main vpc-abc12345
|
||||
|
||||
# 3. 查看 state,了解真实属性
|
||||
terraform state show tencentcloud_vpc.main
|
||||
|
||||
# 4. 根据 state show 的输出补全 .tf 中的参数
|
||||
# 5. 执行 plan,确保 No changes(说明 .tf 与实际状态一致)
|
||||
terraform plan
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State 操作命令
|
||||
|
||||
```bash
|
||||
# ── 查看 ──────────────────────────────────────────────────
|
||||
# 列出所有 managed 资源
|
||||
terraform state list
|
||||
|
||||
# 按前缀过滤
|
||||
terraform state list 'module.vpc.*'
|
||||
terraform state list 'tencentcloud_instance.*'
|
||||
|
||||
# 查看资源完整 state(包括所有属性)
|
||||
terraform state show tencentcloud_vpc.main
|
||||
terraform state show 'tencentcloud_instance.web[0]'
|
||||
|
||||
# 拉取远端 state 查看完整 JSON
|
||||
terraform state pull | python3 -m json.tool | less
|
||||
|
||||
# ── 重命名/移动 ────────────────────────────────────────────
|
||||
# 重构资源名(不删除真实资源)
|
||||
terraform state mv tencentcloud_vpc.old tencentcloud_vpc.main
|
||||
|
||||
# 将顶层资源移入模块
|
||||
terraform state mv \
|
||||
tencentcloud_vpc.main \
|
||||
module.network.tencentcloud_vpc.main
|
||||
|
||||
# 将模块内资源移出
|
||||
terraform state mv \
|
||||
module.network.tencentcloud_vpc.main \
|
||||
tencentcloud_vpc.main
|
||||
|
||||
# ── 删除 ──────────────────────────────────────────────────
|
||||
# 从 state 移除(真实资源不删除,terraform 不再管理此资源)
|
||||
terraform state rm tencentcloud_instance.old
|
||||
terraform state rm 'tencentcloud_instance.web[2]'
|
||||
|
||||
# ── 推送 ──────────────────────────────────────────────────
|
||||
# 将本地 state 强制推送到远端(谨慎使用)
|
||||
terraform state push terraform.tfstate
|
||||
|
||||
# ── 解锁 ─────────────────────────────────<E29480><E29480>────────────────
|
||||
# 强制解锁(确认无并发操作后使用)
|
||||
terraform force-unlock <LOCK_ID>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 跨模块引用 State
|
||||
|
||||
在多个独立 Terraform 项目之间共享输出:
|
||||
|
||||
```hcl
|
||||
# 消费方(例如 compute 项目引用 network 项目的输出)
|
||||
data "terraform_remote_state" "network" {
|
||||
backend = "cos"
|
||||
config = {
|
||||
region = "ap-guangzhou"
|
||||
bucket = "tfstate-1234567890"
|
||||
prefix = "terraform/state/prod/network"
|
||||
}
|
||||
}
|
||||
|
||||
# 使用跨项目输出
|
||||
resource "tencentcloud_instance" "app" {
|
||||
vpc_id = data.terraform_remote_state.network.outputs.vpc_id
|
||||
subnet_id = data.terraform_remote_state.network.outputs.app_subnet_ids[0]
|
||||
# ...
|
||||
}
|
||||
|
||||
# network 项目的 outputs.tf 需要输出这些值
|
||||
output "vpc_id" {
|
||||
value = tencentcloud_vpc.main.id
|
||||
}
|
||||
output "app_subnet_ids" {
|
||||
value = [for k, v in tencentcloud_subnet.app : v.id]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State 锁机制
|
||||
|
||||
Terraform 使用 State Lock 防止并发操作导致 State 损坏。
|
||||
|
||||
COS Backend 通过在 COS 上创建 `.lock` 文件实现锁。
|
||||
|
||||
**常见 Lock 错误:**
|
||||
|
||||
```
|
||||
Error: Error locking state: Error acquiring the state lock
|
||||
Lock Info:
|
||||
ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
Path: terraform/state/prod/myproject/terraform.tfstate
|
||||
Operation: OperationTypePlan
|
||||
Who: user@hostname
|
||||
Version: 1.5.7
|
||||
Created: 2026-03-27 10:00:00 +0000 UTC
|
||||
```
|
||||
|
||||
**解锁步骤:**
|
||||
|
||||
1. 确认上面的 `Who` 和 `Created` 信息,确保之前的操作已经结束
|
||||
2. 执行解锁:`terraform force-unlock <ID>`
|
||||
|
||||
---
|
||||
|
||||
## State 问题排查
|
||||
|
||||
| 现象 | 原因 | 解决方案 |
|
||||
|------|------|---------|
|
||||
| `state pull` 返回 404 | Bucket 或 prefix 不存在 | 检查 backend.tf 配置,确认 Bucket 已创建 |
|
||||
| `terraform plan` 显示大量 `~` | .tf 与 state 不匹配 | 对比 `state show` 输出,补全 .tf 参数 |
|
||||
| state 锁无法释放 | CI/CD 任务异常中断 | `terraform force-unlock <ID>` |
|
||||
| `import` 后 plan 仍显示变更 | .tf 配置与实际资源不一致 | 根据 `state show` 调整 .tf 参数 |
|
||||
| 不同人 plan 结果不同 | Provider 版本不一致 | 提交并使用 `.terraform.lock.hcl` |
|
||||
| state 文件意外损坏 | 未开启版本控制 | 在 COS Bucket 开启版本控制后恢复历史版本 |
|
||||
@@ -0,0 +1,504 @@
|
||||
# 网络资源参考(VPC / 子网 / 安全组 / NAT / CLB)
|
||||
|
||||
> 目录
|
||||
> - [VPC](#vpc)
|
||||
> - [子网](#子网)
|
||||
> - [安全组](#安全组)
|
||||
> - [路由表](#路由表)
|
||||
> - [NAT 网关](#nat-网关)
|
||||
> - [弹性 IP(EIP)](#弹性-ip)
|
||||
> - [VPN 网关](#vpn-网关)
|
||||
> - [对等连接](#对等连接)
|
||||
> - [CLB 负载均衡](#clb-负载均衡)
|
||||
> - [完整三层网络示例](#完整三层网络示例)
|
||||
|
||||
---
|
||||
|
||||
## VPC
|
||||
|
||||
```hcl
|
||||
resource "tencentcloud_vpc" "main" {
|
||||
name = "${var.project_name}-${var.environment}-vpc"
|
||||
cidr_block = "10.0.0.0/16"
|
||||
|
||||
# 开启组播(默认 false,通常不需要)
|
||||
is_multicast = false
|
||||
|
||||
# 自定义 DNS(不填则使用腾讯云默认)
|
||||
dns_servers = ["183.60.83.19", "183.60.82.98"]
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
```
|
||||
|
||||
**CIDR 规划建议:**
|
||||
|
||||
| 环境 | VPC CIDR | 用途说明 |
|
||||
|------|----------|---------|
|
||||
| dev | `10.0.0.0/16` | 开发环境 |
|
||||
| staging | `10.1.0.0/16` | 预发布 |
|
||||
| prod | `10.2.0.0/16` | 生产 |
|
||||
|
||||
各环境 VPC 不重叠,方便后续做对等连接。
|
||||
|
||||
---
|
||||
|
||||
## 子网
|
||||
|
||||
```hcl
|
||||
# 公有子网(放 CLB、NAT Gateway、Bastion)
|
||||
resource "tencentcloud_subnet" "public_a" {
|
||||
name = "${var.project_name}-${var.environment}-public-a"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
cidr_block = "10.0.1.0/24"
|
||||
availability_zone = "${var.region}-1" # ap-guangzhou-1
|
||||
is_multicast = false
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "tencentcloud_subnet" "public_b" {
|
||||
name = "${var.project_name}-${var.environment}-public-b"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
cidr_block = "10.0.2.0/24"
|
||||
availability_zone = "${var.region}-2"
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# 私有子网(放 CVM 应用层)
|
||||
resource "tencentcloud_subnet" "private_a" {
|
||||
name = "${var.project_name}-${var.environment}-private-a"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
cidr_block = "10.0.10.0/24"
|
||||
availability_zone = "${var.region}-1"
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "tencentcloud_subnet" "private_b" {
|
||||
name = "${var.project_name}-${var.environment}-private-b"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
cidr_block = "10.0.11.0/24"
|
||||
availability_zone = "${var.region}-2"
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# 数据层子网(放 MySQL、Redis)
|
||||
resource "tencentcloud_subnet" "data_a" {
|
||||
name = "${var.project_name}-${var.environment}-data-a"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
cidr_block = "10.0.20.0/24"
|
||||
availability_zone = "${var.region}-1"
|
||||
tags = local.common_tags
|
||||
}
|
||||
```
|
||||
|
||||
**可用区代码规律:** `<region>-1`,`<region>-2`,`<region>-3`,`<region>-4`(视地域实际可用区数量)。
|
||||
`ap-guangzhou` 有 `-1` `-2` `-3` `-4` `-6` `-7`(注意没有 `-5`)。
|
||||
|
||||
---
|
||||
|
||||
## 安全组
|
||||
|
||||
### 方式一:lite_rule(简洁,推荐小型项目)
|
||||
|
||||
```hcl
|
||||
resource "tencentcloud_security_group" "web" {
|
||||
name = "${var.project_name}-${var.environment}-web-sg"
|
||||
description = "Web 服务器安全组,允许 80/443 入站"
|
||||
project_id = 0
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "tencentcloud_security_group_lite_rule" "web" {
|
||||
security_group_id = tencentcloud_security_group.web.id
|
||||
|
||||
# 格式:POLICY#SOURCE#PORT#PROTOCOL
|
||||
# POLICY: ACCEPT / DROP
|
||||
# SOURCE: CIDR / 安全组 ID / 0.0.0.0/0
|
||||
# PORT: 端口号 / ALL
|
||||
# PROTOCOL: TCP / UDP / ICMP / ALL
|
||||
ingress = [
|
||||
"ACCEPT#0.0.0.0/0#80#TCP",
|
||||
"ACCEPT#0.0.0.0/0#443#TCP",
|
||||
"ACCEPT#10.0.0.0/8#22#TCP", # 仅 VPC 内可 SSH
|
||||
"ACCEPT#0.0.0.0/0#ALL#ICMP", # 允许 ping
|
||||
"DROP#0.0.0.0/0#ALL#ALL",
|
||||
]
|
||||
|
||||
egress = [
|
||||
"ACCEPT#0.0.0.0/0#ALL#ALL",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 方式二:security_group_rule(精细,推荐生产)
|
||||
|
||||
```hcl
|
||||
# 允许 HTTPS 入站
|
||||
resource "tencentcloud_security_group_rule" "allow_https" {
|
||||
security_group_id = tencentcloud_security_group.web.id
|
||||
type = "ingress"
|
||||
cidr_ip = "0.0.0.0/0"
|
||||
ip_protocol = "tcp"
|
||||
port_range = "443"
|
||||
policy = "ACCEPT"
|
||||
description = "Allow HTTPS from internet"
|
||||
}
|
||||
|
||||
# 允许同安全组内互访
|
||||
resource "tencentcloud_security_group_rule" "allow_self" {
|
||||
security_group_id = tencentcloud_security_group.web.id
|
||||
type = "ingress"
|
||||
source_sgid = tencentcloud_security_group.web.id # 引用自身
|
||||
ip_protocol = "ALL"
|
||||
port_range = "ALL"
|
||||
policy = "ACCEPT"
|
||||
description = "Allow intra-sg communication"
|
||||
}
|
||||
|
||||
# 允许从特定安全组访问(例如 CLB → Web)
|
||||
resource "tencentcloud_security_group_rule" "from_clb" {
|
||||
security_group_id = tencentcloud_security_group.web.id
|
||||
type = "ingress"
|
||||
source_sgid = tencentcloud_security_group.clb.id
|
||||
ip_protocol = "tcp"
|
||||
port_range = "8080"
|
||||
policy = "ACCEPT"
|
||||
description = "Allow from CLB security group"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 路由表
|
||||
|
||||
```hcl
|
||||
resource "tencentcloud_route_table" "private" {
|
||||
name = "${var.project_name}-${var.environment}-private-rt"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# 私有子网默认路由指向 NAT
|
||||
resource "tencentcloud_route_table_entry" "nat_default" {
|
||||
route_table_id = tencentcloud_route_table.private.id
|
||||
destination_cidr_block = "0.0.0.0/0"
|
||||
next_type = "NAT" # NAT/EIP/VPN/PEERCONN/CCN
|
||||
next_hub = tencentcloud_nat_gateway.main.id
|
||||
description = "Default route to NAT"
|
||||
}
|
||||
|
||||
# 子网关联路由表
|
||||
resource "tencentcloud_route_table_association" "private_a" {
|
||||
subnet_id = tencentcloud_subnet.private_a.id
|
||||
route_table_id = tencentcloud_route_table.private.id
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NAT 网关
|
||||
|
||||
```hcl
|
||||
# 先申请 EIP
|
||||
resource "tencentcloud_eip" "nat" {
|
||||
name = "${var.project_name}-${var.environment}-nat-eip"
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "tencentcloud_nat_gateway" "main" {
|
||||
name = "${var.project_name}-${var.environment}-nat"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
|
||||
# 带宽上限 Mbps:100/200/500/1000
|
||||
bandwidth = 100
|
||||
|
||||
# 最大并发连接数:1000000/3000000/10000000
|
||||
max_concurrent = 1000000
|
||||
|
||||
# 绑定的 EIP(可以多个)
|
||||
assigned_eip_set = [tencentcloud_eip.nat.public_ip]
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 弹性 IP
|
||||
|
||||
```hcl
|
||||
# 申请 EIP
|
||||
resource "tencentcloud_eip" "web" {
|
||||
name = "${var.project_name}-${var.environment}-web-eip"
|
||||
internet_charge_type = "TRAFFIC_POSTPAID_BY_HOUR"
|
||||
internet_max_bandwidth_out = 100
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# 绑定到 CVM 实例
|
||||
resource "tencentcloud_eip_association" "web" {
|
||||
eip_id = tencentcloud_eip.web.id
|
||||
instance_id = tencentcloud_instance.web.id
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## VPN 网关
|
||||
|
||||
用于连接本地 IDC 与腾讯云 VPC:
|
||||
|
||||
```hcl
|
||||
resource "tencentcloud_vpn_gateway" "main" {
|
||||
name = "${var.project_name}-${var.environment}-vpngw"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
bandwidth = 5 # Mbps
|
||||
|
||||
# prepaid(包年包月)或 postpaid(按量)
|
||||
charge_type = "POSTPAID_BY_HOUR"
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "tencentcloud_vpn_customer_gateway" "idc" {
|
||||
name = "idc-cgw"
|
||||
public_ip_address = "203.0.113.1" # IDC 公网 IP
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "tencentcloud_vpn_connection" "main" {
|
||||
name = "${var.project_name}-vpn-conn"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
vpn_gateway_id = tencentcloud_vpn_gateway.main.id
|
||||
customer_gateway_id = tencentcloud_vpn_customer_gateway.idc.id
|
||||
pre_share_key = var.vpn_psk # 预共享密钥,用 sensitive variable
|
||||
|
||||
ike_proto_encry_algorithm = "3DES-CBC"
|
||||
ike_proto_authen_algorithm = "MD5"
|
||||
ike_exchange_mode = "MAIN"
|
||||
ike_local_identity = "ADDRESS"
|
||||
ike_remote_identity = "ADDRESS"
|
||||
ike_dh_group_name = "GROUP2"
|
||||
ike_sa_lifetime_seconds = 86400
|
||||
|
||||
ipsec_encrypt_algorithm = "3DES-CBC"
|
||||
ipsec_integrity_algorithm = "MD5"
|
||||
ipsec_sa_lifetime_seconds = 3600
|
||||
ipsec_pfs_dh_group = "NULL"
|
||||
|
||||
security_group_policy {
|
||||
local_cidr_block = "10.0.0.0/16"
|
||||
remote_cidr_block = ["192.168.0.0/24"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 对等连接
|
||||
|
||||
连接同账号或跨账号的两个 VPC:
|
||||
|
||||
```hcl
|
||||
# 同账号、同地域对等连接
|
||||
resource "tencentcloud_vpc_peering_connection" "main" {
|
||||
name = "prod-to-shared-peering"
|
||||
vpc_id = tencentcloud_vpc.prod.id
|
||||
peer_vpc_id = tencentcloud_vpc.shared.id
|
||||
peer_region = var.region # 同地域可省略
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# 对等连接创建后,双向路由都需要添加
|
||||
resource "tencentcloud_route_table_entry" "to_shared" {
|
||||
route_table_id = tencentcloud_route_table.prod_private.id
|
||||
destination_cidr_block = "10.10.0.0/16" # shared VPC CIDR
|
||||
next_type = "PEERCONN"
|
||||
next_hub = tencentcloud_vpc_peering_connection.main.id
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLB 负载均衡
|
||||
|
||||
### 公网 CLB
|
||||
|
||||
```hcl
|
||||
resource "tencentcloud_clb_instance" "web" {
|
||||
clb_name = "${var.project_name}-${var.environment}-web-clb"
|
||||
network_type = "OPEN" # OPEN=公网,INTERNAL=内网
|
||||
project_id = 0
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
|
||||
# 公网带宽计费(OPEN 类型需要)
|
||||
internet_charge_type = "BANDWIDTH_POSTPAID_BY_HOUR"
|
||||
internet_bandwidth_max_out = 10 # Mbps
|
||||
|
||||
security_groups = [tencentcloud_security_group.clb.id]
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# HTTP 监听器
|
||||
resource "tencentcloud_clb_listener" "http" {
|
||||
clb_id = tencentcloud_clb_instance.web.id
|
||||
listener_name = "http-80"
|
||||
port = 80
|
||||
protocol = "HTTP"
|
||||
}
|
||||
|
||||
# HTTPS 监听器(需要 SSL 证书)
|
||||
resource "tencentcloud_clb_listener" "https" {
|
||||
clb_id = tencentcloud_clb_instance.web.id
|
||||
listener_name = "https-443"
|
||||
port = 443
|
||||
protocol = "HTTPS"
|
||||
certificate_ssl_mode = "UNIDIRECTIONAL"
|
||||
certificate_id = var.ssl_cert_id # 从证书服务获取
|
||||
}
|
||||
|
||||
# 转发规则(七层)
|
||||
resource "tencentcloud_clb_listener_rule" "api" {
|
||||
clb_id = tencentcloud_clb_instance.web.id
|
||||
listener_id = tencentcloud_clb_listener.https.listener_id
|
||||
domain = "api.example.com"
|
||||
url = "/"
|
||||
scheduler = "WRR" # WRR/IP_HASH/LEAST_CONN
|
||||
|
||||
health_check_switch = true
|
||||
health_check_interval_time = 5
|
||||
health_check_health_num = 2
|
||||
health_check_unhealth_num = 2
|
||||
health_check_http_code = 2 # 2xx 视为健康
|
||||
health_check_http_path = "/health"
|
||||
health_check_http_domain = "api.example.com"
|
||||
health_check_http_method = "GET"
|
||||
|
||||
session_expire_time = 0 # 0=不开启会话保持
|
||||
}
|
||||
|
||||
# 后端绑定
|
||||
resource "tencentcloud_clb_attachment" "web" {
|
||||
clb_id = tencentcloud_clb_instance.web.id
|
||||
listener_id = tencentcloud_clb_listener.https.listener_id
|
||||
rule_id = tencentcloud_clb_listener_rule.api.rule_id
|
||||
|
||||
dynamic "targets" {
|
||||
for_each = tencentcloud_instance.web[*]
|
||||
content {
|
||||
instance_id = targets.value.id
|
||||
port = 8080
|
||||
weight = 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 内网 CLB(微服务场景)
|
||||
|
||||
```hcl
|
||||
resource "tencentcloud_clb_instance" "internal" {
|
||||
clb_name = "${var.project_name}-${var.environment}-internal-clb"
|
||||
network_type = "INTERNAL"
|
||||
project_id = 0
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
subnet_id = tencentcloud_subnet.private_a.id # 内网 CLB 需要指定子网
|
||||
tags = local.common_tags
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整三层网络示例
|
||||
|
||||
生产级三层网络架构(公网层 / 应用层 / 数据层)完整模板:
|
||||
|
||||
```hcl
|
||||
# ── VPC ──────────────────────────────────────────────────────
|
||||
resource "tencentcloud_vpc" "main" {
|
||||
name = "${var.project_name}-${var.environment}-vpc"
|
||||
cidr_block = "10.0.0.0/16"
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ── 公网子网(CLB、NAT)───────────────────────────────────────
|
||||
resource "tencentcloud_subnet" "public" {
|
||||
for_each = {
|
||||
"a" = { cidr = "10.0.1.0/24", az = "${var.region}-1" }
|
||||
"b" = { cidr = "10.0.2.0/24", az = "${var.region}-2" }
|
||||
}
|
||||
name = "${var.project_name}-${var.environment}-public-${each.key}"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
cidr_block = each.value.cidr
|
||||
availability_zone = each.value.az
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ── 应用子网(CVM、TKE Node)─────────────────────────────────
|
||||
resource "tencentcloud_subnet" "app" {
|
||||
for_each = {
|
||||
"a" = { cidr = "10.0.10.0/24", az = "${var.region}-1" }
|
||||
"b" = { cidr = "10.0.11.0/24", az = "${var.region}-2" }
|
||||
}
|
||||
name = "${var.project_name}-${var.environment}-app-${each.key}"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
cidr_block = each.value.cidr
|
||||
availability_zone = each.value.az
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ── 数据子网(MySQL、Redis)──────────────────────────────────
|
||||
resource "tencentcloud_subnet" "data" {
|
||||
for_each = {
|
||||
"a" = { cidr = "10.0.20.0/24", az = "${var.region}-1" }
|
||||
"b" = { cidr = "10.0.21.0/24", az = "${var.region}-2" }
|
||||
}
|
||||
name = "${var.project_name}-${var.environment}-data-${each.key}"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
cidr_block = each.value.cidr
|
||||
availability_zone = each.value.az
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ── EIP + NAT ────────────────────────────────────────────────
|
||||
resource "tencentcloud_eip" "nat" {
|
||||
name = "${var.project_name}-${var.environment}-nat-eip"
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "tencentcloud_nat_gateway" "main" {
|
||||
name = "${var.project_name}-${var.environment}-nat"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
bandwidth = 100
|
||||
max_concurrent = 1000000
|
||||
assigned_eip_set = [tencentcloud_eip.nat.public_ip]
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
# ── 私有路由表 → NAT ─────────────────────────────────────────
|
||||
resource "tencentcloud_route_table" "private" {
|
||||
name = "${var.project_name}-${var.environment}-private-rt"
|
||||
vpc_id = tencentcloud_vpc.main.id
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
resource "tencentcloud_route_table_entry" "nat_default" {
|
||||
route_table_id = tencentcloud_route_table.private.id
|
||||
destination_cidr_block = "0.0.0.0/0"
|
||||
next_type = "NAT"
|
||||
next_hub = tencentcloud_nat_gateway.main.id
|
||||
}
|
||||
|
||||
# 应用子网和数据子网都关联私有路由表
|
||||
resource "tencentcloud_route_table_association" "app" {
|
||||
for_each = tencentcloud_subnet.app
|
||||
subnet_id = each.value.id
|
||||
route_table_id = tencentcloud_route_table.private.id
|
||||
}
|
||||
|
||||
resource "tencentcloud_route_table_association" "data" {
|
||||
for_each = tencentcloud_subnet.data
|
||||
subnet_id = each.value.id
|
||||
route_table_id = tencentcloud_route_table.private.id
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user