docker中的gosu和su-exec工具
volume的权限问题
在Docker中,需要把host的目录挂载到container中作为volume使用时,往往会发生文件权限问题。 常见的现象是,container对该路径并无写权限,以致其中服务的各种千奇百怪的问题。
导致这类问题的原因,是container内外的UID不同。 比如,host当前使用docker的用户UID是1000(这是默认第一个用户的UID)。 如果container内的UID是2000,那么host创建的目录对container来说就并非owner,默认情况下不可写入。
此外还有一种情况,那就是挂载前,host上不存在被挂载的目录。 Docker会以root权限,先创建该目录,再挂载。 这就导致,即使host与container的UID都是1000,也会出现无写权限的情况。 这种现象,只会在初始化时出现,但也足够令新手困惑,令老手厌烦。
为什么在Dockerfile中不能把volume的权限配置好? 因为Dockerfile是对image的描述,而volume则是container的内容。 Dockerfile中做出的权限配置,对非volume来说是可以生效的,而对volume则不然。 本质上,host挂载到volume上的目录,是属于host的。 Dockerfile是在docker build
期间执行,而volume则是在docker run
的时候产生。
其实,Docker在自动创建volume路径时,应该再自动地把它修改为container内前台进程的user:group。 然而Docker目前并无此类机制,俺们这些用户就只能另谋出路。
一般的临时方案,都是去手动修改权限。 要么通过chown,把owner改成container内用户的UID; 要么通过chmod 777,搞成所有用户通用。 这些当然不是什么好的长期方案,也违背了Docker方便部署的初衷。
目前看来,最好的方案,还是定制Dockerfile的ENTRYPOINT。
ENTRYPOINT
ENTRYPOINT有以下几个重点:
- ENTRYPOINT 指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有其他传入值作为该命令的参数。
- ENTRYPOINT 的值可以通过
docker run --entrypoint
来覆盖掉。 - 只有 Dockerfile 中的最后一条 ENTRYPOINT 指令会起作用。
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令。换句话说实际执行时,会变成<ENTRYPOINT> "<CMD>"
。
所以在dockerfile中ENTRYPOINT里编写一个入口脚本entrypoint.sh
或docker-entrypoint.sh
。在容器运行的时候通过ENTRYPOINT来做一些操作,比如把volume挂载的目录权限给改正确,然后再切换普通用户运行正常的程序进程。
gosu和su-exec
gosu的github仓库地址:https://github.com/tianon/gosu
用法:
$ gosu
Usage: ./gosu user-spec command [args]eg: ./gosu tianon bash./gosu nobody:root bash -c 'whoami && id'./gosu 1000:1 id./gosu version: 1.1 (go1.3.1 on linux/amd64; gc)
文档中的简单例子:
$ docker run -it --rm ubuntu:trusty su -c 'exec ps aux'
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 46636 2688 ? Ss+ 02:22 0:00 su -c exec ps a
root 6 0.0 0.0 15576 2220 ? Rs 02:22 0:00 ps aux
$ 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
$ 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
不管是su
还是sudo
,他们在执行ps aux
命令的PID编号都不为1。在容器中虽然可以,但是这不是一个好的方案,容器里面 PID=1 的进程就是应用本身。因此可以使用gosu
命令来切换用户执行命令。
对于debian安装方法如下:
Debian 9(“Debian Stretch”)或更新的版本:
RUN set -eux; \apt-get update; \apt-get install -y gosu; \rm -rf /var/lib/apt/lists/*; \
# verify that the binary worksgosu nobody true
较旧的Debian版本(或较新的gosu版本)。
ENV GOSU_VERSION 1.16
RUN set -eux; \
# save list of currently installed packages for later so we can clean upsavedAptMark="$(apt-mark showmanual)"; \apt-get update; \apt-get install -y --no-install-recommends ca-certificates wget; \if ! command -v gpg; then \apt-get install -y --no-install-recommends gnupg2 dirmngr; \elif gpg --version | grep -q '^gpg (GnuPG) 1\.'; then \
# "This package provides support for HKPS keyservers." (GnuPG 1.x only)apt-get install -y --no-install-recommends gnupg-curl; \fi; \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"; \\
# verify the signatureexport GNUPGHOME="$(mktemp -d)"; \gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \command -v gpgconf && gpgconf --kill all || :; \rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \\
# clean up fetch dependenciesapt-mark auto '.*' > /dev/null; \[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \\chmod +x /usr/local/bin/gosu; \
# verify that the binary worksgosu --version; \gosu nobody true
对于alpine (3.7+)
当使用Alpine时,可能也值得检查一下su-exec
(apk add --no-cache su-exec
),自从0.2版以来,它完全与gosu
兼容,文件大小只有几分之一。
ENV GOSU_VERSION 1.16
RUN set -eux; \\apk add --no-cache --virtual .gosu-deps \ca-certificates \dpkg \gnupg \; \\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"; \\
# verify the signatureexport GNUPGHOME="$(mktemp -d)"; \gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \command -v gpgconf && gpgconf --kill all || :; \rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \\
# clean up fetch dependenciesapk del --no-network .gosu-deps; \\chmod +x /usr/local/bin/gosu; \
# verify that the binary worksgosu --version; \gosu nobody true
entrypoint脚本文件
脚本例1:
#!/bin/sh
set -e
ls ${LOG_PATH} > /dev/null 2>&1 || mkdir -p ${LOG_PATH}
chown -R www-data ${LOG_PATH}
if [ $# -gt 0 ];then#su ${USERNAME} -c "exec $@"exec su-exec www-data $@
else#su ${USERNAME} -c "exec uwsgi --ini uwsgi.ini --http=0.0.0.0:${DJANGO_PORT}"exec su-exec www-data uwsgi --ini uwsgi.ini --http=0.0.0.0:${DJANGO_PORT}
fi
脚本说明:
set -e
:如果出现命令执行失败,那么就应该退出脚本不继续往下执行,避免失败对后续有影响。可以避免操作失败还继续往下执行的问题。exec
:系统调用exec
是以新的进程去代替原来的进程,但进程的PID保持不变,可以保证容器的主程序PID=1。
脚本例2:
#!/bin/sh
set -e
if [ "$1" = 'uwsgi' -a "$(id -u)" = '0' ]
thenls ${LOG_PATH} > /dev/null 2>&1 || mkdir -p ${LOG_PATH}chown -R www-data ${LOG_PATH}exec su-exec www-data "$0" "$@"
fi
exec "$@"
脚本说明:
- 当前用户是root的话, 那么创建和修改LOG_PATH目录权限,并切换到www-data的身份,带上剩余的参数,再次运行docker-entrypoint.sh文件(
"$0"
表示docker-entrypoint.sh本身,"$@"
表示剩余的参数)。如果此脚本其他位置还有需要由www-data用户执行的代码,则可以一并执行。 - 当再次执行该脚本时由于已经不是root用户了, 会直接执行
exec "$@"
, 于是直接执行带的参数,即CMD定义的脚本。
在Dockerfile中添加docker-entrypoint.sh脚本,并且需要注意x
执行权限,否则将无权限执行。
COPY docker-entrypoint.sh /usr/local/bin/ENTRYPOINT ["docker-entrypoint.sh"]
通过此docker-entrypoint.sh脚本,可以在容器运行时强制把目录权限修改成需要的权限,即使docker通过root用户初始化创建的volume挂载目录。如此一来,就可以通过容器中的普通用户来运行程序,并在这个普通的权限的目录中写入文件。
docker中的gosu和su-exec工具相关推荐
- yum install -y 是什么意思_为什么你应该在docker 中使用gosu?
为什么要使用gosu? Docker容器中运行的进程,如果以root身份运行话会有安全隐患,该进程拥有容器内的全部权限,更可怕的是如果有数据卷映射到宿主机,那么通过该容器就能操作宿主机的文件夹了,一旦 ...
- 在docker中配置apt工具与python的源均为国内源
在docker中配置apt工具与python的源均为国内源 1.准备并进入一个docker容器 本文主要包含一些常用的配置方法.包括在docker中配置系统源.python源. 打开 linux 终端 ...
- 以两台Linux主机在docker中实现mysql主主备份以用nginx实现mysql高可用
使用nginx反向代理主主备份的两台mysql,连接时连接nginx,当其中一台myql停止后,仍然可以正常使用,如果使用k8s 会简单许多.所谓主主复制就是在主从复制的基础上掉了个头. 请博主买块糖 ...
- docker中使用git_如何在 Docker 中使用 Docker
1. 典型适用场景 在 CI 中,通常会有一个 CI Engine 负责解析流程,控制整个构建过程,而将真正的构建交给 Agent 去完成.例如,Jenkins .GitLab 均是如此. 如下图, ...
- 开发机直连 Docker 中的 Redis 容器小教程
在笔者日常开发中,都是把redis装在windows系统中.虽然可以通过RedisDesktopManager等客户端工具连接操作redis,但是还是觉得low了一些.因为作为程序员,我可能更想在Li ...
- 在Docker中运行ASP.NET Web API解决方案
目录 介绍 先决条件 如何容器化现有项目 添加docker-compose项目 带有docker-compose的容器化解决方案 添加环境变量 后端 前端 不使用Visual Studio运行您的应用 ...
- 【DB宝3】在Docker中使用rpm包的方式安装Oracle 19c
[DB宝3]在Docker中使用rpm包的方式安装Oracle 19c 文章目录 [DB宝3]在Docker中使用rpm包的方式安装Oracle 19c 一.安装Docker软件 二.创建CentOS ...
- 再见 Docker,是时候拥抱下一代容器工具 Containerd 了!
公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 1. Containerd 的前世今生 很久以前,Docker 强势崛起,以"镜像"这个大招席 ...
- docker中使用aspose word转pdf 乱码问题
如果导出的字体是小方块说明乱码, 解决办法就是将 字体文件simsun.ttc安装到docker中 我们想在docker中安装字体 需要先 进入到容器 (注: 进入容器的命令 docker exec ...
最新文章
- 测试开发人员与开发人员_如何升级为开发人员
- 003_Jsp动作标签
- pytorch model.eval()的作用
- python浪漫代码_五行Python代码实现批量抠图
- VxWorks下几种定时延时方法的小结
- FedNLP: 首个联邦学习赋能NLP的开源框架,NLP迈向分布式新时代
- Category 中属性的使用
- android 应用升级sdk版本号,Bugly Android 应用升级 SDK 常见问题
- js生成二维码并下载、批量生成二维码和压缩下载
- 回溯法求解TSP问题(旅行商问题)
- 二叉树非递归遍历——python
- 魔兽世界用宏显示服务器时间,魔兽世界宏命令全表
- tf.keras.losses.LogCosh 双曲余弦 损失函数 示例
- 实习单位评价意见~实习鉴定
- JavaScript脚本编写的两个创意时钟
- 什么是前端、什么是后端
- TTL(生存时间)介绍
- esp32语音控制_乐鑫发布针对物联网嵌入式设备AI语音麦克风阵列开发板
- 【ARCore】Android ARCore 简介 ( AR 增强现实技术简介 | Android 平台常用的 AR 技术 | ARCore 相关资料收集 )
- 6.面向对象,构造器,递归以及对象创建时内存分析(内含代码与练习)