Config va .env bilan ishlash
Oldingi darslarda baza manzili os.Getenv("DATABASE_URL") orqali olindi. Lekin API’da sozlamalar bittadan ancha ko‘p bo‘ladi: port, baza manzili, JWT maxfiy kaliti, timeoutlar, log darajasi, CORS uchun ruxsat etilgan domenlar.
Bu qiymatlar muhitga qarab farq qiladi. Kompyuteringizda baza localhostda, test serverida boshqa manzilda, production’da esa butunlay boshqa joyda turadi. Kod esa hamma joyda bir xil bo‘lishi kerak.
Bu darsda sozlamalarni koddan ajratish, ularni bitta joyda o‘qish, tekshirish va xavfsiz saqlashni o‘rganamiz. Muhit o‘zgaruvchilari bilan ishlash asoslari Environment o‘zgaruvchilari darsida berilgan. Bu yerda ularni API loyihasida qo‘llaymiz.
Nima uchun kodga yozib bo‘lmaydi?
// ❌ Hech qachon bunday qilmang
db, _ := sql.Open("pgx", "postgres://app:[email protected]:5432/books")
Bu kodda uchta muammo bor:
- Parol Git tarixida qoladi. Keyinroq o‘chirilsa ham, u eski commit’larda saqlanib qoladi. Repozitoriyga kirish huquqi bor har bir kishi, jumladan sobiq xodimlar ham, uni ko‘ra oladi.
- Muhitni almashtirish uchun kodni o‘zgartirish kerak. Test serveriga chiqarish uchun qaytadan kompilyatsiya qilishga to‘g‘ri keladi.
- Parolni almashtirish qiyinlashadi. Parol sizib chiqsa, uni yangilash uchun kodni o‘zgartirib, qaytadan deploy qilish kerak.
Keng tarqalgan Twelve-Factor App metodologiyasida bu qoida shunday ifodalangan: konfiguratsiya muhit o‘zgaruvchilarida saqlanadi. Bitta build qilingan dastur istalgan muhitda ishlaydi, faqat unga berilgan muhit o‘zgaruvchilari farq qiladi.
Config struct
Birinchi qadam — barcha sozlamalarni bitta struct’ga yig‘ish:
type Config struct {
Env string // development, staging, production
HTTPAddr string // ":8080"
DatabaseURL string // postgres://...
JWTSecret string // token imzolash kaliti
ReadTimeout time.Duration // HTTP server timeouti
DBMaxConns int32 // ulanishlar puli hajmi
CORSOrigins []string // ruxsat etilgan domenlar
LogLevel slog.Level // debug, info, warn, error
}
Afzalliklari:
- dastur qanday sozlamalarga muhtojligi bitta joyda ko‘rinib turadi;
- har bir qiymat kerakli turda saqlanadi:
time.Duration,int32,[]string; os.Getenvkod bo‘ylab tarqalib ketmaydi.
O‘qish va tekshirish
Sozlamalar dastur ishga tushganda bir marta o‘qiladi va darhol tekshiriladi. Biror majburiy qiymat yo‘q bo‘lsa, dastur ishga tushmasligi kerak.
Nima uchun darhol? JWT kaliti berilmagan server ishga tushib, faqat birinchi login so‘rovida xato bersa, muammo kechikib aniqlanadi. Ishga tushish paytidagi aniq xato esa deploy bosqichidayoq ko‘rinadi.
Barcha xatolarni birma-bir emas, bir vaqtda ko‘rsatish ham muhim. Aks holda bitta xatoni tuzatib qayta ishga tushirasiz, keyingisi chiqadi va bu jarayon takrorlanaveradi.
| main.go | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | |
Hech narsa bermasdan ishga tushiramiz:
Noto‘g‘ri qiymatlar bilan:
DATABASE_URL=postgres://localhost/books JWT_SECRET=qisqa \
DB_MAX_CONNS=ko‘p HTTP_READ_TIMEOUT=10 go run main.go
konfiguratsiya xatosi:
HTTP_READ_TIMEOUT davomiylik bo‘lishi kerak (masalan 5s, 1m)
DB_MAX_CONNS musbat butun son bo‘lishi kerak
JWT_SECRET kamida 32 belgi bo‘lishi kerak
To‘g‘ri qiymatlar bilan:
DATABASE_URL=postgres://localhost/books \
JWT_SECRET=$(openssl rand -hex 32) \
CORS_ORIGINS="https://go-lang.uz, https://admin.go-lang.uz" \
go run main.go
Muhit: development, manzil: :8080, pul: 25, timeout: 10s, CORS: [https://go-lang.uz https://admin.go-lang.uz]
Kod qanday ishladi:
loaderhar bir xatoni yig‘ib boradi va birinchi xatoda to‘xtamaydi.errors.Joinbarcha xatolarni bittaga birlashtiradi. Har bir xato alohida qatorda chiqadi.- Ixtiyoriy qiymatlar uchun standart qiymat berildi. Majburiy qiymatlar uchun esa standart qiymat yo‘q:
DATABASE_URLuchun "taxminiy" qiymat bo‘lmaydi. openssl rand -hex 3232 baytlik tasodifiy kalit yaratadi. JWT kalitini aynan shunday yaratish kerak, uni o‘ylab topish emas.
.env fayli
Ishlab chiqish paytida har safar o‘nlab o‘zgaruvchini terminalga yozish noqulay. Shuning uchun ular loyiha ildizidagi .env fayliga yoziladi:
APP_ENV=development
HTTP_ADDR=:8080
DATABASE_URL=postgres://app:secret@localhost:5432/books?sslmode=disable
JWT_SECRET=8f3c1a9e7b2d4f6a0c5e9b1d3f7a2c4e6b8d0f1a3c5e7b9d2f4a6c8e0b1d3f5a
LOG_LEVEL=debug
CORS_ORIGINS=http://localhost:3000
Go .env faylini o‘zi o‘qimaydi. Buning uchun github.com/joho/godotenv paketi keng qo‘llanadi:
import "github.com/joho/godotenv"
func main() {
// .env bo‘lsa yuklanadi, bo‘lmasa jimgina o‘tib ketiladi
if err := godotenv.Load(); err != nil && !errors.Is(err, fs.ErrNotExist) {
log.Fatalf(".env faylini o‘qib bo‘lmadi: %v", err)
}
cfg, err := Load()
// ...
}
godotenv.Load() .env faylidagi qiymatlarni jarayon muhitiga yozadi. Shundan keyin os.Getenv ularni ko‘radi.
Muhim jihatlar:
- Mavjud o‘zgaruvchilar ustidan yozilmaydi. Muhitda
DATABASE_URLallaqachon bo‘lsa,.envdagi qiymat e’tiborsiz qoladi. Shu sababli production’da serverning haqiqiy muhit o‘zgaruvchilari doim ustun turadi. - Fayl bo‘lmasligi xato emas. Production’da
.envfayli bo‘lmaydi, qiymatlar to‘g‘ridan-to‘g‘ri muhitdan keladi. Shuning uchunfs.ErrNotExistalohida tekshiriladi. godotenvfaqatmainpaketida chaqiriladi. Kutubxona va boshqa paketlar muhitni o‘zgartirmasligi kerak.
.envni Git’dan himoyalash
.env faylida parollar bor. U hech qachon repozitoriyga tushmasligi kerak:
Oxirgi qator .env.example faylini istisno qiladi. Bu fayl commit qilinadi va loyihaga qanday o‘zgaruvchilar kerakligini ko‘rsatadi. Unda maxfiy qiymatlar bo‘lmaydi:
APP_ENV=development
HTTP_ADDR=:8080
# postgres://USER:PASSWORD@HOST:PORT/DB?sslmode=disable
DATABASE_URL=
# openssl rand -hex 32 buyrug‘i bilan yarating
JWT_SECRET=
LOG_LEVEL=info
CORS_ORIGINS=http://localhost:3000
Yangi dasturchi loyihani yuklab olgach, cp .env.example .env bajaradi va qiymatlarni to‘ldiradi.
Danger
Maxfiy qiymat bir marta commit qilinib, GitHub’ga yuklangan bo‘lsa, faylni o‘chirish yetarli emas. Uni zudlik bilan almashtiring: yangi parol, yangi JWT kaliti. Ochiq repozitoriylarni avtomatik skanerlaydigan botlar sizib chiqqan kalitlarni bir necha daqiqada topadi.
Maxfiy qiymatlarni logdan yashirish
Sozlamalarni debug uchun logga chiqarish qulay. Lekin slog.Info("config", "cfg", cfg) qatori JWT kalitini ham logga yozib yuboradi.
Himoya usullaridan biri — maxfiy qiymatlar uchun maxsus tur yaratish:
{HTTPAddr::8080 DatabaseURL:[YASHIRILGAN] JWTSecret:[YASHIRILGAN]}
{"time":"...","level":"INFO","msg":"sozlamalar","addr":":8080","db":"[YASHIRILGAN]","jwt":"[YASHIRILGAN]"}
String()metodifmtpaketi uchun ishlaydi.LogValue()metodislog.LogValuerinterfeysini amalga oshiradi vasloguchun ishlaydi.- Haqiqiy qiymat faqat
Reveal()orqali olinadi. Bu nom kodni o‘qiyotgan odamga maxfiy qiymat ishlatilayotganini aniq ko‘rsatadi.
Config’ni qanday uzatish kerak?
Configni global o‘zgaruvchiga saqlab, istalgan joydan o‘qish vasvasasi bor. Lekin clean architecture darsidagi kabi, bog‘liqliklar ochiq uzatilgani yaxshiroq:
func main() {
cfg, err := config.Load()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
pool, err := newPool(ctx, cfg.DatabaseURL.Reveal(), cfg.DBMaxConns)
// ...
tokens := auth.NewTokenService(cfg.JWTSecret.Reveal(), 15*time.Minute)
// ...
srv := &http.Server{
Addr: cfg.HTTPAddr,
ReadTimeout: cfg.ReadTimeout,
// ...
}
}
Har bir komponent faqat o‘ziga kerakli qiymatlarni oladi. Token servisi baza manzilini bilmaydi, repository esa JWT kalitini bilmaydi. Testlarda ham kerakli qiymatni to‘g‘ridan-to‘g‘ri berish mumkin, muhit o‘zgaruvchilarini sozlash shart emas.
Production’da maxfiy qiymatlar
Serverda qiymatlar turli yo‘llar bilan beriladi:
| Muhit | Qanday beriladi |
|---|---|
systemd servis |
EnvironmentFile=/etc/books-api/env (fayl huquqlari 600) |
| Docker | docker run --env-file yoki -e |
| Docker Compose | environment: yoki env_file: bo‘limi |
| Kubernetes | Secret obyekti orqali |
| Bulut | AWS Secrets Manager, Google Secret Manager, HashiCorp Vault |
Maxfiy qiymatlarni markazlashgan boshqaruvchida (secret manager) saqlash eng ishonchli usul. U kim qachon qaysi kalitni o‘qiganini qayd etadi va kalitlarni avtomatik almashtirish imkonini beradi.
Xulosa
- Sozlamalar koddan ajratiladi va muhit o‘zgaruvchilari orqali beriladi.
- Barcha sozlamalar bitta
Configstruct’ida, kerakli turlarda saqlanadi. - Config dastur boshida bir marta o‘qiladi, tekshiriladi va barcha xatolar birdaniga ko‘rsatiladi.
- Ishlab chiqishda
.envvagodotenvqulay..envGit’ga tushmaydi, uning o‘rniga.env.examplecommit qilinadi. - Maxfiy qiymatlar uchun alohida tur logga va
fmtchiqishiga tushishining oldini oladi. - Config global o‘zgaruvchi orqali emas, komponentlarga parametr sifatida uzatiladi.
Shu bilan ma’lumotlar bazasi bo‘limi tugadi. Keyingi bo‘limda foydalanuvchilarni tanish — autentifikatsiya mavzusini boshlaymiz.