envsubst 指令用法,以及如何跳脫 (escape)



前言

  • 有時在渲染 Kubernetes 的 yaml 檔時,不一定每個環境都有裝 helm,或其他的 templating 工具 …
  • 如果又不想在當前環境裝額外的工具,只想用簡單粗暴的內建指令做到渲染變數的效果,這時就要善用 envsubst 這個指令!



說明

envsubst 是一個常見指令,可以用來把檔案中預留的變數,用「環境變數」或「指定變數」的「值」取代,然後將結果輸出至 stdout。


安裝

如果發現 command not found

1
2
3
envsubst

bash: envsubst: command not found

就代表環境沒有這個指令,需要安裝 gettext 這個套件。

不同系統的安裝方式不同 [1]:

系統 安裝指令
OS X brew install gettext
Debian apt-get install gettext-base
Ubuntu apt-get install gettext-base
Alpine apk add gettext
Arch Linux pacman -S gettext
CentOS yum install gettext
Fedora dnf install gettext

範例

指定變數

假設我有一個檔案,例如 demo.svc.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: ${MY_SERVICE_NAME}
spec:
  ports:
  - name: http
    port: ${MY_PORT_NUMBER}
    protocol: TCP
    targetPort: ${MY_PORT_NUMBER}
  selector:
    app: ${MY_SERVICE_NAME}
  type: ClusterIP

我們可以把這個檔案中的 ${MY_SERVICE_NAME}${MY_PORT_NUMBER} 這兩個變數,用指定變數取代

1
<demo.svc.yaml MY_SERVICE_NAME="demo-service" MY_PORT_NUMBER=80 envsubst

就會順利得到渲染後的結果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: demo-service
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: demo-service
  type: ClusterIP

Note

  1. 當然你可以用 cat 指令:
1
cat demo.yaml | MY_SERVICE_NAME="demo-service" MY_PORT_NUMBER=80 envsubst

但是 ShellCheck - SC2002 會建議你不要這樣做


  1. 為了排版好閱讀起見,我會把指令寫成多行:
1
2
3
4
<demo.svc.yaml \
  MY_SERVICE_NAME="demo-service" \
  MY_PORT_NUMBER=80 \
  envsubst

  1. 如果你習慣把 stdin 放在後面,也可以:
1
2
3
4
MY_SERVICE_NAME="demo-service" \
MY_PORT_NUMBER=80 \
envsubst \
<demo.svc.yaml 



環境變數

如果有些變數非常常用到、或覺得每次都要指定變數太麻煩的話,可以直接 export 成環境變數:

1
2
export MY_SERVICE_NAME="demo-service"
export MY_PORT_NUMBER=80

然後就可以直接用 envsubst 渲染:

1
<demo.svc.yaml envsubst

一樣可以得到渲染後的結果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: demo-service
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: demo-service
  type: ClusterIP



保留錢字號 $

有的時候,我就是單純想要保留錢字號 $,讓渲染之後的結果可以再給其他工具用、或是某些變數我不想被 envsubst 渲染,例如這種情況:

  • Kubernetes deployment 的 arg,我就是要讓指定的 container command 取得 env 裡面的變數 NAMESPACE、同時要依據 MY_DOMAIN 的值渲染;但要讓 NAMESAPCE 維持原狀,不能被 envsubst 渲染:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 原本的寫法,但想要嘗試保留 "${NAMESPACE}"

- args:
  - /bin/k3s server --disable-agent --tls-san=${NAMESPACE}.${MY_DOMAIN}   
  command:
  - sh
  - -c
  env:
  - name: NAMESPACE
    valueFrom:
      fieldRef:
        apiVersion: v1
        fieldPath: metadata.namespace

(錯誤示範) 該怎麼處理,使用跳脫字元?像是 \${NAMESPACE}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 省略上面的部分

- args:
  - /bin/k3s server --disable-agent --tls-san=\${NAMESPACE}.${MY_DOMAIN}   
  command:
  - sh
  - -c
  env:
  - name: NAMESPACE
    valueFrom:
      fieldRef:
        apiVersion: v1
        fieldPath: metadata.namespace

但是

1
<test.pod.yaml MY_DOMAIN="example.com" envsubst

渲染出來卻會是

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
- args:
  - /bin/k3s server --disable-agent --tls-san=\.example.com
  command:
  - sh
  - -c
  env:
  - name: NAMESPACE
    valueFrom:
      fieldRef:
        apiVersion: v1
        fieldPath: metadata.namespace

因為 envsubst 會先找到 ${NAMESPACE},但因為我們沒有指定 NAMESPACE 的值,

所以就會先被渲染成空字串,然後才剩下原有的跳脫符號 \


用空變數輔助

目前看到 [2] 我認為最好的方式,就是用兩個 $ 符號配上一個「空變數」(根本沒有這個變數) 來完成渲染,像是這樣:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 省略上面的部分

- args:
  - /bin/k3s server --disable-agent --tls-san=$${MY_NULL_VAR}{NAMESPACE}.${MY_DOMAIN}   
  command:
  - sh
  - -c
  env:
  - name: NAMESPACE
    valueFrom:
      fieldRef:
        apiVersion: v1
        fieldPath: metadata.namespace
  • 其中,MY_NULL_VAR 根本沒有宣告過

這是因為 envsubst 會先渲染 ${MY_NULL_VAR} – 變成空值,

然後剩下來前面的 $ 符號、以及後面的大括號 {NAMESPACE},被保留下來剛好放在一起。


這樣,

1
<test.pod.yaml MY_DOMAIN="example.com" envsubst

就能產生理想的結果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
- args:
  - /bin/k3s server --disable-agent --tls-san=${NAMESPACE}.example.com
  command:
  - sh
  - -c
  env:
  - name: NAMESPACE
    valueFrom:
      fieldRef:
        apiVersion: v1
        fieldPath: metadata.namespace



REF

  1. https://www.thegeekdiary.com/envsubst-command-not-found/
  2. https://stackoverflow.com/questions/24963705/is-there-an-escape-character-for-envsubst/65652760#65652760

主題 StackJimmy 設計