容器的环境变量问题

今天mei问容器里面怎么拿环境变量,让我想起了之前帮广告部门查的一个ssh后环境变量丢失的问题,那就顺道总结下。

首先我们的目标是:

容器里的环境变量,对于跑在容器内的程序按理,应该直接就可以获得,即直接从各种语言的读取环境变量的方式就可以了。
docker帮我们实现了:启动的时候entrypoint拿到的是正确的环境变量,并且docker exec进入的时候环境变量是正确的,可获取到的。
但是用户有2种方式可能导致环境变量丢失:
可能用su - username的方式切换了账户导致环境变量丢失,注意这里虽然su - 的语义就是丢弃环境变量,但是我们认为k8s pod上设置的环境变量是全局可用的,我们也保证这样切换后设置的环境变量可用,当然你也可以不这么干
使用ssh的方式进入,这里又有两种 ssh进去进入login bash,或者就是ssh $ip xxxcommand 执行一个命令,进入一个nointeractive nologinbash的方式
这里2种其实都和bash对于环境变量文件的加载问题有关,所以看下bash这里的逻辑

背景知识:

bash有2种大的类型或者区别(这里不知道怎么描述)

  1. login:就是你登陆进去后获得的(第一个!)bash
  2. interactive:就是有终端提示符,可以敲命令那种。

相应的也有取非的情况,两两排列组合,bash一共有4种模式

  1. login+interactive 就是你ssh 登陆进去后那个
  2. login+nointeractive 不常见,主要是bash -l script.sh 有时候我们在某些系统的运行脚本设置里面会这么写(比如监控的自定义命令监控)
  3. interactive+nologin 就是你开的各种终端模拟器开的那些 比如gnome-terminal那些标签页
  4. nointeractive+nologin 这种其实容易被遗忘,但这就是上次查问题所在 ssh $ip xxxxcommand 的情况

与此同时,系统有一堆文件,bash会去读,具体就不一个个说明了,总结如下:
bash的每种模式会读取其所在列的内容,首先执行A,然后是B,C。而B1,B2和B3表示只会执行第一个存在的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

+----------------+--------+-----------+---------------+
| | login |interactive|non-interactive|
| | |non-login |non-login |
+----------------+--------+-----------+---------------+
|/etc/profile | A | | |
+----------------+--------+-----------+---------------+
|/etc/bash.bashrc| | A | |
+----------------+--------+-----------+---------------+
|~/.bashrc | | B | |
+----------------+--------+-----------+---------------+
|~/.bash_profile | B1 | | |
+----------------+--------+-----------+---------------+
|~/.bash_login | B2 | | |
+----------------+--------+-----------+---------------+
|~/.profile | B3 | | |
+----------------+--------+-----------+---------------+
|BASH_ENV | | | A |
+----------------+--------+-----------+---------------+

另外通常情况下~/.bash_profile 里面都会加载~/.bashrc ,~/.bashrc里面又会加载/etc/bashrc 所以你平常加的时候,看起来到处加了都生效,但其实是上面的顺序。
除此之外login bash中B1 B2 B3 是有一个就不执行另外一个,所以通常不建议使用.bash_login 然后其实.profile 的目的是兼容非bash的,比如csh之类的,优雅点,可以.bash_profile里面写bash相关的,然后在.bash_profile里面自己主动加载下.profile(考虑到这个时候bash看到有.bash_profile是不会去加载.profile的)

回到正题:

对于切换账户的情况,我们不可能一个个去改每个账户下的.bash_profile 所以我们统一在/etc/profile 里面处理。
处理方式2种:

  1. 如这里所说 cat /proc/1/environ | tr ‘\0’ ‘\n’ 然后再逐行加载的方式(我不推荐,我从不认为在有系统命令的情况下,自己去解析一个文件是个好方案)
  2. 系统启动的时候执行的脚本中导出下环境变量到另外一个文件中 然后/etc/profile写上去加载这个文件

对于ssh 直接执行命令的情况:
可以看到表中只认BASH_ENV指定的文件,意思是你ssh前先export BASH_ENV=/etc/profile 然后执行ssh $ip xxcommand bash会去加载这个文件,但是又有2个问题,一个是用户操作麻烦,另外一个是如果用户脚本的shebang没写bash又不认这个玩意。。。所以我们用pam_env.so的方案来解决。具体看https://linux.die.net/man/8/pam_env

1
2
#需要修改 /etc/pam.d/password-auth 成
auth required pam_env.so envfile=/etc/environment

这个时候会发现有个系统提供好的路径/etc/environment 来放环境变量,那么对于切账户的问题中的环境变量也刚好用这个文件,多么的优雅。

再留个问题,对于crontab 又是个什么情况呢? 欢迎评论