Docker容器中运行的进程,若是以root身份运行话会有安全隐患,该进程拥有容器内的所有权限,更可怕的是若是有数据卷映射到宿主机,那么经过该容器就能操做宿主机的文件夹了,一旦该容器的进程有漏洞被外部利用后果是很严重的。git
所以,容器内使用非root帐号运行进程才是安全的方式,这也是咱们在制做镜像时要注意的地方。github
而咱们今天讲到的gosu 正是解决使用非root用户运行业务进程的一种最佳实践方法。docker
su
和sudo
具备很是奇怪且常常使人讨厌的TTY和信号转发行为的问题。su
和sudo
的设置和使用也有些复杂(特别是在sudo
的状况下),虽然它们有很大的表达力,可是若是您所须要的只是“以特定用户身份运行特定应用程序”,那么它们将再也不那么适合。ubuntu
处理完用户/组后,咱们将切换到指定用户,而后执行指定的进程,gosu自己再也不驻留或彻底不在进程生命周期中。这避免了信号传递和TTY的全部问题。安全
概念老是晦涩的,让咱们经过一些示例来加深理解。bash
$ 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
对于debian:curl
Debian 9 ("Debian Stretch") or newer:ide
RUN set -eux; \ apt-get update; \ apt-get install -y gosu; \ rm -rf /var/lib/apt/lists/*; \ # verify that the binary works gosu nobody true
Older Debian releases (or newer gosu
releases):post
ENV GOSU_VERSION 1.12 RUN set -eux; \ # save list of currently installed packages for later so we can clean up savedAptMark="$(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 signature export 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 dependencies apt-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 works gosu --version; \ gosu nobody true
对于alpine(3.7+):fetch
ENV GOSU_VERSION 1.12 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 signature export 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 dependencies apk del --no-network .gosu-deps; \ \ chmod +x /usr/local/bin/gosu; \ # verify that the binary works gosu --version; \ gosu nobody true
通常是在entrypoint.sh
使用。
例如,Postgres Official Image使用如下脚本做为其ENTRYPOINT:
#!/bin/bash set -e if [ "$1" = 'postgres' ]; then chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then gosu postgres initdb fi exec gosu postgres "$@" fi exec "$@"
关于 exec ,你们能够查阅我以前写的文章,其做用主要是会将gosu postgres 后面命令运行的进程替换entrypoint.sh 进程做为1号进程。而且运行该进程的用户为postgres,而不是root。
拿咱们线上的一个容器来举例:
entrypoint.sh为:
#! /bin/bash set -e chown -R xxxuser:xxxgroup /data/logs exec gosu xxxuser tini -- myprogram -config /etc/config.prod.yaml
exec 到容器执行whoami:
sh-4.2# whoami root
能够看到整个容器当前的用户是root。
而后查看运行咱们tini 和 myprogram进程的用户:
sh-4.2# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND xxxuser 1 0.0 0.0 4372 368 ? Ss 18:17 0:00 tini -- myprogram -config /etc/config.prod.yaml xxxuser 14 2.6 0.4 1015768 315868 ? Sl 18:17 1:20 myprogram -config /etc/config.prod.yaml
到了这里可能你们已经很是清楚了。
至于tini,你们能够查阅我以前的文章。