Docker volume權限導致的幾個問題


個人學習筆記,謝絕轉載!!!
原文:https://www.cnblogs.com/wshenjin/p/10518785.html


掛宿主目錄的權限問題

由於容器和宿主機共用了一套內核,因此同一個uid對應的容器用戶和宿主機用戶(哪怕用戶名不同)對於內核權限控制而言都是同一個用戶。而默認情況下,如果未做特殊配置,容器里的進程默認是以root用戶運行的。

這里就有兩個問題:

  1. 直接以root權限運行容器不安全
  2. 以其他用戶運行容器可能因為掛載宿主機目錄的權限問題而蛋疼

先說第二個問題:

導致這個問題的原因,一般是因為容器內該uid的用戶對宿主目錄無權限導致的。 比如,容器里的mysql用戶的uid是2000,而宿主當前mysql用戶uid是1000,即便宿主要掛載的目錄權限是mysql:mysql,容器里看到的權限也是1000:1000而權限被拒絕。

還有一種情況,宿主不存在被掛載的目錄。 Docker會以root權限先創建該目錄再掛載。 這就導致以普通用戶運行的容器進程無權限訪問該目錄

這個問題最簡單的處理方式就是先手動chmod 777 或者chown 2000:2000 ,再啟動容器。不過這不是長久之計也違背了docker的初衷。

對於這個問題,目前大家最好的處理方式,就在entrypoint腳本里先對需要本地掛載的目錄做權限配置,再啟動服務。因為執行entrypoint腳本則是在啟動階段(start)所以在entrypoint.sh中可以對volume做權限配置。 當然,權限配置需要root權限,這就需要以root啟動容器。

第一個問題:

以root權限運行容器,會導致容器中的進程有了適當的機會,它就可以控制宿主機上的一切!如何解決比較好呢?

有些服務可以直接以root啟用,並配置其他用戶運行。

比如:

  • mysqld可以mysqld --defaults-file=/etc/my.cnf --user=mysql

  • memcached可以memcached -u www

  • 甚至像ngx php-fpm 這類的服務可以直接在配置文件里指定運行worker進程的用戶而master進程保持root運行

但對於像Redis這類服務呢?這類服務就需要使用Dockerfile 的USER指令去指定整個容器內部的運行用戶了,當然還可以在entrypoint腳本里采用su命令切到相關的用戶運行服務進程,但這種情況就會導致容器里出現一個sh父進程,如下:

[root@docker_121.201.47.196 ~]# docker exec -it redis ps -ef 
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 09:20 pts/0    00:00:00 /bin/sh /entrypoint /redis-se
root         6     1  0 09:20 pts/0    00:00:00 su - redis -s /bin/sh -c /redis-
redis        7     6  0 09:20 ?        00:00:02 /redis-server 0.0.0.0:6001
root        27     0  0 09:42 pts/1    00:00:00 ps -ef

這里的Redis就是直接在entrypoint腳本里采用su命令切到"redis"用戶啟動的。

可以發現容器里的除了redis-server,還有pid為1的sh進程。如果存粹只是為了運行Redis進程,其實也沒啥問題。不過從規范的角度,容器里不應該運行不必要的多余的進程。

而且上面的栗子還存在一個問題,就是PID1進程是sh而不是redis-server,這樣docker stop 該容器時,里面的redis-sever進程將無法接收到關閉的信號而導致超時十秒被docker強殺,這在需要數據落地的場景下是不被允許的。比如mysql的docker容器一般只跑mysqld進程而不需要mysqld_safe。

詳參:https://www.oschina.net/translate/docker-and-the-pid-1-zombie-reaping-problem?spm=5176.100239.blogcont5545.17.qOGovX

gosu

那對於上面redis引出的這個問題,該如何解決最好呢?

這里推薦在entrypoint腳本里使用gosu來替換su,來切換用戶運行程序

具體gosu的解析,可以參考:https://segmentfault.com/a/1190000004527476

Redis的栗子

