启动时,程序读取配置有几种方法:
- 把配置文件作为参数传给程序
- 程序从配置服务器读取配置参数
- 通过环境变量载入参数
第三种方法,由于简单方便,兼容性高,无需依赖其他基础设施,常作为中小型程序首选方法。本文分享几个使用环境变量的经验。
环境变量文件
通过设置环境变量,把配置项传递给程序的最简单用法如下:
1import os
2
3# 读取环境变量A,如果读不到就打印0
4print(os.environ.get("A", "0"))
测试一下
1$ python your_program.py
20
3
4$ A=1 python your_program.py
51
随着程序复杂度的升高,需要越来越多的环境变量配置程序行为。如果还是像上面这种用法,每次运行起来会格外的头疼:
1$ A=1 B=2 C=3 D=4 E=5 AA=1 BA=2 CB=3 DC=4 EE=5 python your_program.py
因此,可以把所有的环境变量放入一个文件中.env(此处使用.env,也可以使用其它文件名,例如:.dot) 。文件内容如下:
1export A=1
2export B=2
3export C=3
4export D=4
5export E=5
6export AA=1
7export BA=2
8export CB=3
export A=1
会增加一个名字为A的环境变量同时赋值为1。有了这个文件,就可以这样操作了:
1$ source .env && export && python your_program.py
2declare -x A="1"
3declare -x AA="1"
4declare -x B="2"
5declare -x BA="2"
6declare -x C="3"
7declare -x CB="3"
8declare -x CLASSPATH=".:/usr/local/jdk/lib:/usr/local/jdk/jre/lib:/usr/local/jdk/lib/dt.jar:/usr/local/jdk/lib/tools.jar"
9declare -x D="4"
10declare -x E="5"
11declare -x HOME="/home/hao"
12...
13declare -x WT_SESSION="63c4896d-7f36-473b-bc42-51ed14350db4"
14declare -x XDG_DATA_DIRS="/usr/local/share:/usr/share:/var/lib/snapd/desktop"
151
通过export命令就能看到所有.env文件中的环境变量都已经声明复制,并在程序中被正确引用。不过严格的说, 我们写的.env
不是真的.env
环境变量文件,而是shell
脚本。真正的环境变量文件应该长成这个样子:
1A=1
2B=2
3C=3
4D=4
5E=5
6AA=1
7BA=2
8CB=3
如果此时再次执行上面的那条命令(请打开一个新terminal做这个测试,否则之前的环境变量依旧在Session中):
1$ source .env && export && python your_program.py
2declare -x CLASSPATH=".:/usr/local/jdk/lib:/usr/local/jdk/jre/lib:/usr/local/jdk/lib/dt.jar:/usr/local/jdk/lib/tools.jar"
3declare -x HOME="/home/hao"
4declare -x HOSTTYPE="x86_64"
5declare -x JAVA_HOME="/usr/local/jdk"
6declare -x JRE_HOME="/usr/local/jdk/jre"
7...
8declare -x WT_SESSION="ae8443cd-142b-4c66-96cb-15467fed0b4b"
9declare -x XDG_DATA_DIRS="/usr/local/share:/usr/share:/var/lib/snapd/desktop"
100
因为source是执行脚本,而.env中并没有脚本命令,所以环境变量也就没有被声明和赋值。如果你希望用docker的–env-file或者docker-compose的 env_file指令加载环境变量文件,那就必须使用不含export命令的 真 环境变量文件。可这样一来,你就无法在terminal开发调试时,用source加载环境变量文件。当然可以写一段复杂的遍历语句来依次export环境变量。但更直接的方案是在程序中使用加载环境变量文件的库,读入环境变量文件。例如:
1from dotenv import load_dotenv
2import os
3
4load_dotenv()
5
6print(os.getenv("A"))
关于引号
在Linux环境下,如下两种环境变量是一样的:
1$ cat .env
2A=Hello
3B="Hello"
4
5$ echo $A
6Hello
7
8$ echo $B
9Hello
然而docker的–env-file会将有双引号和没有双引号视为不同的内容。可以阅读strange interpretation/parsing of .env file #3702
1$ docker run --env-file=.env alpine sh -c 'echo $A'
2Hello
3
4$ docker run --env-file=.env alpine sh -c 'echo $B'
5"Hello"
这个变化会造成什么影响呢?举个例子,假设你有个python文件要调用API,所依赖的库可能会去拿你传入URL的hostname:
1import os
2from urllib.parse import urlparse
3from dotenv import load_dotenv
4
5load_dotenv()
6
7result = urlparse(os.getenv("U"))
8print(result.hostname)
1$ cat .env
2U="https://www.baidu.com/a.mp4"
3
4$ python url.py
5www.baidu.com
6
7$ docker run --env-file=.env -v $PWD:/opt python:3.8-alpine sh -c "pip install python-dotenv && python /opt/url.py"
8None
如果在docker中调用API的程序去获取result.hostname,期待拿到www.baidu.com(在非docker环境下,也的确是这个值)。但实际得到None,因为传入urlparse的值是’“https://www.baidu.com/a.mp4"',urlparse无法得到正确结果。因此,在环境变量中除非有空格,否则不要使用引号(双引号和单引号)。
可以阅读这篇文章再次加深以下印象Don’t Quote Environment Variables in Docker。