Dockerfile之gosu浅析
Docker容器中运行的进程,如果以root身份运行话会有安全隐患,该进程拥有容器内的全部权限,更可怕的是如果有数据卷映射到宿主机,那么通过该容器就能操作宿主机的文件夹了,一旦该容器的进程有漏洞被外部利用后果是很严重的,因此,容器内使用非root账号运行进程才是安全的方式,这也是我们在制作镜像时要注意的地方
·
权限分析
- Docker容器运行的时候,如果没有专门指定user, 默认以root用户运行。我们镜像里没有指定user,容器里的执行用户的id是0,输出文件的权限也是0
# 以ROOT用户执行docker查看权限情况如下,发现容器里面文件宿主都是ROOT用户
[root@boy dockerfile]# docker run -it --rm centos:7 /bin/bash -c "echo 1 > /root/text && ls -al /root"
total 28
dr-xr-x---. 1 root root 18 Apr 25 05:44 .
drwxr-xr-x 1 root root 18 Apr 25 05:44 ..
-rw-r--r--. 1 root root 18 Dec 29 2013 .bash_logout
-rw-r--r--. 1 root root 176 Dec 29 2013 .bash_profile
-rw-r--r--. 1 root root 176 Dec 29 2013 .bashrc
-rw-r--r--. 1 root root 100 Dec 29 2013 .cshrc
-rw-r--r--. 1 root root 129 Dec 29 2013 .tcshrc
-rw-------. 1 root root 3416 Nov 13 2020 anaconda-ks.cfg
-rw-r--r-- 1 root root 2 Apr 25 05:44 text
# 以ROOT用户来执行进程,可见也是ROOT身份
[root@boy dockerfile]# docker run -it --rm centos:7 /bin/bash -c "ps -aux"
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 51728 1544 pts/0 Rs+ 06:27 0:00 ps -aux
gosu
- Docker容器中运行的进程,如果以root身份运行话会有安全隐患,该进程拥有容器内的全部权限,更可怕的是如果有数据卷映射到宿主机,那么通过该容器就能操作宿主机的文件夹了,一旦该容器的进程有漏洞被外部利用后果是很严重的,因此,容器内使用非root账号运行进程才是安全的方式,这也是我们在制作镜像时要注意的地方
su
和sudo
具有非常奇怪且经常令人讨厌的TTY和信号转发行为的问题。su
和sudo
的设置和使用也有些复杂(特别是在sudo
的情况下),虽然它们有很大的表达力,但是如果您所需要的只是“以特定用户身份运行特定应用程序”,那么它们将不再那么适合- 处理完用户/组后,我们将切换到指定用户,然后执行指定的进程,gosu本身不再驻留或完全不在进程生命周期中。这避免了信号传递和TTY的所有问题
- 不要在容器中使用
sudo
命令,因为sudo的执行机制问题,如下所示,我们在启动容器时执行了sudo ps -ef
命令,发现我们命名只执行了一条命令,但是竟然会有2个进程,请注意PID
,真正执行ps -ef
的命令的PID是7
,而不是1
,这回导致当前进程无法接受Unix的SIGNAL
[root@jiayu ~]# docker run -it --rm ubuntu su -c 'exec ps -aux'
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4484 1612 pts/0 Ss+ 14:31 0:00 su -c exec ps -
root 7 0.0 0.0 5888 1412 pts/0 R+ 14:31 0:00 ps -aux
# 下面二个实例,不一定带sudo命令
[root@jiayu ~]# docker run -it --rm ubuntu:trusty sudo ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 3.0 0.0 46020 3144 ? Ss+ 02:22 0:00 sudo ps aux
root 7 0.0 0.0 15576 2172 ? R+ 02:22 0:00 ps aux
# 可见使用gosu后,切换身份后,且只有一个进程
[root@jiayu ~]# docker run -it --rm -v $PWD/gosu-amd64:/usr/local/bin/gosu:ro ubuntu:trusty gosu root ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 7140 768 ? Rs+ 02:22 0:00 ps aux
redis-dockerfile和entrypoint分析
:链接https://github.com/docker-library/redis/blob/6845f6d4940f94c50a9f1bf16e07058d0fe4bc4f/5.0/Dockerfile
# 查看dockerfile
# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r redis && useradd -r -g redis redis
...............................................
RUN mkdir /data && chown redis:redis /data
VOLUME /data
WORKDIR /data
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD ["redis-server"]
# 查看entrypoint.sh
#!/bin/sh
set -e
# first arg is `-f` or `--some-option`
# or first arg is `something.conf`
if [ "${1#-}" != "$1" ] || [ "${1%.conf}" != "$1" ]; then
set -- redis-server "$@"
fi
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
find . \! -user redis -exec chown redis '{}' +
exec gosu redis "$0" "$@"
fi
exec "$@"
注意上面的代码,我们来分析一下:
- 假设启动容器的命令是docker run -it redis redis-server /etc/redis.conf
- 容器启动后会执行docker-entrypoint.sh脚本,此时的账号是root,此时对于容器来说接收到命令为:
docker-entrypoint.sh redis-server /etc/redis.conf
- exec gosu redis $0 $@
# 这条命令当于以redis身份执行如下命令
docker-entrypoint.sh redis-server /etc/redis.conf
# exec
替换当前的shell,且不会生成新的进程保证了gosu redis “$0” "@"对应的进程ID为1
此时运行命令为:docker-entrypoint.sh redis-server /etc/redis.conf
- 当切换成redis用户后会执行如下命令
# exec $@
redis-server /etc/redis.conf
gosu实例
[root@boy dockerfile]# cat dockerfile
FROM debian:sid-slim
RUN groupadd -r test && useradd -r -g test test
COPY docker-entrypoint.sh /usr/bin/
RUN apt-get update && apt-get install -y --no-install-recommends procps gosu && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["ps -aux && sleep 36000"]
[root@boy dockerfile]# cat docker-entrypoint.sh
#!/bin/bash
if [ "$(id -u)" == "0" ]; then
exec gosu test "$0" "$@"
fi
exec $@
# 构建镜像
[root@boy dockerfile]# docker build -t gosu:1.0 .
# 启动容器,可以发现目前容器内的进程都是以test身份运行了,且只有一个进程,不存在sudo,su导致生成二个进程
[root@boy dockerfile]# docker run -it --rm gosu:1.0 ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
test 1 4.0 0.0 6960 1572 pts/0 Rs+ 07:34 0:00 ps -aux
总结
- gosu启动命令时只有一个进程,所以docker容器启动时使用gosu,那么该进程可以做到PID等于1
- sudo启动命令时先创建sudo进程,然后该进程作为父进程去创建子进程,1号PID被sudo进程占据
综上所述,在docker的entrypoint中有如下建议:
- 创建group和普通账号,不要使用root账号启动进程
- 如果普通账号权限不够用,建议使用gosu来提升权限,而不是sudo
- entrypoint.sh脚本在执行的时候也是个进程,启动业务进程的时候,在命令前面加上exec,这样新的进程就会取代entrypoint.sh的进程,得到1号PID
更多推荐
已为社区贡献3条内容
所有评论(0)