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,你們能夠查閱我以前的文章。