本文主要接收怎么通过
Dockerfile
的EntryPoint
做到,容器启动后的登录用户不是root
,而是用户通过docker run
参数指定的用户名和组名
这篇文章源自我上周工作中遇到的一个问题,我在k8s
中通过docker
启动jupyterlab
服务,但是发现每次docker
每次启动容器后的用户都默认是root
,这不利于权限控制和用户管理。
所以,我就想:是否控制用户启动容器后的登录用户呢? 周末在家好好学习了下怎么通过Dockerfile
对镜像进行定制,并通过EntryPoint
指令完成了上诉的功能,现分享给大家,希望对大家有帮助。
如果你对docker
相关技术比较熟悉的化,你应该首先会想到docker run --users
或者在Dockerfile
中通过USER
指令来完成,但这两种方式都有个缺陷:就是我必须提前创建好用户和用户组。
如果你docker
的使用还有上层应用的化,那倒还好,你可以提前创建好用户和用户组,或者容器启动后手动切换到其他用户。
但是,如果我仅仅想通过docker run
的方式启动,启动后便自动完成创建和切换的工作呢?例如我就是通过docker
启动notebook
(一种基于web
的交互式编程服务),我想用户在notebook terminal
中登录的用户是自己而不都是root
。
了解docker
的同学应该知道,docker
是定制镜像,一般是先编写Dockerfile
,然后build
成镜像,最后基于这个镜像通过docker run
启动容器。
所以,现在问题关键就是:怎么通过docker run
后面跟一些参数传入用户名和组名,然后容器启动的时候,能够得到指定的用户名和组名,完成用户和组的创建,并切换到此用户?
Dockerfile
是通过一系列的指令,从某个基础镜像出发,加上自己定制的功能,完成定制镜像的工作。下面的代码就是一个简单的例子,它基于nginx
镜像进行扩展,添加一个html
文件,然后启动nginx
服务。
FROM nginx
RUN echo 'Hello, Docker!
' > /usr/share/nginx/html/index.html
CMD ["nginx", "-g", "daemon off;"]
完整的Dockerfile
指令可以参考DockerFile指令详解,这里我们介绍本文相关的CMD
和ENTRYPOINT
两个指令。
CMD
是容器启动命令,docker
技术中的容器和虚拟机不一样,它是个进程,所以需要指定进程运行的程序和相关参数,CMD
命令有两种写法,shell
写法和exec
写法。
# shell写法,即CMD后面直接跟shell命令。实际会被包装成sh -c的方式完成执行
CMD echo $HOME
# exec写法,指定执行文件和参数,推荐方式
# 上诉命令等价于如下写法
CMD [ "sh", "-c", "echo $HOME" ]
上诉的过程,也能通过docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
的方式显式的写启动命令,这会覆盖DockerFile
中的CMD
指令,上诉工作等价写法:docker run yourImage echo "hello world"
ENTRYPOINT
的格式和RUN
指令格式一样,分为exec
格式和shell
格式,ENTRYPOINT
的目的和CMD
一样,都是在指定容器启动程序及参数。两者可以单独使用,但是两者一起出现时,微妙的事情发生了,CMD
将会变成ENTRYPOINT
的参数,启动命令将会变成:
,详细的组合方式可以参考ENTRYPOINT和CMD不通组合表现出的效果。例如下面启动命名是访问百度网站
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "https://www.baidu.com" ]
如果我想仅仅得到网址的header
信息,即加上-i
参数,于是我如下方式启动容器,发现报错,说找不到-i
这个执行命令,那是因为镜像后面显式定义的CMD
把dockerfile
中定义的CMD
给覆盖了,而-i
不是shell
命令。
$ docker run myImage -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
前面讲过了,ENTRYPOINT
和CMD
一起使用的时候,CMD
会变成ENTRYPOINT
的参数,所以我们可以通过这种方式,让我们的参数控制容器的启动命令。于是,上述的Dockerfile
变为如下方式:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "https://ip.cn" ]
# build后,我们再次通过 docker run myImage -i就ok了
# 因为 -i 作为CMD会作为参数拼接到后面,即 curl -s https://ip.cn -i
经过上面的命令介绍后,现在回到正题,我们如何通过ENTRYPOINT
完成用户创建和切换。ENTRYPOINT
如果运行的命令比较复杂,是可以直接写shell
脚本的,所以,Dockerfile
如下:
FROM hub.c.163.com/library/nginx
# 添加到环境变量中,entrypoint.sh可以直接运行
COPY entrypoint.sh /usr/bin
ENTRYPOINT ["entrypoint.sh"]
其中的entrypoint.sh
脚本为:
#!/bin/bash
if [ "$#" -ne "2" ]; then
echo "usage example: docker run -d image:version username groupname"
exit 2
fi
echo "username:"$2
echo "groupname:"$1
# 添加用户和组,并切换
groupadd $2
useradd -g $2 $1
su $1
# 让nginx前台方式运行,为了当前进程不退出
# 不然shell脚本执行完,主进程完成,容器就会退出了
exec nginx -g 'daemon off;'
运行结果如下,说明下,为了直观的看到效果,我通过交互式的方式启动docker
,第一个参数需要传入/bin/bash
参数,所以我临时将上面shell
脚本的参数校验先去了,然后后面获取参数的index
都加了1
,正常使用不用管。
[root@centos-7 zhanhaitao]# docker run -it nginx:v4 /bin/bash zhanht zhanht
username:zhanht
groupname:zhanht
$ id
uid=1000(zhanht) gid=1000(zhanht) groups=1000(zhanht)