可见redis官方镜像使用groupadd和useradd创建了名为redis的组合账号,接下来就是用redis账号来启动服务了,理论上应该是以下套路:
-
用USER redis将账号切换到redis;
-
在docker-entrypoint.sh执行的时候已经是redis身份了,如果遇到权限问题,例如一些文件只有root账号有读、写、执行权限,用sudo xxx命令来执行即可;
但事实并非如此!
在Dockerfile脚本中未发现USER redis命令,这意味着执行docker-entrypoint.sh文件的身份是root;
其次,在docker-entrypoint.sh中没有发现su - redis命令,也没有sudo命令;
这是怎么回事呢?难道容器内的redis服务是用root账号启动的?
确认redis服务的启动账号
还是自己动手来证实一下吧,我的环境信息如下:
操作系统:CentOS Linux release 7.6.1810
Docker: 1.13.1
操作步骤如下:
- 启动一个redis容器:
docker run --name myredis -idt redis
- 进入容器:
docker exec -it myredis /bin/bash
- 在容器内,先更新apt:
apt-get update
- 安装ps命令:
apt-get install procps
- 执行命令ps -ef查看redis服务,结果如下:
root@122c2df16bbb:/data# ps -ef
UID PID PPID C STIME TTY TIME CMD
redis 1 0 0 09:22 ? 00:00:01 redis-server *:6379
root 287 0 0 09:36 ? 00:00:00 /bin/bash
root 293 287 0 09:39 ? 00:00:00 ps -ef
上面的结果展示了两个关键信息:
第一,redis服务是redis账号启动的,并非root;
第二,redis服务的PID等于1,这很重要,宿主机执行docker stop命令时,该进程可以收到SIGTERM信号量,于是redis应用可以做一些退出前的准备工作,例如保存变量、退出循环等,也就是优雅停机(Gracefully Stopping);
现在我们已经证实了redis服务并非root账号启动,而且该服务进程在容器内还是一号进程,但是我们在Dockerfile和docker-entrypoint.sh脚本中都没有发现切换到redis账号的命令,也没有sudo和su,这是怎么回事呢?
答案是gosu
再看一次redis的docker-entrypoint.sh文件,如下图,地址是:https://github.com/docker-library/redis/blob/6845f6d4940f94c50a9f1bf16e07058d0fe4bc4f/5.0/docker-entrypoint.sh :
注意上图中的代码,我们来分析一下:
-
假设启动容器的命令是docker run --name myredis -idt redis redis-server /usr/local/etc/redis/redis.conf;
-
容器启动后会执行docker-entrypoint.sh脚本,此时的账号是root;
-
当前账号是root,因此会执行上图红框中的逻辑;
-
红框中的$0表示当前脚本的名称,即docker-entrypoint.sh;
-
红框中的$@表示外部传入的所有参数,即redis-server /usr/local/etc/redis/redis.conf;
-
gosu redis “$0” “@”,表示以redis账号的身份执行以下命令:
docker-entrypoint.sh redis-server /usr/local/etc/redis/redis.conf
-
gosu redis “$0” "@"前面加上个exec,表示以gosu redis “$0” "@"这个命令启动的进程替换正在执行的docker-entrypoint.sh进程,这样就保证了gosu redis “$0” "@"对应的进程ID为1;
-
gosu redis “ 0 ” " @ " 导致 d o c k e r − e n t r y p o i n t . s h 再执行一次,但是当前的账号已经不是 r o o t 了,于是会执行兜底逻辑 e x e c “ 0” "@"导致docker-entrypoint.sh再执行一次,但是当前的账号已经不是root了,于是会执行兜底逻辑 exec “ 0”"@"导致docker−entrypoint.sh再执行一次,但是当前的账号已经不是root了,于是会执行兜底逻辑exec“@”;
-
此时的$@是redis-server /usr/local/etc/redis/redis.conf,因此redis服务会启动,而且账号是redis;
-
$@前面有个exec,会用redis-server命令启动的进程取代当前的docker-entrypoint.sh进程,所以,最终redis进程的PID等于1,而docker-entrypoint.sh这个脚本的进程已经被替代,因此就结束掉了;
关于gosu
通过上面的分析,我们对gosu的作用有了基本了解:功能和sudo类似,提升指定账号的权限,用来执行指定的命令,其官网地址是:https://github.com/tianon/gosu ,如下图所示,官方的描述也是说su和sudo命令有一些问题,所以才有了gosu工具来作为替代品:
在docker的官方文档中,也见到了gosu的使用示例,和前面的redis很像,如下图,地址是:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
注意上图中底部的那段话:使用exec XXX命令以确保XXX对应的进程的PID保持为1,这样该进程才能收到宿主机发送给容器的信号量;
为什么要用gosu取代sudo?
前面主要讲gosu的用法,但是为什么要用gosu呢?接下来通过实战对比来看看sudo的问题在哪:
- 执行以下命令创建一个容器:
docker run --rm gosu/alpine gosu root ps aux
上述命令会启动一个安装了gosu的linux容器,并且启动后自动执行命令gosu root ps aux,作用是以root账号的身份执行ps aux,也就是将当前进程都打印出来,执行结果如下:
[root@centos7 ~]# docker run --rm gosu/alpine gosu root ps aux
PID USER TIME COMMAND
1 root 0:00 ps aux
上述信息显示,我们执行docker run时的gosu root ps aux会执行ps命令,该命令成了容器内的唯一进程,这说明通过gosu启动的是符合我们要求的(PID为1),接下来再看看用sudo执行ps命令的效果;
2. 执行以下命令创建一个容器:
docker run --rm ubuntu:trusty sudo ps aux
上述命令会用sudo启动ps命令,结果如下:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 46012 1772 ? Rs 12:05 0:00 sudo ps aux
root 6 0.0 0.0 15568 1140 ? R 12:05 0:00 ps aux
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
我还为大家准备了一套体系化的架构师学习资料包以及BAT面试资料,供大家参考及学习
已经将知识体系整理好(源码,笔记,PPT,学习视频)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
面试资料,供大家参考及学习
已经将知识体系整理好(源码,笔记,PPT,学习视频)
[外链图片转存中…(img-lVa2jgzI-1713457811615)]
[外链图片转存中…(img-mXDq1Z50-1713457811617)]
[外链图片转存中…(img-AqXnJptv-1713457811619)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!