對於上面扯的幾個問題,這里用redis作為栗子重寫Dockerfile https://www.cnblogs.com/wshenjin/p/9328763.html

Dockerfile:

FROM centos

COPY ["src","/src"]

RUN groupadd -g 1002 redis \
    && useradd -u 1002 -g redis -s /sbin/nologin redis ;\
    yum -y install tcl-8.5* make gcc gcc-c++ \
    && yum clean all ;\
    cd /src/ \
    && cp redis_entrypoint.sh / \
    && cp gosu /usr/local/bin/ \
    && cp redis.conf /etc/ \
    && tar xf redis-3.2.7.tar.gz \
    && cd redis-3.2.7/ \
    && make MALLOC=libc \
    && cp src/{redis-benchmark,redis-check-aof,redis-cli,redis-sentinel,redis-server} /usr/local/bin/ ;\
    cd / ;\
    mkdir -p /data/redis \
    && chown redis:redis -R /data/redis ;\
    echo "/usr/local/bin/redis-cli -h 127.0.0.1 -p 6001 -a $(awk '/^requirepass/{print $NF}' /etc/redis.conf) save" >> /root/redis_stop ;\
    echo "sleep 5s" >> /root/redis_stop ;\
    echo "/usr/local/bin/redis-cli -h 127.0.0.1 -p 6001 -a $(awk '/^requirepass/{print $NF}' /etc/redis.conf) shutdown" >> /root/redis_stop ;\
    echo "/usr/local/bin/redis-cli -h 127.0.0.1 -p 6001 -a $(awk '/^requirepass/{print $NF}' /etc/redis.conf) info" >> /root/redis_info ;\
    echo "/usr/local/bin/redis-cli -h 127.0.0.1 -p 6001 -a $(awk '/^requirepass/{print $NF}' /etc/redis.conf) bgsave" >> /root/redis_bgsave ;\
    chmod 700 /root/redis_* ;\
    yum remove -y iputils* \
    bind* \
    vim* \
    make \
    cmake \
    cpp \
    acl \
    rootfiles \
    lzo \
    readline-devel \ 
    python-chardet \
    hostnamed \
    bus-python \
    gobject-introspection \
    libxml2-python \
    python-gobject-base \
    basesystem \
    libgomp \
    libstdc++-devel \
    glibc-headers \
    mpfr \
    passwd \
    yum-plugin-ovl \
    dbus-glib \
    python-kitchen \
    ncurses-devel \
    kernel-headers \
    gpg-pubkey \ 
    yum-util ;\
    userdel mail ;\
    userdel ftp ;\
    userdel games ;\
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/ ;\
    ln -svf /etc/Shanghai /etc/localtime ;\
    rm -rf /src /root/.bash* /root/.cshrc /root/.tcshrc /var/cache /usr/share/zoneinfo 

EXPOSE 6001

HEALTHCHECK --interval=60s --timeout=5s CMD /usr/local/bin/redis-cli -h 127.0.0.1 -p 6001 -a $(awk '/^requirepass/{print $NF}' /etc/redis.conf) ping | grep -i PONG || exit 1

ENTRYPOINT ["/redis_entrypoint.sh"]

redis_entrypoint.sh:

#/bin/sh

chown redis:redis -R /data/redis/ #redis數據目錄,log pid rdb。
exec gosu redis /bin/sh -c "/usr/local/bin/redis-server /etc/redis.conf"

Redis用到的唯一目錄是/data/redis/,entrypoint腳本會進行權限處理。當掛載宿主目錄時,不論宿主目錄如何,entrypoint腳本都會處理好。

這樣得到的容器內部進程就如下:

[root@docker_121.201.47.196 ~]# docker exec -it redis ps -ef 
UID        PID  PPID  C STIME TTY          TIME CMD
redis        1     0  0 Mar08 pts/0    00:12:15 /usr/local/bin/redis-server 0.0.0.0:6001
root     18206     0  0 17:42 pts/1    00:00:00 ps -ef


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM