การสร้างแม่แบบการกำหนดค่าด้วย envsubst และ heredoc
สร้างการกำหนดค่าขณะทำงานจากตัวแปรสภาพแวดล้อมด้วย envsubst และ heredoc ที่ใส่เครื่องหมายคำพูด
การสร้างแม่แบบการกำหนดค่าด้วย envsubst และ heredoc เป็นบทเรียน DevOps Bootcamp ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DevOps Bootcamp และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DevOps Bootcamp มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดการสร้างไฟล์ตั้งค่าจากแม่แบบขณะทำงานจึงสำคัญ
ในกระบวนการ DevOps และการทำงานกับคอนเทนเนอร์ ไฟล์ตั้งค่าอย่าง nginx.conf, prometheus.yml และ docker-compose.yml มักต้องเปลี่ยนไปตามสภาพแวดล้อม เช่น การทดสอบก่อนใช้งาน การใช้งานจริง และ DR การฝังค่าแบบตายตัวทำให้เกิดความคลาดเคลื่อนระหว่างสภาพแวดล้อมและการเปิดเผยข้อมูลลับ
ทางแก้คือ การสร้างไฟล์ตั้งค่าจากแม่แบบขณะทำงาน: นำแม่แบบที่มีช่องตัวแทนไปไว้ในอิมเมจ แล้วเติมค่าจริงจากตัวแปรสภาพแวดล้อมเมื่อเริ่มทำงาน วิธีนี้ทำให้อิมเมจของคุณไม่เปลี่ยนแปลง และตรวจสอบการตั้งค่าได้
- ไม่มีข้อมูลลับฝังอยู่ในอิมเมจ
- ใช้อาร์ติแฟกต์เดียวกันเลื่อนขึ้นไปยังสภาพแวดล้อมต่าง ๆ
- สร้างการตั้งค่าทันทีก่อนเริ่มกระบวนการ
เครื่องมือสองอย่างที่ทำให้เรื่องนี้ง่ายมากใน Bash คือ envsubst และ heredoc ที่ใส่เครื่องหมายอัญประกาศ
envsubst: เครื่องมือสร้างการตั้งค่าในบรรทัดเดียว
envsubst เป็นโปรแกรมอรรถประโยชน์ GNU ขนาดเล็กที่อ่านข้อมูลจาก stdin แทนที่ช่องตัวแทน $VARIABLE และ ${VARIABLE} ด้วยค่าจากสภาพแวดล้อมปัจจุบัน แล้วเขียนผลลัพธ์ไปยัง stdout
โปรแกรมนี้มาพร้อมแพ็กเกจ gettext และมีให้ใช้ในดิสทริบิวชัน Linux และอิมเมจพื้นฐานของ Docker แทบทุกชนิด
- ทำงานกับรูปแบบข้อความใดก็ได้: NGINX, YAML, TOML, JSON, INI
- ไม่ประเมินไวยากรณ์ของเชลล์ แต่จะแทนที่เฉพาะการอ้างอิงตัวแปร
- ปลอดภัย: จะไม่เรียกใช้คำสั่งใด ๆ ที่อยู่ภายในแม่แบบ
#!/usr/bin/env bash
# Install check (usually already present)
which envsubst || apt-get install -y gettext-base
# Minimal demo
export APP_PORT=8080
export APP_HOST=api.example.com
echo 'server { listen ${APP_PORT}; server_name ${APP_HOST}; }' | envsubst
# Output: server { listen 8080; server_name api.example.com; }การแทนที่ตัวแปรแบบเลือกเฉพาะ
โดยค่าเริ่มต้น envsubst จะแทนที่ $VAR ทุกแห่งที่พบ ซึ่งอาจเขียนทับตัวแปรของ NGINX เช่น $uri หรือ $host ได้ เพราะสิ่งเหล่านี้เป็นคำสั่งของ NGINX จริง ไม่ใช่ตัวแปรสภาพแวดล้อมของคุณ
ส่งรายการตัวแปรที่ระบุอย่างชัดเจนเป็นอาร์กิวเมนต์แรก เพื่อจำกัดการแทนที่ให้เหลือเฉพาะชื่อตัวแปรเหล่านั้น:
envsubst '$VAR1 $VAR2'อาร์กิวเมนต์นี้เป็นสตริงที่อยู่ในเครื่องหมายอัญประกาศเดี่ยว เพื่อไม่ให้เชลล์ขยายค่า โดยภายในจะมีชื่อตัวแปรที่ต้องการแทนที่ คั่นด้วยช่องว่างหรือขึ้นบรรทัดใหม่
#!/usr/bin/env bash
export APP_PORT=8080
export APP_HOST=api.example.com
# NGINX template contains both our vars AND nginx vars ($uri, $host)
TEMPLATE='server {
listen ${APP_PORT};
server_name ${APP_HOST};
location / {
proxy_set_header Host $host;
proxy_pass http://backend$uri;
}
}'
# Only substitute APP_PORT and APP_HOST — leave $host and $uri untouched
echo "$TEMPLATE" | envsubst '${APP_PORT} ${APP_HOST}'
ไฟล์แม่แบบบนดิสก์
สำหรับการตั้งค่าจริง ให้จัดเก็บแม่แบบเป็นไฟล์ เช่น nginx.conf.template ไว้ข้าง Dockerfile เมื่อคอนเทนเนอร์เริ่มทำงาน ให้เรียกใช้ envsubst เพื่อสร้างไฟล์ตั้งค่าขั้นสุดท้ายก่อนเริ่มดีมอน
นี่คือรูปแบบมาตรฐานที่อิมเมจ Docker อย่างเป็นทางการของ NGINX ใช้
#!/usr/bin/env bash
# File: nginx.conf.template
# (In practice this lives on disk; we write it here for demo purposes)
cat > /tmp/nginx.conf.template << 'TMPL'
server {
listen ${NGINX_PORT};
server_name ${SERVER_NAME};
root /var/www/${APP_ENV};
location / {
proxy_pass http://app:${APP_PORT};
}
}
TMPL
export NGINX_PORT=80
export SERVER_NAME=myapp.example.com
export APP_ENV=production
export APP_PORT=3000
# Generate final config
envsubst '${NGINX_PORT} ${SERVER_NAME} ${APP_ENV} ${APP_PORT}' \
< /tmp/nginx.conf.template \
> /tmp/nginx.conf
cat /tmp/nginx.confเฮเรด็อกแบบใส่เครื่องหมายคำพูด: เทมเพลตแบบแทรกในบรรทัดโดยไม่ใช้ไฟล์ชั่วคราว
เฮเรด็อกแบบใส่เครื่องหมายคำพูด (ใช้ << 'EOF' โดยใส่เครื่องหมายคำพูดเดี่ยวรอบตัวคั่น) จะป้องกันไม่ให้เชลล์ขยายตัวแปรหรือดำเนินการแทนที่คำสั่งภายในบล็อก เนื้อหาจะถูกจัดการเป็นสตริงตามตัวอักษร
ด้วยเหตุนี้ เฮเรด็อกจึงเหมาะอย่างยิ่งสำหรับการเขียนเทมเพลตแบบแทรกในบรรทัด แล้วส่งต่อเข้า envsubst โดยตรง โดยไม่จำเป็นต้องใช้ไฟล์ชั่วคราว
<< EOF(ไม่ใส่เครื่องหมายคำพูด) — เชลล์จะขยาย$VARทันที<< 'EOF'(ใส่เครื่องหมายคำพูด) — เนื้อหาเป็นข้อความตามตัวอักษร และเลื่อนการขยายไปให้envsubst
#!/usr/bin/env bash
export DB_HOST=postgres.internal
export DB_PORT=5432
export DB_NAME=myapp_prod
# Quoted heredoc: shell does NOT expand $DB_HOST etc. yet
envsubst << 'EOF'
[database]
host = ${DB_HOST}
port = ${DB_PORT}
dbname = ${DB_NAME}
EOF
# Output uses actual env var values — expansion done by envsubst, not the shellการใช้เฮเรด็อกร่วมกับการเปลี่ยนเส้นทางผลลัพธ์
ส่งเฮเรด็อกแบบใส่เครื่องหมายคำพูดผ่าน envsubst แล้วเปลี่ยนเส้นทางผลลัพธ์ไปยังไฟล์ด้วยนิพจน์เดียว นี่เป็นรูปแบบการเขียนที่สะอาดที่สุดสำหรับสร้างไฟล์การกำหนดค่าในสคริปต์จุดเริ่มต้น
ใช้การแทนที่แบบเลือก ('${VAR1} ${VAR2}') เมื่อรูปแบบเป้าหมาย เช่น Prometheus หรือ NGINX มีไวยากรณ์ $variable ของตนเองที่ต้องป้องกันไว้
#!/usr/bin/env bash
# entrypoint.sh — Docker container entrypoint
set -euo pipefail
export PROM_PORT=${PROM_PORT:-9090}
export SCRAPE_INTERVAL=${SCRAPE_INTERVAL:-15s}
export TARGET_HOST=${TARGET_HOST:-localhost:8080}
envsubst '${PROM_PORT} ${SCRAPE_INTERVAL} ${TARGET_HOST}' << 'EOF' > /etc/prometheus/prometheus.yml
global:
scrape_interval: ${SCRAPE_INTERVAL}
evaluation_interval: ${SCRAPE_INTERVAL}
scrape_configs:
- job_name: 'app'
static_configs:
- targets: ['${TARGET_HOST}']
EOF
echo "[entrypoint] Prometheus config written on port ${PROM_PORT}"
exec prometheus --config.file=/etc/prometheus/prometheus.yml --web.listen-address=":${PROM_PORT}"ค่าเริ่มต้นและการตรวจสอบความถูกต้องก่อนการแทนที่
อย่าสันนิษฐานว่าตัวแปรที่จำเป็นทั้งหมดถูกกำหนดไว้แล้ว ใช้การขยายพารามิเตอร์ของเชลล์เพื่อกำหนดค่าเริ่มต้นหรือหยุดการทำงานพร้อมแจ้งข้อผิดพลาดอย่างชัดเจน:
${VAR:-default}— ใช้defaultหากไม่ได้กำหนดVARไว้หรือเป็นค่าว่าง${VAR:?error message}— ยกเลิกการทำงานพร้อมข้อผิดพลาด หากไม่ได้กำหนดVARไว้หรือเป็นค่าว่าง
กำหนดค่าเหล่านี้ก่อนเรียกใช้ envsubst เพื่อให้เทมเพลตได้รับค่าที่ชัดเจนเสมอ หรือให้สคริปต์หยุดตั้งแต่ต้นพร้อมข้อความที่ช่วยอธิบายปัญหา
#!/usr/bin/env bash
set -euo pipefail
# Required — abort if missing
: "${DATABASE_URL:?DATABASE_URL must be set}"
: "${SECRET_KEY:?SECRET_KEY must be set}"
# Optional with defaults
export APP_PORT=${APP_PORT:-8000}
export LOG_LEVEL=${LOG_LEVEL:-info}
export WORKERS=${WORKERS:-4}
envsubst '${DATABASE_URL} ${SECRET_KEY} ${APP_PORT} ${LOG_LEVEL} ${WORKERS}' \
< /app/config/app.conf.template \
> /app/config/app.conf
echo "[init] Config generated — port=${APP_PORT} workers=${WORKERS} log=${LOG_LEVEL}"การสร้างการกำหนดค่าแบบหลายส่วนด้วยเฮเรด็อกหลายชุด
สำหรับการกำหนดค่าที่ซับซ้อนและประกอบด้วยส่วนเชิงตรรกะ คุณสามารถสร้างแต่ละส่วนแยกกันแล้วนำมาต่อกัน หรือใช้เฮเรด็อกชุดเดียวที่ครอบคลุมทั้งไฟล์ก็ได้ ทั้งสองวิธีใช้ได้เหมือนกัน ให้เลือกตามความอ่านง่าย
เมื่อมีการใส่บางส่วนตามเงื่อนไข เช่น ให้มีบล็อก TLS เฉพาะเมื่อกำหนดเส้นทางใบรับรองไว้ วิธีใช้เฮเรด็อกหลายชุดร่วมกับบล็อก if จะอ่านและดูแลรักษาได้ง่ายกว่า
#!/usr/bin/env bash
set -euo pipefail
export APP_HOST=${APP_HOST:-localhost}
export APP_PORT=${APP_PORT:-8080}
export TLS_CERT=${TLS_CERT:-}
export TLS_KEY=${TLS_KEY:-}
CONFIG_FILE=/tmp/app.conf
# Base section
envsubst '${APP_HOST} ${APP_PORT}' << 'BASE' > "$CONFIG_FILE"
[server]
host = ${APP_HOST}
port = ${APP_PORT}
BASE
# Conditional TLS section — only appended when cert is provided
if [[ -n "$TLS_CERT" && -n "$TLS_KEY" ]]; then
envsubst '${TLS_CERT} ${TLS_KEY}' << 'TLS' >> "$CONFIG_FILE"
[tls]
cert_file = ${TLS_CERT}
key_file = ${TLS_KEY}
TLS
echo "[init] TLS enabled"
else
echo "[init] TLS disabled (no cert/key provided)"
fi
cat "$CONFIG_FILE"รูปแบบจุดเริ่มต้นของ Docker
รูปแบบจุดเริ่มต้นของ Docker ที่แนะนำใช้สคริปต์เชลล์ (docker-entrypoint.sh) เพื่อสร้างการกำหนดค่าเมื่อเริ่มทำงาน จากนั้นจึงส่งต่อการควบคุมให้กระบวนการหลักด้วย exec การใช้ exec จะแทนที่กระบวนการเชลล์ด้วยดีมอน ทำให้สัญญาณ (SIGTERM, SIGINT) ไปถึงดีมอนโดยตรง ซึ่งสำคัญอย่างยิ่งต่อการปิดระบบอย่างเรียบร้อย
ไฟล์เทมเพลตจะถูกเพิ่มเข้าไปในอิมเมจขณะสร้าง ส่วนค่าต่าง ๆ จะถูกส่งเข้าไปขณะเรียกใช้จาก docker run -e หรือ env: / envFrom: ของ Kubernetes
#!/usr/bin/env bash
# docker-entrypoint.sh
set -euo pipefail
# Validate required env vars
for var in DATABASE_URL REDIS_URL SECRET_KEY; do
: "${!var:?$var is required}"
done
export APP_PORT=${APP_PORT:-8000}
export WORKERS=${WORKERS:-$(nproc)}
echo "[entrypoint] Generating configuration..."
envsubst '${DATABASE_URL} ${REDIS_URL} ${SECRET_KEY} ${APP_PORT} ${WORKERS}' \
< /app/config/settings.toml.template \
> /app/config/settings.toml
echo "[entrypoint] Starting server on port ${APP_PORT} with ${WORKERS} workers"
exec gunicorn app:application \
--bind "0.0.0.0:${APP_PORT}" \
--workers "${WORKERS}"รูปแบบ Kubernetes ConfigMap + envsubst
ใน Kubernetes ตัวแปรสภาพแวดล้อมจะถูกส่งเข้าไปผ่าน env: หรือ envFrom: ในข้อกำหนดของ Pod จุดเริ่มต้นของคอนเทนเนอร์จะเรียกใช้ envsubst เพื่อสร้างการกำหนดค่าให้เป็นรูปธรรมก่อนกระบวนการจะเริ่มทำงาน จึงไม่จำเป็นต้องมี ConfigMap แยกสำหรับแต่ละสภาพแวดล้อม
วิธีนี้ทำให้ค่าที่แตกต่างกันตามสภาพแวดล้อมอยู่ใน Kubernetes Secrets และ ConfigMaps (สำหรับข้อมูลที่ไม่ละเอียดอ่อน) ขณะที่เทมเพลตการกำหนดค่าอยู่ในอิมเมจเดียวกัน อิมเมจเดียวใช้ได้กับหลายสภาพแวดล้อม
- ขณะสร้าง:
COPY nginx.conf.template /etc/nginx/templates/ - ขณะเรียกใช้: จุดเริ่มต้นเรียกใช้
envsubstและเขียนลงใน/etc/nginx/nginx.conf - Kubernetes ส่งค่าเข้าไป:
APP_PORT,BACKEND_HOSTจากข้อมูลลับหรือ ConfigMap
การแก้ไขข้อบกพร่องของ envsubst: ค้นหาตัวแปรที่หายไปหรือยังไม่ได้แทนที่
เมื่อการกำหนดค่าที่สร้างขึ้นมี ${VAR} ตามตัวอักษรแทนที่จะเป็นค่า แสดงว่าตัวแปรนั้นไม่ได้ส่งออกหรือไม่ได้รวมไว้ในรายการแทนที่ ใช้เทคนิคต่อไปนี้เพื่อแก้ไขข้อบกพร่อง:
printenv | sort— แสดงรายการตัวแปรที่ส่งออกทั้งหมด- เปรียบเทียบตัวยึดตำแหน่งในเทมเพลตกับตัวแปรที่ส่งออกโดยใช้
grep - เรียกใช้
envsubstแล้วใช้ grep ค้นหารูปแบบ${ที่ยังเหลืออยู่ในผลลัพธ์ - ใช้
set -uในสคริปต์ที่เรียกใช้ เพื่อให้การอ้างอิงตัวแปรที่ไม่ได้กำหนดในโค้ด Bash ยกเลิกการทำงานทันที
#!/usr/bin/env bash
set -euo pipefail
TEMPLATE=/tmp/app.conf.template
OUTPUT=/tmp/app.conf
# Write a demo template
cat > "$TEMPLATE" << 'EOF'
host=${DB_HOST}
port=${DB_PORT}
name=${DB_NAME}
EOF
export DB_HOST=db.internal
export DB_PORT=5432
# DB_NAME intentionally left unset
envsubst < "$TEMPLATE" > "$OUTPUT"
# Detect unresolved placeholders
if grep -qE '\$\{[A-Z_]+\}' "$OUTPUT"; then
echo "ERROR: unresolved placeholders found:"
grep -oE '\$\{[A-Z_]+\}' "$OUTPUT" | sort -u
exit 1
fi
echo "Config OK:"
cat "$OUTPUT"ตรวจสอบความรู้: การแทนที่แบบเลือกด้วย envsubst
พิจารณาเทมเพลตการกำหนดค่า NGINX ที่มีทั้งตัวแปรของแอปพลิเคชัน ${APP_PORT} และตัวแปรดั้งเดิมของ NGINX $uri คุณเรียกใช้คำสั่งต่อไปนี้:
envsubst < nginx.conf.template > nginx.conf
ผลลัพธ์จะเป็นอย่างไร
สรุปบทเรียน: การสร้างเทมเพลตการกำหนดค่าด้วย envsubst และเฮเรด็อก
ขณะนี้คุณมีชุดเครื่องมือระดับพร้อมใช้งานจริงสำหรับการสร้างการกำหนดค่าขณะรันด้วย Bash:
- envsubst แทนที่ตัวยึดตำแหน่ง
${VAR}ในไฟล์ข้อความใด ๆ โดยใช้สภาพแวดล้อมปัจจุบัน ไม่ต้องเขียนสคริปต์และไม่ต้องจัดการการหลีกอักขระแบบพิเศษ - การแทนที่แบบเลือก (
envsubst '${VAR1} ${VAR2}') ป้องกันไม่ให้ตัวแปรดั้งเดิมใน NGINX, Prometheus และเครื่องมือที่คล้ายกันถูกแทนที่โดยไม่ตั้งใจ - เฮเรด็อกแบบใส่เครื่องหมายคำพูด (
<< 'EOF') เลื่อนการขยายของเชลล์ออกไป ทำให้เนื้อหาเทมเพลตไปถึงenvsubstโดยไม่เปลี่ยนแปลง และไม่ต้องใช้ไฟล์ชั่วคราว - ตรวจสอบความถูกต้องก่อนแทนที่: ใช้
${VAR:?message}เพื่อยกเลิกการทำงานเมื่อไม่มีตัวแปรที่จำเป็น และใช้${VAR:-default}สำหรับตัวแปรที่ไม่บังคับ - รูปแบบจุดเริ่มต้นของ Docker: สร้างการกำหนดค่าเมื่อคอนเทนเนอร์เริ่มทำงาน จากนั้นใช้
execกับดีมอนเพื่อให้จัดการสัญญาณได้อย่างถูกต้อง - แก้ไขตัวยึดตำแหน่งที่ยังไม่ได้แทนที่ โดยใช้ grep ค้นหารูปแบบ
${ที่ยังเหลือในผลลัพธ์ก่อนกระบวนการจะเริ่มทำงาน
รูปแบบเหล่านี้ช่วยให้อิมเมจคอนเทนเนอร์ของคุณไม่เปลี่ยนแปลง ข้อมูลลับไม่อยู่ในระบบควบคุมซอร์สโค้ด และการกำหนดค่ามีความสอดคล้องกันในทุกสภาพแวดล้อม
คำถามที่พบบ่อย
บทเรียน “การสร้างแม่แบบการกำหนดค่าด้วย envsubst และ heredoc” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การสร้างแม่แบบการกำหนดค่าด้วย envsubst และ heredoc” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DevOps Bootcamp ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DevOps Bootcamp มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การสร้างแม่แบบการกำหนดค่าด้วย envsubst และ heredoc”
สร้างการกำหนดค่าขณะทำงานจากตัวแปรสภาพแวดล้อมด้วย envsubst และ heredoc ที่ใส่เครื่องหมายคำพูด คุณปฏิบัติ DevOps Bootcamp ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DevOps Bootcamp หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน DevOps Bootcamp บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “การสร้างแม่แบบการกำหนดค่าด้วย envsubst และ heredoc” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน DevOps Bootcamp นี้ได้ไหม
ได้ บทเรียน DevOps Bootcamp ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การเขียน Dockerfile และจุดเริ่มต้น Shell แบบกระชับ
- การสร้างแม่แบบการกำหนดค่าด้วย envsubst และ heredoc
- การเขียนสคริปต์จัดการทรัพยากรคลาวด์ด้วย CLI และ jq
- โพรบสุขภาพ ด่านความพร้อม และลูปรอ