[add] add skill-L10n

This commit is contained in:
2026-03-30 21:15:34 +08:00
parent 11fe47983a
commit 4ef365872a
12 changed files with 3140 additions and 1 deletions

View File

@@ -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 图表部署

View 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` 报告,便于审阅与回滚。

View File

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

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

View 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()

View 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 4State 管理
参考:[🔒 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 6State 操作
```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

View 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删除索引 1web-02会导致 web-03 重建
resource "tencentcloud_instance" "web" {
count = 3
instance_name = "${local.name_prefix}-web-${format("%02d", count.index + 1)}"
# ...
}
# ✅ 子网 for_eachmap 形式,包含元数据)
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
# prodCI/CD 中从 Secrets 注入)
export TF

View File

@@ -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生产推荐用 CLBCVM 不分配公网)
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

View File

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

View File

@@ -0,0 +1,238 @@
# Provider 配置参考
> 目录
> - [完整 Provider 配置](#完整-provider-配置)
> - [versions.tf 模板](#versionstf-模板)
> - [多 RegionProvider 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" {
# 使用默认 providerap-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 # 秒,最长 4320012小时
# 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 # 应无意外变更
```
检查新版本 changeloghttps://github.com/tencentcloudstack/terraform-provider-tencentcloud/releases

View File

@@ -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
# CVMcount 语法,导入到数组第 0 个)
terraform import 'tencentcloud_instance.web[0]' ins-xxxxxxxx
# CVMfor_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 开启版本控制后恢复历史版本 |

View File

@@ -0,0 +1,504 @@
# 网络资源参考VPC / 子网 / 安全组 / NAT / CLB
> 目录
> - [VPC](#vpc)
> - [子网](#子网)
> - [安全组](#安全组)
> - [路由表](#路由表)
> - [NAT 网关](#nat-网关)
> - [弹性 IPEIP](#弹性-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
# 带宽上限 Mbps100/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
}
```