docker与gosu

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: gosu命令在docker的entrypoint.sh中经常用到,本文详细分析了gosu的作用和用法

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码): https://github.com/zq2599/blog_demos

容器中不要使用root账号

  • gosu是个工具,用来提升指定账号的权限,作用与sudo命令类似,而docker中使用gosu的起源来自安全问题;
  • docker容器中运行的进程,如果以root身份运行的会有安全隐患,该进程拥有容器内的全部权限,更可怕的是如果有数据卷映射到宿主机,那么通过该容器就能操作宿主机的文件夹了,一旦该容器的进程有漏洞被外部利用后果是很严重的。
  • 因此,容器内使用非root账号运行进程才是安全的方式,这也是我们在制作镜像时要注意的地方。
  • 这里有篇文章也推荐在容器中使用最小权限的账号:https://snyk.io/blog/10-docker-image-security-best-practices/ ,如下图:

在这里插入图片描述

在镜像中创建非root账号

在这里插入图片描述

  • 可见redis官方镜像使用groupadd和useradd创建了名为redis的组合账号,接下来就是用redis账号来启动服务了,理论上应该是以下套路:
  1. USER redis将账号切换到redis;
  2. 在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

在这里插入图片描述

  • 注意上图中的代码,我们来分析一下:
  • 假设启动容器的命令是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" "@"导致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工具来作为替代品:

在这里插入图片描述

在这里插入图片描述

  • 注意上图中底部的那段话:使用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命令的效果;
  • 执行以下命令创建一个容器:
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进程。
  • 通过上面对可以小结:
  • gosu启动命令时只有一个进程,所以docker容器启动时使用gosu,那么该进程可以做到PID等于1;
  • sudo启动命令时先创建sudo进程,然后该进程作为父进程去创建子进程,1号PID被sudo进程占据;
  • 综上所述,在docker的entrypoint中有如下建议:
  • 创建group和普通账号,不要使用root账号启动进程;
  • 如果普通账号权限不够用,建议使用gosu来提升权限,而不是sudo;
  • entrypoint.sh脚本在执行的时候也是个进程,启动业务进程的时候,在命令前面加上exec,这样新的进程就会取代entrypoint.sh的进程,得到1号PID;

在这里插入图片描述

如何在镜像中安装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/$GOSU_VERSION/gosu-$dpkgArch"; \
    wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.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; \
    chmod +x /usr/local/bin/gosu; \
    gosu nobody true; \
    \
    apt-get purge -y --auto-remove $fetchDeps
  • 至此,gosu在docker中的作用已经分析完毕,希望在您编写自定义镜像的时候,本文能给您带来一些参考;

欢迎关注阿里云开发者社区博客:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...
相关文章
|
域名解析 Kubernetes 网络协议
k8s教程(service篇)-pod的dns域名
k8s教程(service篇)-pod的dns域名
2961 0
|
SQL 关系型数据库 MySQL
docker(15):以docker 方式启动 单机版 tidb
1,关于tidb tidb 其灵感来自于 Google 的 F1 和 Google spanner, TiDB 支持包括传统 RDBMS 和 NoSQL 的特性。 sql 完全支持mysql,同时人家还是一个分布式数据库。 什么分库分表都弱爆了,这个直接分,超级方便。而且还是开源的。 是国内的 技术大牛 黄东旭 的公司 pincap 开发的。 就是之前写 codi
6770 0
|
6月前
|
缓存 Ubuntu Linux
Docker Buildx 简介与安装指南
Docker Buildx 是一个强大的工具,提供了多架构构建、并行构建和高级缓存管理等功能。通过正确安装和配置 Buildx,可以显著提升 Docker 镜像的构建效率和灵活性。希望本文能帮助你更好地理解和使用 Docker Buildx,以提高开发和部署的效率。
3224 16
|
存储 Linux 数据库连接
【专栏】掌握 `envsubst` 可提升在 Linux 系统中处理环境变量的效率。
【4月更文挑战第28天】`envsubst` 是 Linux 中用于替换文本中环境变量值的工具。它遍历文本,将环境变量替换为实际值。要使用它,首先可能需要安装相应软件包。基本用法是 `envsubst < input.txt > output.txt`,将输入文件的环境变量替换后输出到输出文件。命令还支持选项如 `-e` 和 `-d`。实例包括:配置文件替换、脚本执行中的环境变量替换和动态生成文件。掌握 `envsubst` 可提升在 Linux 系统中处理环境变量的效率。
669 0
|
10月前
|
存储 运维 Linux
如何在 Linux 系统中使用 envsubst 命令替换环境变量?
`envsubst` 是 Linux 系统中用于替换文本中环境变量值的实用工具。本文分三部分介绍其工作原理、使用方法及实际应用,包括配置文件替换、脚本执行中环境变量替换和动态生成文件等场景,帮助用户高效利用 `envsubst` 进行开发和运维工作。
475 4
|
10月前
Cursor + qwen2.5-coder 32b 的配置方式
安装Cursor后,进入设置修改OpenAI基础URL为阿里云的DashScope接口,并添加Qwen2.5-Coder 32B模型。需先访问阿里云百灵控制台申请免费Key。配置完成后,即可使用该模型进行开发和测试。
7764 2
|
运维 Kubernetes Go
"解锁K8s二开新姿势!client-go:你不可不知的Go语言神器,让Kubernetes集群管理如虎添翼,秒变运维大神!"
【8月更文挑战第14天】随着云原生技术的发展,Kubernetes (K8s) 成为容器编排的首选。client-go作为K8s的官方Go语言客户端库,通过封装RESTful API,使开发者能便捷地管理集群资源,如Pods和服务。本文介绍client-go基本概念、使用方法及自定义操作。涵盖ClientSet、DynamicClient等客户端实现,以及lister、informer等组件,通过示例展示如何列出集群中的所有Pods。client-go的强大功能助力高效开发和运维。
926 1
|
弹性计算 关系型数据库 数据库
PostgreSQL 数据库实例只读锁定(readonly) - 硬锁定,软锁定,解锁
标签 PostgreSQL , 只读 , 锁定 , readonly , recovery.conf , 恢复模式 , pg_is_in_revoery , default_transaction_read_only 背景 在一些场景中,可能要将数据库设置为只读模式。 例如, 1、云数据库,当使用的容量超过了购买的限制时。切换到只读(锁定)模式,确保用户不会用超。 2、业务上需要对
7359 0
|
运维 安全 Ubuntu
`/var/log/syslog` 和 `/var/log/messages` 日志详解
`/var/log/syslog` 和 `/var/log/messages` 是Linux系统的日志文件,分别在Debian和Red Hat系发行版中记录系统事件和错误。它们包含时间戳、日志级别、PID及消息内容,由`rsyslog`等守护进程管理。常用命令如`tail`和`grep`用于查看和搜索日志。日志级别从低到高包括`debug`到`emerg`,表示不同严重程度的信息。注意保护日志文件的安全,防止未授权访问,并定期使用`logrotate`进行文件轮转以管理磁盘空间。
4648 1
|
Java Linux 虚拟化
Docker 部署spring-boot项目(超详细 包括Docker详解、Docker常用指令整理等)
Docker 部署spring-boot项目(超详细 包括Docker详解、Docker常用指令整理等)
6618 2