docker与gosu

  1. 启动一个redis容器:

docker run --name myredis -idt redis

  1. 进入容器:

docker exec -it myredis /bin/bash

  1. 在容器内,先更新apt:

apt-get update

  1. 安装ps命令:

apt-get install procps

  1. 执行命令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

在这里插入图片描述

注意上图中的代码,我们来分析一下:

  1. 假设启动容器的命令是docker run --name myredis -idt redis redis-server /usr/local/etc/redis/redis.conf;

  2. 容器启动后会执行docker-entrypoint.sh脚本,此时的账号是root;

  3. 当前账号是root,因此会执行上图红框中的逻辑;

  4. 红框中的$0表示当前脚本的名称,即docker-entrypoint.sh;

  5. 红框中的$@表示外部传入的所有参数,即redis-server /usr/local/etc/redis/redis.conf;

  6. gosu redis “$0” “@”,表示以redis账号的身份执行以下命令:

docker-entrypoint.sh redis-server /usr/local/etc/redis/redis.conf

  1. gosu redis “$0” "@"前面加上个exec,表示以gosu redis “$0” "@"这个命令启动的进程替换正在执行的docker-entrypoint.sh进程,这样就保证了gosu redis “$0” "@"对应的进程ID为1;

  2. 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”"@"导致dockerentrypoint.sh再执行一次,但是当前的账号已经不是root了,于是会执行兜底逻辑exec@”;

  3. 此时的$@是redis-server /usr/local/etc/redis/redis.conf,因此redis服务会启动,而且账号是redis;

  4. $@前面有个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的问题在哪:

  1. 执行以下命令创建一个容器:

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

尽管我们只想启动ps进程,但是容器内出现了两个进程,sudo命令会创建第一个进程,然后该进程再创建了ps进程,而且ps进程的PID并不等于1,这是达不到我们要求的,此时在宿主机向该容器发送信号量,收到信号量的是sudo进程。

通过上面对可以小结:

  1. gosu启动命令时只有一个进程,所以docker容器启动时使用gosu,那么该进程可以做到PID等于1;

  2. sudo启动命令时先创建sudo进程,然后该进程作为父进程去创建子进程,1号PID被sudo进程占据;

综上所述,在docker的entrypoint中有如下建议:

  1. 创建group和普通账号,不要使用root账号启动进程;

  2. 如果普通账号权限不够用,建议使用gosu来提升权限,而不是sudo;

  3. entrypoint.sh脚本在执行的时候也是个进程,启动业务进程的时候,在命令前面加上exec,这样新的进程就会取代entrypoint.sh的进程,得到1号PID;

  4. exec " @ " 是个保底的逻辑,如果 e n t r y p o i n t . s h 的入参在整个脚本中都没有被执行,那么 e x e c " @"是个保底的逻辑,如果entrypoint.sh的入参在整个脚本中都没有被执行,那么exec " @"是个保底的逻辑,如果entrypoint.sh的入参在整个脚本中都没有被执行,那么exec"@"会把入参执行一遍,如果前面执行过了,这一行就不起作用,这个命令的细节在Stack Overflow上有详细的描述,如下图,地址是:https://stackoverflow.com/questions/39082768/what-does-set-e-and-exec-do-for-docker-entrypoint-scripts

在这里插入图片描述

如何在镜像中安装gosu

前面的redis例子中,我们看到docker-entrypoint.sh中用到了gosu,那么是在哪里安装了gosu呢?自然是Dockerfile中,一起来看看redis的Dockerfile中是如何安装gosu的:

grab gosu for easy step-down from root

https://github.com/tianon/gosu/releases

ENV GOSU_VERSION 1.10

RUN set -ex; \

\

fetchDeps=" \

ca-certificates \

dirmngr \

gnupg \

wget \

"; \

apt-get update; \

apt-get install -y --no-install-recommends $fetchDeps; \

rm -rf /var/lib/apt/lists/*; \

\

dpkgArch=“$(dpkg --print-architecture | awk -F- ‘{ print $NF }’)”; \

wget -O /usr/local/bin/gosu “https://github.com/tianon/gosu/releases/download/ G O S U V E R S I O N / g o s u − GOSU_VERSION/gosu- GOSUVERSION/gosudpkgArch”; \

wget -O /usr/local/bin/gosu.asc “https://github.com/tianon/gosu/releases/download/ G O S U V E R S I O N / g o s u − GOSU_VERSION/gosu- GOSUVERSION/gosudpkgArch.asc”; \

export GNUPGHOME=“$(mktemp -d)”; \

gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \

gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \

gpgconf --kill all; \

rm -r “$GNUPGHOME” /usr/local/bin/gosu.asc; \

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值