@vincent119/go-copilot-rules 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent_agy/INSTALLATION.md +786 -0
- package/.agent_agy/README.md +243 -0
- package/.agent_agy/SKILLS_INDEX.md +516 -0
- package/.agent_agy/rules/go-core.copilot-instructions.md +251 -0
- package/.agent_agy/skills/go-api-design/SKILL.md +535 -0
- package/.agent_agy/skills/go-ci-tooling/SKILL.md +533 -0
- package/.agent_agy/skills/go-configuration/SKILL.md +609 -0
- package/.agent_agy/skills/go-database/SKILL.md +412 -0
- package/.agent_agy/skills/go-ddd/SKILL.md +374 -0
- package/.agent_agy/skills/go-dependency-injection/SKILL.md +546 -0
- package/.agent_agy/skills/go-domain-events/SKILL.md +525 -0
- package/.agent_agy/skills/go-examples/SKILL.md +690 -0
- package/.agent_agy/skills/go-graceful-shutdown/SKILL.md +708 -0
- package/.agent_agy/skills/go-grpc/SKILL.md +484 -0
- package/.agent_agy/skills/go-http-advanced/SKILL.md +494 -0
- package/.agent_agy/skills/go-observability/SKILL.md +684 -0
- package/.agent_agy/skills/go-testing-advanced/SKILL.md +573 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/cli/install.js +344 -0
- package/package.json +47 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: go-configuration
|
|
3
|
+
description: |
|
|
4
|
+
Go 設定管理最佳實務:Viper 配置、環境變數優先級、Secrets 處理、設定驗證、
|
|
5
|
+
動態重載、多環境管理、12-Factor App 原則。
|
|
6
|
+
|
|
7
|
+
**適用場景**:使用 Viper、環境變數管理、Secrets 處理、設定驗證、動態重載設定、
|
|
8
|
+
多環境配置(dev/staging/prod)、Kubernetes ConfigMap/Secret 整合。
|
|
9
|
+
|
|
10
|
+
**關鍵字**:configuration, viper, environment variables, secrets, validation, env vars,
|
|
11
|
+
config reload, 12-factor, configmap, kubernetes config, dotenv, yaml config
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Go 設定管理規範
|
|
15
|
+
|
|
16
|
+
> **相關 Skills**:本規範建議搭配 `go-observability`(日誌配置)與 `go-graceful-shutdown`(動態重載)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 12-Factor App 原則
|
|
21
|
+
|
|
22
|
+
### 核心原則
|
|
23
|
+
|
|
24
|
+
1. **設定與程式碼分離**:不將設定硬編碼
|
|
25
|
+
2. **環境變數優先**:使用環境變數覆蓋預設設定
|
|
26
|
+
3. **Secrets 管理**:敏感資訊不進入版本控制
|
|
27
|
+
4. **多環境支援**:dev/staging/prod 使用相同程式碼
|
|
28
|
+
|
|
29
|
+
### 優先級(從高到低)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
環境變數 > 設定檔 > 預設值
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Viper 基本使用
|
|
38
|
+
|
|
39
|
+
### 安裝
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
go get github.com/spf13/viper
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 基本配置
|
|
46
|
+
|
|
47
|
+
```go
|
|
48
|
+
package config
|
|
49
|
+
|
|
50
|
+
import (
|
|
51
|
+
"fmt"
|
|
52
|
+
"github.com/spf13/viper"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
type Config struct {
|
|
56
|
+
Server ServerConfig `mapstructure:"server"`
|
|
57
|
+
Database DatabaseConfig `mapstructure:"database"`
|
|
58
|
+
Redis RedisConfig `mapstructure:"redis"`
|
|
59
|
+
Log LogConfig `mapstructure:"log"`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type ServerConfig struct {
|
|
63
|
+
Host string `mapstructure:"host"`
|
|
64
|
+
Port int `mapstructure:"port"`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type DatabaseConfig struct {
|
|
68
|
+
Host string `mapstructure:"host"`
|
|
69
|
+
Port int `mapstructure:"port"`
|
|
70
|
+
User string `mapstructure:"user"`
|
|
71
|
+
Password string `mapstructure:"password"` // 敏感資訊
|
|
72
|
+
Database string `mapstructure:"database"`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type RedisConfig struct {
|
|
76
|
+
Host string `mapstructure:"host"`
|
|
77
|
+
Port int `mapstructure:"port"`
|
|
78
|
+
Password string `mapstructure:"password"`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type LogConfig struct {
|
|
82
|
+
Level string `mapstructure:"level"`
|
|
83
|
+
Format string `mapstructure:"format"` // json or console
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func Load() (*Config, error) {
|
|
87
|
+
// 1. 設定檔路徑
|
|
88
|
+
viper.SetConfigName("config") // config.yaml
|
|
89
|
+
viper.SetConfigType("yaml")
|
|
90
|
+
viper.AddConfigPath(".") // 當前目錄
|
|
91
|
+
viper.AddConfigPath("./configs") // ./configs 目錄
|
|
92
|
+
viper.AddConfigPath("/etc/myapp") // 系統目錄
|
|
93
|
+
|
|
94
|
+
// 2. 預設值
|
|
95
|
+
setDefaults()
|
|
96
|
+
|
|
97
|
+
// 3. 讀取設定檔
|
|
98
|
+
if err := viper.ReadInConfig(); err != nil {
|
|
99
|
+
// 設定檔不存在不算錯誤(使用預設值 + 環境變數)
|
|
100
|
+
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
|
101
|
+
return nil, fmt.Errorf("read config: %w", err)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 4. 環境變數覆蓋
|
|
106
|
+
viper.AutomaticEnv()
|
|
107
|
+
viper.SetEnvPrefix("MYAPP") // 環境變數前綴:MYAPP_SERVER_PORT
|
|
108
|
+
|
|
109
|
+
// 5. 綁定到 Struct
|
|
110
|
+
var cfg Config
|
|
111
|
+
if err := viper.Unmarshal(&cfg); err != nil {
|
|
112
|
+
return nil, fmt.Errorf("unmarshal config: %w", err)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 6. 驗證設定
|
|
116
|
+
if err := cfg.Validate(); err != nil {
|
|
117
|
+
return nil, fmt.Errorf("validate config: %w", err)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return &cfg, nil
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func setDefaults() {
|
|
124
|
+
viper.SetDefault("server.host", "0.0.0.0")
|
|
125
|
+
viper.SetDefault("server.port", 8080)
|
|
126
|
+
viper.SetDefault("log.level", "info")
|
|
127
|
+
viper.SetDefault("log.format", "json")
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 設定檔範例(config.yaml)
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
server:
|
|
135
|
+
host: 0.0.0.0
|
|
136
|
+
port: 8080
|
|
137
|
+
|
|
138
|
+
database:
|
|
139
|
+
host: localhost
|
|
140
|
+
port: 5432
|
|
141
|
+
user: postgres
|
|
142
|
+
password: ${DB_PASSWORD} # 從環境變數讀取(Viper 不支援)
|
|
143
|
+
database: myapp
|
|
144
|
+
|
|
145
|
+
redis:
|
|
146
|
+
host: localhost
|
|
147
|
+
port: 6379
|
|
148
|
+
password: ""
|
|
149
|
+
|
|
150
|
+
log:
|
|
151
|
+
level: info
|
|
152
|
+
format: json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 環境變數管理
|
|
158
|
+
|
|
159
|
+
### 命名慣例
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# 格式:<PREFIX>_<SECTION>_<KEY>
|
|
163
|
+
export MYAPP_SERVER_PORT=8080
|
|
164
|
+
export MYAPP_DATABASE_HOST=postgres.example.com
|
|
165
|
+
export MYAPP_DATABASE_PASSWORD=secret123
|
|
166
|
+
export MYAPP_LOG_LEVEL=debug
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Viper 環境變數綁定
|
|
170
|
+
|
|
171
|
+
```go
|
|
172
|
+
import "strings"
|
|
173
|
+
|
|
174
|
+
func Load() (*Config, error) {
|
|
175
|
+
// 自動替換 . 為 _
|
|
176
|
+
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
177
|
+
viper.AutomaticEnv()
|
|
178
|
+
viper.SetEnvPrefix("MYAPP")
|
|
179
|
+
|
|
180
|
+
// 現在可以這樣使用:
|
|
181
|
+
// MYAPP_SERVER_PORT=9000 -> viper.GetInt("server.port")
|
|
182
|
+
|
|
183
|
+
var cfg Config
|
|
184
|
+
if err := viper.Unmarshal(&cfg); err != nil {
|
|
185
|
+
return nil, err
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return &cfg, nil
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### .env 檔案(開發環境)
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# .env
|
|
196
|
+
MYAPP_SERVER_PORT=8080
|
|
197
|
+
MYAPP_DATABASE_HOST=localhost
|
|
198
|
+
MYAPP_DATABASE_PASSWORD=dev_password
|
|
199
|
+
MYAPP_LOG_LEVEL=debug
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```go
|
|
203
|
+
import "github.com/joho/godotenv"
|
|
204
|
+
|
|
205
|
+
func init() {
|
|
206
|
+
// 開發環境載入 .env(生產環境不需要)
|
|
207
|
+
if os.Getenv("ENV") != "production" {
|
|
208
|
+
_ = godotenv.Load()
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Secrets 處理
|
|
216
|
+
|
|
217
|
+
### 原則
|
|
218
|
+
|
|
219
|
+
- **不要將 Secrets 提交到版本控制**
|
|
220
|
+
- **使用環境變數或 Secret Management 工具**
|
|
221
|
+
- **限制 Secrets 存取權限**
|
|
222
|
+
|
|
223
|
+
### 方法 1:環境變數
|
|
224
|
+
|
|
225
|
+
```go
|
|
226
|
+
type DatabaseConfig struct {
|
|
227
|
+
Host string `mapstructure:"host"`
|
|
228
|
+
Port int `mapstructure:"port"`
|
|
229
|
+
User string `mapstructure:"user"`
|
|
230
|
+
Password string `mapstructure:"password"` // 從環境變數讀取
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
func main() {
|
|
234
|
+
// 直接從環境變數讀取
|
|
235
|
+
dbPassword := os.Getenv("DB_PASSWORD")
|
|
236
|
+
if dbPassword == "" {
|
|
237
|
+
log.Fatal("DB_PASSWORD not set")
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 方法 2:Kubernetes Secret
|
|
243
|
+
|
|
244
|
+
```yaml
|
|
245
|
+
# k8s-secret.yaml
|
|
246
|
+
apiVersion: v1
|
|
247
|
+
kind: Secret
|
|
248
|
+
metadata:
|
|
249
|
+
name: myapp-secrets
|
|
250
|
+
type: Opaque
|
|
251
|
+
data:
|
|
252
|
+
db-password: cGFzc3dvcmQxMjM= # base64 編碼
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```yaml
|
|
256
|
+
# deployment.yaml
|
|
257
|
+
env:
|
|
258
|
+
- name: MYAPP_DATABASE_PASSWORD
|
|
259
|
+
valueFrom:
|
|
260
|
+
secretKeyRef:
|
|
261
|
+
name: myapp-secrets
|
|
262
|
+
key: db-password
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 方法 3:AWS Secrets Manager / Vault
|
|
266
|
+
|
|
267
|
+
```go
|
|
268
|
+
import (
|
|
269
|
+
"github.com/aws/aws-sdk-go/aws/session"
|
|
270
|
+
"github.com/aws/aws-sdk-go/service/secretsmanager"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
func LoadSecrets() (string, error) {
|
|
274
|
+
sess := session.Must(session.NewSession())
|
|
275
|
+
svc := secretsmanager.New(sess)
|
|
276
|
+
|
|
277
|
+
input := &secretsmanager.GetSecretValueInput{
|
|
278
|
+
SecretId: aws.String("myapp/database/password"),
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
result, err := svc.GetSecretValue(input)
|
|
282
|
+
if err != nil {
|
|
283
|
+
return "", err
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return *result.SecretString, nil
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 設定驗證
|
|
293
|
+
|
|
294
|
+
### 使用 validator
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
go get github.com/go-playground/validator/v10
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```go
|
|
301
|
+
import "github.com/go-playground/validator/v10"
|
|
302
|
+
|
|
303
|
+
type Config struct {
|
|
304
|
+
Server ServerConfig `mapstructure:"server" validate:"required"`
|
|
305
|
+
Database DatabaseConfig `mapstructure:"database" validate:"required"`
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
type ServerConfig struct {
|
|
309
|
+
Host string `mapstructure:"host" validate:"required,hostname|ip"`
|
|
310
|
+
Port int `mapstructure:"port" validate:"required,min=1,max=65535"`
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
type DatabaseConfig struct {
|
|
314
|
+
Host string `mapstructure:"host" validate:"required"`
|
|
315
|
+
Port int `mapstructure:"port" validate:"required,min=1,max=65535"`
|
|
316
|
+
User string `mapstructure:"user" validate:"required"`
|
|
317
|
+
Password string `mapstructure:"password" validate:"required,min=8"`
|
|
318
|
+
Database string `mapstructure:"database" validate:"required"`
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
func (c *Config) Validate() error {
|
|
322
|
+
validate := validator.New()
|
|
323
|
+
return validate.Struct(c)
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 自訂驗證邏輯
|
|
328
|
+
|
|
329
|
+
```go
|
|
330
|
+
func (c *Config) Validate() error {
|
|
331
|
+
// 使用 validator
|
|
332
|
+
validate := validator.New()
|
|
333
|
+
if err := validate.Struct(c); err != nil {
|
|
334
|
+
return err
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 自訂邏輯
|
|
338
|
+
if c.Database.Host == "localhost" && os.Getenv("ENV") == "production" {
|
|
339
|
+
return errors.New("production should not use localhost database")
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if c.Log.Level != "debug" && c.Log.Level != "info" && c.Log.Level != "warn" && c.Log.Level != "error" {
|
|
343
|
+
return fmt.Errorf("invalid log level: %s", c.Log.Level)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return nil
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## 動態重載設定
|
|
353
|
+
|
|
354
|
+
### 監聽設定檔變更
|
|
355
|
+
|
|
356
|
+
```go
|
|
357
|
+
import "github.com/fsnotify/fsnotify"
|
|
358
|
+
|
|
359
|
+
func WatchConfig(cfg *Config) {
|
|
360
|
+
viper.WatchConfig()
|
|
361
|
+
viper.OnConfigChange(func(e fsnotify.Event) {
|
|
362
|
+
log.Info("config file changed", zap.String("file", e.Name))
|
|
363
|
+
|
|
364
|
+
// 重新載入設定
|
|
365
|
+
var newCfg Config
|
|
366
|
+
if err := viper.Unmarshal(&newCfg); err != nil {
|
|
367
|
+
log.Error("failed to reload config", zap.Error(err))
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if err := newCfg.Validate(); err != nil {
|
|
372
|
+
log.Error("invalid config after reload", zap.Error(err))
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 更新設定(需要 atomic 操作)
|
|
377
|
+
*cfg = newCfg
|
|
378
|
+
log.Info("config reloaded successfully")
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 使用 atomic.Value(安全更新)
|
|
384
|
+
|
|
385
|
+
```go
|
|
386
|
+
import "sync/atomic"
|
|
387
|
+
|
|
388
|
+
type ConfigHolder struct {
|
|
389
|
+
cfg atomic.Value
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
func NewConfigHolder(cfg *Config) *ConfigHolder {
|
|
393
|
+
holder := &ConfigHolder{}
|
|
394
|
+
holder.cfg.Store(cfg)
|
|
395
|
+
return holder
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
func (h *ConfigHolder) Get() *Config {
|
|
399
|
+
return h.cfg.Load().(*Config)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
func (h *ConfigHolder) Update(cfg *Config) {
|
|
403
|
+
h.cfg.Store(cfg)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// 使用範例
|
|
407
|
+
func main() {
|
|
408
|
+
cfg, _ := config.Load()
|
|
409
|
+
holder := NewConfigHolder(cfg)
|
|
410
|
+
|
|
411
|
+
// 監聽變更
|
|
412
|
+
go func() {
|
|
413
|
+
viper.WatchConfig()
|
|
414
|
+
viper.OnConfigChange(func(e fsnotify.Event) {
|
|
415
|
+
var newCfg Config
|
|
416
|
+
if err := viper.Unmarshal(&newCfg); err != nil {
|
|
417
|
+
log.Error("reload failed", zap.Error(err))
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
holder.Update(&newCfg)
|
|
421
|
+
})
|
|
422
|
+
}()
|
|
423
|
+
|
|
424
|
+
// 讀取設定(執行緒安全)
|
|
425
|
+
currentCfg := holder.Get()
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## 多環境管理
|
|
432
|
+
|
|
433
|
+
### 方法 1:環境變數指定設定檔
|
|
434
|
+
|
|
435
|
+
```go
|
|
436
|
+
func Load() (*Config, error) {
|
|
437
|
+
env := os.Getenv("ENV")
|
|
438
|
+
if env == "" {
|
|
439
|
+
env = "development"
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
configFile := fmt.Sprintf("config.%s.yaml", env)
|
|
443
|
+
viper.SetConfigName(configFile)
|
|
444
|
+
viper.AddConfigPath("./configs")
|
|
445
|
+
|
|
446
|
+
// config.development.yaml
|
|
447
|
+
// config.staging.yaml
|
|
448
|
+
// config.production.yaml
|
|
449
|
+
|
|
450
|
+
if err := viper.ReadInConfig(); err != nil {
|
|
451
|
+
return nil, err
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ...
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### 方法 2:Base Config + Override
|
|
459
|
+
|
|
460
|
+
```go
|
|
461
|
+
func Load() (*Config, error) {
|
|
462
|
+
// 1. 載入基礎設定
|
|
463
|
+
viper.SetConfigName("config.base")
|
|
464
|
+
viper.AddConfigPath("./configs")
|
|
465
|
+
if err := viper.ReadInConfig(); err != nil {
|
|
466
|
+
return nil, err
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// 2. 載入環境特定設定(覆蓋)
|
|
470
|
+
env := os.Getenv("ENV")
|
|
471
|
+
if env != "" {
|
|
472
|
+
viper.SetConfigName(fmt.Sprintf("config.%s", env))
|
|
473
|
+
if err := viper.MergeInConfig(); err != nil {
|
|
474
|
+
// 環境設定可選
|
|
475
|
+
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
|
476
|
+
return nil, err
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// 3. 環境變數覆蓋
|
|
482
|
+
viper.AutomaticEnv()
|
|
483
|
+
|
|
484
|
+
// ...
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Kubernetes ConfigMap 整合
|
|
491
|
+
|
|
492
|
+
### ConfigMap 定義
|
|
493
|
+
|
|
494
|
+
```yaml
|
|
495
|
+
# configmap.yaml
|
|
496
|
+
apiVersion: v1
|
|
497
|
+
kind: ConfigMap
|
|
498
|
+
metadata:
|
|
499
|
+
name: myapp-config
|
|
500
|
+
data:
|
|
501
|
+
config.yaml: |
|
|
502
|
+
server:
|
|
503
|
+
host: 0.0.0.0
|
|
504
|
+
port: 8080
|
|
505
|
+
log:
|
|
506
|
+
level: info
|
|
507
|
+
format: json
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Deployment 掛載
|
|
511
|
+
|
|
512
|
+
```yaml
|
|
513
|
+
# deployment.yaml
|
|
514
|
+
apiVersion: apps/v1
|
|
515
|
+
kind: Deployment
|
|
516
|
+
metadata:
|
|
517
|
+
name: myapp
|
|
518
|
+
spec:
|
|
519
|
+
template:
|
|
520
|
+
spec:
|
|
521
|
+
containers:
|
|
522
|
+
- name: myapp
|
|
523
|
+
image: myapp:latest
|
|
524
|
+
volumeMounts:
|
|
525
|
+
- name: config
|
|
526
|
+
mountPath: /etc/myapp
|
|
527
|
+
readOnly: true
|
|
528
|
+
env:
|
|
529
|
+
- name: MYAPP_DATABASE_PASSWORD
|
|
530
|
+
valueFrom:
|
|
531
|
+
secretKeyRef:
|
|
532
|
+
name: myapp-secrets
|
|
533
|
+
key: db-password
|
|
534
|
+
volumes:
|
|
535
|
+
- name: config
|
|
536
|
+
configMap:
|
|
537
|
+
name: myapp-config
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### 程式碼讀取
|
|
541
|
+
|
|
542
|
+
```go
|
|
543
|
+
func Load() (*Config, error) {
|
|
544
|
+
viper.SetConfigName("config")
|
|
545
|
+
viper.SetConfigType("yaml")
|
|
546
|
+
viper.AddConfigPath("/etc/myapp") // Kubernetes 掛載路徑
|
|
547
|
+
viper.AddConfigPath(".") // 本地開發
|
|
548
|
+
|
|
549
|
+
if err := viper.ReadInConfig(); err != nil {
|
|
550
|
+
return nil, err
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// 環境變數覆蓋(包含 Secret)
|
|
554
|
+
viper.AutomaticEnv()
|
|
555
|
+
|
|
556
|
+
var cfg Config
|
|
557
|
+
if err := viper.Unmarshal(&cfg); err != nil {
|
|
558
|
+
return nil, err
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return &cfg, nil
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## 檢查清單
|
|
568
|
+
|
|
569
|
+
**基本設定**
|
|
570
|
+
- [ ] 設定與程式碼分離(不硬編碼)
|
|
571
|
+
- [ ] 使用 Viper 管理設定
|
|
572
|
+
- [ ] 設定多個搜尋路徑(當前目錄、系統目錄)
|
|
573
|
+
- [ ] 定義合理的預設值
|
|
574
|
+
|
|
575
|
+
**環境變數**
|
|
576
|
+
- [ ] 環境變數優先於設定檔
|
|
577
|
+
- [ ] 使用統一的前綴(例如 `MYAPP_`)
|
|
578
|
+
- [ ] 支援 `.env` 檔案(開發環境)
|
|
579
|
+
- [ ] 生產環境使用環境變數
|
|
580
|
+
|
|
581
|
+
**Secrets**
|
|
582
|
+
- [ ] Secrets 不提交到版本控制
|
|
583
|
+
- [ ] 使用環境變數或 Secret Management
|
|
584
|
+
- [ ] 限制 Secrets 存取權限
|
|
585
|
+
- [ ] 定期輪替 Secrets
|
|
586
|
+
|
|
587
|
+
**驗證**
|
|
588
|
+
- [ ] 使用 `validator` 驗證設定
|
|
589
|
+
- [ ] 啟動時驗證設定(Fail Fast)
|
|
590
|
+
- [ ] 提供清晰的錯誤訊息
|
|
591
|
+
- [ ] 驗證業務邏輯規則
|
|
592
|
+
|
|
593
|
+
**多環境**
|
|
594
|
+
- [ ] 支援 dev/staging/prod 環境
|
|
595
|
+
- [ ] 環境特定設定覆蓋基礎設定
|
|
596
|
+
- [ ] 環境變數覆蓋所有設定
|
|
597
|
+
- [ ] 文件說明各環境差異
|
|
598
|
+
|
|
599
|
+
**動態重載**
|
|
600
|
+
- [ ] 使用 `viper.WatchConfig` 監聽變更
|
|
601
|
+
- [ ] 重載後重新驗證設定
|
|
602
|
+
- [ ] 使用 `atomic.Value` 安全更新
|
|
603
|
+
- [ ] 記錄重載事件
|
|
604
|
+
|
|
605
|
+
**Kubernetes**
|
|
606
|
+
- [ ] 使用 ConfigMap 管理設定
|
|
607
|
+
- [ ] 使用 Secret 管理敏感資訊
|
|
608
|
+
- [ ] 正確設定 `volumeMounts` 路徑
|
|
609
|
+
- [ ] 考慮設定變更的部署策略
|