首页
常用命令
About Me
推荐
weibo
github
Search
1
linuxea:gitlab-ci之docker镜像质量品质报告
30,554 阅读
2
Graylog收集文件日志实例
17,013 阅读
3
linuxea:jenkins+pipeline+gitlab+ansible快速安装配置(1)
16,475 阅读
4
git+jenkins发布和回滚示例
16,306 阅读
5
linuxea:如何复现查看docker run参数命令
16,075 阅读
ops
Openvpn
Sys Basics
rsync
Mail
NFS
Other
Network
HeartBeat
server 08
Code
Awk
Shell
Python
Golang
virtualization
KVM
Docker
openstack
Xen
kubernetes
kubernetes-cni
Data
Mariadb
PostgreSQL
MongoDB
Redis
MQ
Ceph
TimescaleDB
kafka
surveillance system
zabbix
ELK Stack
Open-Falcon
Prometheus
Web
apache
Tomcat
Nginx
自动化
Puppet
Ansible
saltstack
Proxy
HAproxy
Lvs
varnish
更多
音乐
影视
music
Internet Consulting
最后的净土
软件交付
持续集成
gitops
devops
登录
Search
标签搜索
kubernetes
docker
zabbix
Golang
mariadb
持续集成工具
白话容器
linux基础
nginx
elk
dockerfile
Gitlab-ci/cd
最后的净土
基础命令
docker-compose
saltstack
haproxy
jenkins
GitLab
prometheus
marksugar
累计撰写
657
篇文章
累计收到
140
条评论
首页
栏目
ops
Openvpn
Sys Basics
rsync
Mail
NFS
Other
Network
HeartBeat
server 08
Code
Awk
Shell
Python
Golang
virtualization
KVM
Docker
openstack
Xen
kubernetes
kubernetes-cni
Data
Mariadb
PostgreSQL
MongoDB
Redis
MQ
Ceph
TimescaleDB
kafka
surveillance system
zabbix
ELK Stack
Open-Falcon
Prometheus
Web
apache
Tomcat
Nginx
自动化
Puppet
Ansible
saltstack
Proxy
HAproxy
Lvs
varnish
更多
音乐
影视
music
Internet Consulting
最后的净土
软件交付
持续集成
gitops
devops
页面
常用命令
About Me
推荐
weibo
github
搜索到
91
篇与
Docker
的结果
2019-02-26
linuxea:dockerfile中的RUN指令对镜像大小的影响
我们在github上,或者在一些应用官方提供的docker镜像的Dockerfile中,经常会看到很多难以琢磨的操作,这篇文章主要说明使用Dockerfile的RUN命令为什么要在后面使用&&链接,以及在实际镜像中的影响和区别。阅读本篇文章对Dockerfile的RUN命令和层有更深的认识,同时学会更好的缩减容器的镜像。为了更好的演示RUN命令与层带来的差异,我们分为几个步骤来做!1,测试多条命令的大小与差异2,减少多条命令的大小与差异多条RUN首先,我们进行多条命令RUN命令测试,如下:安装nginx,php,curl三个软件包[root@linuxea.com ~]# cat Dockerfile FROM alpine:3.9 as default MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN apk add nginx RUN apk add php RUN apk add curl在Build的同时,依次进行[root@linuxea.com ~]# docker build -t linuxea:26 . Sending build context to Docker daemon 427kB Step 1/6 : FROM alpine:3.9 as default ---> caf27325b298 Step 2/6 : MAINTAINER www.linuxea.com ---> Using cache ---> 5ee27f5e579a Step 3/6 : LABEL maintainer="www.linuxea.com" ---> Using cache ---> a9b2f826f6c9 Step 4/6 : RUN apk add nginx ---> Running in c2b048d78656 fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz (1/2) Installing pcre (8.42-r1) (2/2) Installing nginx (1.14.2-r0) Executing nginx-1.14.2-r0.pre-install Executing busybox-1.29.3-r10.trigger OK: 7 MiB in 16 packages Removing intermediate container c2b048d78656 ---> 116c307055ed Step 5/6 : RUN apk add php ---> Running in 29c651203043 (1/7) Installing php7-common (7.2.14-r0) (2/7) Installing ncurses-terminfo-base (6.1_p20190105-r0) (3/7) Installing ncurses-terminfo (6.1_p20190105-r0) (4/7) Installing ncurses-libs (6.1_p20190105-r0) (5/7) Installing libedit (20181209.3.1-r0) (6/7) Installing libxml2 (2.9.9-r1) (7/7) Installing php7 (7.2.14-r0) Executing busybox-1.29.3-r10.trigger OK: 21 MiB in 23 packages Removing intermediate container 29c651203043 ---> ec1cbef7f89e Step 6/6 : RUN apk add curl ---> Running in d96aef6e09f8 (1/5) Installing ca-certificates (20190108-r0) (2/5) Installing nghttp2-libs (1.35.1-r0) (3/5) Installing libssh2 (1.8.0-r4) (4/5) Installing libcurl (7.63.0-r0) (5/5) Installing curl (7.63.0-r0) Executing busybox-1.29.3-r10.trigger Executing ca-certificates-20190108-r0.trigger OK: 22 MiB in 28 packages Removing intermediate container d96aef6e09f8 ---> f4000a9883f1 Successfully built f4000a9883f1 Successfully tagged linuxea:26此时构建的镜像大小为18.8M[root@linuxea.com ~]# docker images linuxea:26 REPOSITORY TAG IMAGE ID CREATED SIZE linuxea 26 f4000a9883f1 12 seconds ago 18.8MB分为七层,每一条命令都分为一层,加起来的总大小是18.8M[root@linuxea.com ~]# docker history linuxea:26 IMAGE CREATED CREATED BY SIZE COMMENT f4000a9883f1 18 seconds ago /bin/sh -c apk add curl 1.68MB ec1cbef7f89e 21 seconds ago /bin/sh -c apk add php 8.89MB 116c307055ed 54 seconds ago /bin/sh -c apk add nginx 2.74MB a9b2f826f6c9 9 days ago /bin/sh -c #(nop) LABEL maintainer=www.linu… 0B 5ee27f5e579a 9 days ago /bin/sh -c #(nop) MAINTAINER www.linuxea.com 0B caf27325b298 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:2a1fc9351afe35698… 5.53MB 单条RUN此刻,我们尝试将安装的nginx,php,curl放在一条RUN,使用&&链接起来执行[root@linuxea.com ~]# cat Dockerfile FROM alpine:3.9 as default MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN apk add nginx \ && apk add php \ && apk add curl [root@linuxea.com ~]# docker build -t linuxea:26-1 . Sending build context to Docker daemon 427kB Step 1/4 : FROM alpine:3.9 as default ---> caf27325b298 Step 2/4 : MAINTAINER www.linuxea.com ---> Using cache ---> 5ee27f5e579a Step 3/4 : LABEL maintainer="www.linuxea.com" ---> Using cache ---> a9b2f826f6c9 Step 4/4 : RUN apk add nginx && apk add php && apk add curl ---> Running in 19fb009c38d1 fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz (1/2) Installing pcre (8.42-r1) (2/2) Installing nginx (1.14.2-r0) Executing nginx-1.14.2-r0.pre-install Executing busybox-1.29.3-r10.trigger OK: 7 MiB in 16 packages (1/7) Installing php7-common (7.2.14-r0) (2/7) Installing ncurses-terminfo-base (6.1_p20190105-r0) (3/7) Installing ncurses-terminfo (6.1_p20190105-r0) (4/7) Installing ncurses-libs (6.1_p20190105-r0) (5/7) Installing libedit (20181209.3.1-r0) (6/7) Installing libxml2 (2.9.9-r1) (7/7) Installing php7 (7.2.14-r0) Executing busybox-1.29.3-r10.trigger OK: 21 MiB in 23 packages (1/5) Installing ca-certificates (20190108-r0) (2/5) Installing nghttp2-libs (1.35.1-r0) (3/5) Installing libssh2 (1.8.0-r4) (4/5) Installing libcurl (7.63.0-r0) (5/5) Installing curl (7.63.0-r0) Executing busybox-1.29.3-r10.trigger Executing ca-certificates-20190108-r0.trigger OK: 22 MiB in 28 packages Removing intermediate container 19fb009c38d1 ---> 6d1be09ddac1 Successfully built 6d1be09ddac1 Successfully tagged linuxea:26-1得到的大小是18.7M[root@linuxea.com ~]# docker images linuxea:26-1 REPOSITORY TAG IMAGE ID CREATED SIZE linuxea 26-1 6d1be09ddac1 9 seconds ago 18.7MB而层也随机变少了,只有5层,18.7M[root@linuxea.com ~]# docker history linuxea:26-1 IMAGE CREATED CREATED BY SIZE COMMENT 6d1be09ddac1 23 seconds ago /bin/sh -c apk add nginx && apk add php &… 13.1MB a9b2f826f6c9 9 days ago /bin/sh -c #(nop) LABEL maintainer=www.linu… 0B 5ee27f5e579a 9 days ago /bin/sh -c #(nop) MAINTAINER www.linuxea.com 0B caf27325b298 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:2a1fc9351afe35698… 5.53MB 思考?现在我们经过对比,多条RUN似乎会导致多层,并且比一条RUN,也就是同一个层的镜像构建的要小一些。那现在肯定有人会迷惑,这说明了什么?现在,我们不妨在看看,我们换个方式。[root@linuxea.com ~]# cat Dockerfile FROM alpine:3.9 as default MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN apk add nginx RUN apk add php RUN apk add curl RUN apk del nginx php curl如上,假设,我此刻在Dockerfile中已经使用了在这三个软件,在构建的最后步骤,我希望删掉->依赖安装最小化原则。而后我在进行build[root@linuxea.com ~]# docker build -t linuxea:26-2 . Sending build context to Docker daemon 427kB Step 1/7 : FROM alpine:3.9 as default ---> caf27325b298 Step 2/7 : AINTAINER www.linuxea.com ---> Using cache ---> 5ee27f5e579a Step 3/7 : LABEL maintainer="www.linuxea.com" ---> Using cache ---> a9b2f826f6c9 Step 4/7 : RUN apk add nginx ---> Using cache ---> 116c307055ed Step 5/7 : RUN apk add php ---> Using cache ---> ec1cbef7f89e Step 6/7 : RUN apk add curl ---> Using cache ---> f4000a9883f1 Step 7/7 : RUN apk del nginx php curl ---> Running in 124fea0865fb (1/14) Purging nginx (1.14.2-r0) (2/14) Purging php7 (7.2.14-r0) (3/14) Purging php7-common (7.2.14-r0) (4/14) Purging curl (7.63.0-r0) (5/14) Purging pcre (8.42-r1) (6/14) Purging libedit (20181209.3.1-r0) (7/14) Purging ncurses-libs (6.1_p20190105-r0) (8/14) Purging ncurses-terminfo (6.1_p20190105-r0) (9/14) Purging ncurses-terminfo-base (6.1_p20190105-r0) (10/14) Purging libxml2 (2.9.9-r1) (11/14) Purging libcurl (7.63.0-r0) (12/14) Purging ca-certificates (20190108-r0) Executing ca-certificates-20190108-r0.post-deinstall (13/14) Purging nghttp2-libs (1.35.1-r0) (14/14) Purging libssh2 (1.8.0-r4) Executing busybox-1.29.3-r10.trigger OK: 6 MiB in 14 packages Removing intermediate container 124fea0865fb ---> 3a74abf45025 Successfully built 3a74abf45025 Successfully tagged linuxea:26-2构建完成。镜像的大小仍然是18.9M。[root@linuxea.com ~]# docker images linuxea:26-2 REPOSITORY TAG IMAGE ID CREATED SIZE linuxea 26-2 3a74abf45025 16 seconds ago 18.9MB我们使用docker history命令查看镜像,可见我们的确执行了apk del nginx php curl命令[root@linuxea.com ~]# docker history linuxea:26-2 IMAGE CREATED CREATED BY SIZE COMMENT 3a74abf45025 28 seconds ago /bin/sh -c apk del nginx php curl 21.2kB f4000a9883f1 6 minutes ago /bin/sh -c apk add curl 1.68MB ec1cbef7f89e 6 minutes ago /bin/sh -c apk add php 8.89MB 116c307055ed 6 minutes ago /bin/sh -c apk add nginx 2.74MB a9b2f826f6c9 9 days ago /bin/sh -c #(nop) LABEL maintainer=www.linu… 0B 5ee27f5e579a 9 days ago /bin/sh -c #(nop) MAINTAINER www.linuxea.com 0B caf27325b298 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:2a1fc9351afe35698… 5.53MB 我们进入容器确认是否被卸载[root@linuxea.com ~]# docker run --rm -it linuxea:26-2 sh / # curl sh: curl: not found / # 发生了什么?为什么在Dockerfile删除的软件已经消失,而层的大小并没有因为删除而缩减?要回答这一点,我们要返回上面的docker history linuxea:26-2中查看。我们将SIZE用加法运算,得到的大小与docker image查看的几乎接近,如下[root@linuxea.com ~]# docker history linuxea:26-2|awk '{print $NF}'|grep -v "COMMENT" 21.2kB 1.68MB 8.89MB 2.74MB 0B 0B 0B 5.53MB这说明docker会将所有的层相加。尽管我们在删除了这三个包,但是在上一层中,这三个包仍然是存在的,因为从下到上叠加的层最终构成了镜像。所以,要从层中有效删除,就需要在一个层中有效的组织。这些层是否就没有了作用?当在测试环境中,编写Dockerfile的时候可以使用多层,也就是多条RUN的命令来缓存每一个层做测试或者调试,当相同的指令就会复用缓存中间层。这可以有效的缩减build时间,原因是利用了docker的构建缓存。而构建缓存在CI/CD中是很有用的。RUN正确的使用方式我们应该将命令使用RUN和&&链接起来,这样在中间的层就表现为一个层,这样的情况并不利于调试,如上所述。每次构建都会认为是一条指令,当发生一条命令的变化,就无法使用缓存加速构建。我们重新build,并且删除缓存FROM alpine:3.9 as default MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN apk add nginx \ && apk add php \ && apk add curl \ && apk del nginx php curl \ && rm /var/cache/apk/*重新build后的大小仅为5.56M[root@linuxea.com ~]# docker images linuxea:26-3 REPOSITORY TAG IMAGE ID CREATED SIZE linuxea 26-3 f300b5797d81 33 seconds ago 5.56MB[root@linuxea.com ~]# docker history linuxea:26-3 IMAGE CREATED CREATED BY SIZE COMMENT f300b5797d81 2 seconds ago /bin/sh -c apk add nginx && apk add php &&… 26kB a9b2f826f6c9 10 days ago /bin/sh -c #(nop) LABEL maintainer=www.linu… 0B 5ee27f5e579a 10 days ago /bin/sh -c #(nop) MAINTAINER www.linuxea.com 0B caf27325b298 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:2a1fc9351afe35698… 5.53MB 其他的办法我们在使用RUN命令的时候,除了尽可能的减少层,我们还可以使用docker多阶段构建Multi-Stage。docker多阶段构建将"上一次的构建用在下一个Dockerfile中",这很实用FROM golang:1.7.3 as builder WORKDIR /go/src/github.com/alexellis/href-counter/ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest WORKDIR /root/ COPY --from=builder /go/src/github.com/alexellis/href-counter/app .延伸阅读linuxea:如何利用docker构建缓存快速迭代?linuxea:docker多阶段构建Multi-Stage与Builder对比总结学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器docker-compose
2019年02月26日
4,765 阅读
0 评论
0 点赞
2019-02-25
linuxea:docker卷和文件系统权限
Docker容器是无状态的(不需要在运行时候做持久化数据)。一般来讲,一些有状态的,存储重要数据的应用需要一些持久存储。卷功能提供了一种支持此要求的方法,但它带有一些关于文件系统权限的问题。在大多数部署的设置中,将使用容器编排机制,并且持久存储由某些公共云产品提供,这些产品可能具有自己的配置权限的方式。但是,在本地开发或产品的早期迭代期间,最简单的方法是将主机目录公开为docker卷。简而言之,这些是将主机目录配置为卷时需要注意的事实:从主机和容器的角度来看,对卷中内容设置的文件权限是相同的。只有UID(用户ID)和GID(组ID)很重要。例如,用户和组的名称和密码不需要匹配,甚至不需要存在于主机和容器中根据自己的配置对在运行的容器,具有所有操作权限,如:强制执行文件权限。例如,如果用户A同时存在于主机和容器中,将用户A添加到主机上的组B,不允许用户A写入容器内的组B所拥有的目录,除非在容器内创建组B并且向其添加用户A.默认情况下,容器的命令以root身份运行(在基于unix的系统上)可以将文件/目录所有权设置为不属于任何实际组的GID如果你掌握上述事实,应该能够配置容器和卷而不会有太多意外事故。如果你不熟悉UNIX文件权限,我可以推荐本站的linuxea基础之权限管理一篇。权限我们可以方便地为本地开发配置内容,示例:将要用作卷的目录的组所有权设置为某些GID(在此示例中为1101)未在主机上的任何实际组上使用[root@linuxea.com ~]# mkdir /root/linuxea.com [root@linuxea.com ~]# chown :1101 /root/linuxea.com更改目录的权限以授予组成员完全访问权限(读取+写入+执行)[root@linuxea.com ~]# chmod 755 /root/linuxea.com确保文件夹中的所有之后内容都将继承组所有权[root@linuxea.com ~]# chmod g+s /root/linuxea.com/在Dockerfile中创建一个用户,该用户是1101组的成员看起来可以是这样FROM alpine:3.9 MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN addgroup --gid 1101 www \ && adduser -u 1101 -S -H -s /bin/bash -g www -G www www -D(可选)将主机用户添加到组中,以便你方便地使用主机中的目录centos:useradd linuxea上面的示例是一个最小化设置,可确保你不以root身份运行容器命令,并且可以使用主机上附加卷的内容,而无需使用主机root用户(你必须确保你使用的非root用户启动容器。该设置在构建时执行硬编码配置,使你无法在运行时调整GID。如果需要,你必须将GID作为环境变量传递,并包含可以使用它的通用脚本。示例如下:entrypoint.sh#!/bin/bash # www.linuxea.com USER_ID=${LOCAL_USER_ID:-1101} USER_NAME=${LOCAL_USER_NAME:-www} echo "Starting with UID : $USER_ID And user $USER_NAME" addgroup --gid $USER_ID $USER_NAME adduser -u $USER_ID -S -H -s /bin/bash -g $USER_NAME -G $USER_NAME $USER_NAME -D # useradd --shell /bin/bash -u $USER_ID -o -c "" -m user export HOME=/home/$USER_NAME exec /usr/local/bin/gosu $USER_NAME "$@"我们在这里做的是从环境变量中获取UID和将要用到的名称,如果它不存在则默认为1101,NAME不存在则www,并且使用adduser/useradd设置UID时实际使用熟悉的命令创建用户“www” 。entrypoint创建最后我们用这个用户gosu来执行我们的流程"$@"。记住来自Dockerfile的CMD或来自docker CLI的命令作为命令行参数传递给entrypoint.sh脚本。我们在看DockerfileFROM alpine:3.9 MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" COPY entrypoint.sh /bin/entrypoint.sh RUN apk update \ && apk add bash \ && wget https://github.com/tianon/gosu/releases/download/1.11/gosu-amd64 -O /usr/local/bin/gosu \ && chmod +x /bin/entrypoint.sh /usr/local/bin/gosu \ && rm /var/cache/apk/* ENTRYPOINT ["entrypoint.sh"] CMD ["sleep","30000"]build[root@linuxea.com ~]# docker build -t marksugar/alpine:3.9 .run可以传递LOCAL_USER_ID和LOCAL_USER_NAME改变脚本参数,从而改变gid和name[root@linuxea.com ~]# docker run -d marksugar/alpine:3.9 00f7afb58a9ee069f1bca6fdd716131e48538bc161911e62bf4249328f98270b[root@linuxea.com ~]# docker exec -it 00f7 ps aux PID USER TIME COMMAND 1 www 0:00 sleep 30000 14 root 0:00 ps aux[root@linuxea.com ~]# ps aux|grep sleep 1101 27414 0.3 0.0 1520 4 ? Ss 21:56 0:00 sleep 30000现在你就可以下载marksugar/alpine:3.9镜像来作为基础镜像了。延伸阅读linuxea:了解uid和gid如何在docker容器中工作linuxea:docker容器中程序不应该以root用户身份运行学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用Bash命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器docker-compose
2019年02月25日
2,492 阅读
0 评论
0 点赞
2019-02-23
linuxea:docker容器中程序不应该以root用户身份运行
容器中的进程不应以root身份运行,或者假设它们是root用户。正确的方式是使用已知的UID和GID在Dockerfile中创建用户,并以此用户身份运行你的进程。通过限制对资源的访问,遵循此模式的映像更容易安全运行概观精心设计的系统遵循最小特权原则。这简单地说明应用程序应该只能访问它所需的资源以执行其所需的功能。这在设计安全系统时至关重要。无论是恶意还是由于某些错误,进程可能会在运行时产生意外后果。保护自己免受任何意外的最佳方法之一是仅授予进程运行所需的最小权限。大多数容器化进程是应用程序服务,因此不需要root访问权限。虽然Docker需要root运行,但容器本身却不需要。编写良好,安全且可重用的Docker镜像不应该以root身份运行,并且应该提供一种可预测且简单的方法来限制访问。为什么这很重要请记住,在容器中运行的进程与在Linux上运行的其他进程没有什么不同,除了它有一小段元数据声明它在容器中。容器不是信任边界,因此,容器中运行的任何东西都应该与主机本身上运行的任何东西一样对待。就像你不会(或不应该)在服务器上以root身份运行任何东西一样,你不应该在服务器上的容器中以root身份运行任何东西。在别处创建的运行二进制文件需要大量的信任条件,对于容器中的二进制文件也是如此。如果容器内的进程默认以root身份运行,则可以在启动容器时更改uid和gid。作为镜像的作者,你应该默认以不同的用户身份运行,并且更容易限制该用户的访问权限。通过在Dockerfile中创建用户,你不仅可以使其默认安全,而且更容易保持安全。示例将显示以root身份运行容器的风险。让我们在/root目录中创建一个test.file文件,防止root 以外的任何人查看它:[root@linuxea.com ~]# echo "www.linuxea.com" >> ./test.file [root@linuxea.com ~]# chmod 600 test.file [root@linuxea.com ~]# ls -l total 4 -rw------- 1 root root 16 Feb 23 13:24 test.file而后切换到linuxea用户下[root@linuxea.com ~]# su - linuxea Last login: Wed Feb 20 17:44:35 CST 2019 on pts/2[linuxea@linuxea.com ~]$ cat /root/test.file cat: /root/test.file: Permission denied [linuxea@linuxea.com ~]$ ls /root/test.file ls: cannot access /root/test.file: Permission denied [linuxea@linuxea.com ~]$ ls -l /root/test.file ls: cannot access /root/test.file: Permission denied现在我在root家目录创建了一个test.file的文件,并且追加了信息,权限是600,只有root可以看到内容。以普通个用户登陆是没有权限查看的。现在,我创建一个Dockerfile镜像FROM alpine:latest MAINTAINER www.linuxea.com mark ENTRYPOINT ["cat","/opt/test.file"][linuxea@linuxea.com ~]$ docker build -t linuxea:23 . Sending build context to Docker daemon 10.24kB Step 1/3 : FROM alpine:latest ---> caf27325b298 Step 2/3 : MAINTAINER www.linuxea.com mark ---> Using cache ---> c9603327aeb5 Step 3/3 : ENTRYPOINT ["cat","/opt/test.file"] ---> Running in d1d8fd1f5fd6 Removing intermediate container d1d8fd1f5fd6 ---> 1bd2e84b67f9 Successfully built 1bd2e84b67f9 Successfully tagged linuxea:23而后我运行这个镜像,并且将不能查看的文件挂在到容器中并运行[linuxea@linuxea.com ~]$ docker run --rm -v /root/test.file:/opt/test.file linuxea:23 www.linuxea.com即使我是linuxea,容器正在运行root,因此可以访问root此服务器上的所有内容。这不理想; 以这种方式运行容器意味着你从Docker Hub中提取的每个容器都可以完全访问服务器上的所有内容(具体取决于你运行它的方式)。建议这里的建议是创建一个uid Dockerfile中已知的用户,并以该用户身份运行应用程序进程。Dockerfile的开头应遵循以下模式:FROM <centos:-基本镜像> RUN groupadd -g 999 USER && \ useradd -r -u 999 -g USER USER USER USER ... <Dockerfile的其余部分> ...使用此模式,可以在具有所需权限最少的用户/组的上下文中轻松运行容器。例如,我将从上面将它添加到我的Dockerfile并重新运行该示例。我的Dockerfile现在看起来像这样:FROM alpine:latest MAINTAINER www.linuxea.com mark RUN addgroup www && adduser -u 1001 -S -H -s /sbin/nologin -g 'nginx' -G www www USER www ENTRYPOINT ["cat","/opt/test.file"][linuxea@linuxea.com ~]$ docker build -t linuxea:24 . Sending build context to Docker daemon 10.24kB Step 1/5 : FROM alpine:latest ---> caf27325b298 Step 2/5 : MAINTAINER www.linuxea.com mark ---> Using cache ---> c9603327aeb5 Step 3/5 : RUN addgroup www && adduser -u 1001 -S -H -s /sbin/nologin -g 'nginx' -G www www ---> Running in 09d9fc94586a Removing intermediate container 09d9fc94586a ---> 85d82a2b1c7f Step 4/5 : USER www ---> Running in 9dd67807ba0e Removing intermediate container 9dd67807ba0e ---> c56e5dc43a7f Step 5/5 : ENTRYPOINT ["cat","/opt/test.file"] ---> Running in 31007cdf4293 Removing intermediate container 31007cdf4293 ---> e6dfe1c64d2c Successfully built e6dfe1c64d2c Successfully tagged linuxea:24使用与以前相同的命令运行此容器:[linuxea@linuxea.com ~]$ docker run --rm -v /root/test.file:/opt/test.file linuxea:24 cat: can't open '/opt/test.file': Permission denied重复使用其他图像Docker的镜像是很好用的,因为它们是可重用的。但是当FROM的镜像以非root身份运行时,你的容器将继承该非root用户。如果你需要创建自己的或以root身份执行操作,请确保USER root在Dockerfile顶部附近的某处。然后FROM User再次使其可用。FROM alpine:latest MAINTAINER www.linuxea.com mark USER root RUN make install 《root权限需要》 RUN addgroup www && adduser -u 1001 -S -H -s /sbin/nologin -g 'nginx' -G www www USER www ENTRYPOINT ["cat","/opt/test.file"]以非root用户身份运行其他容器Docker镜像设计为可移植的,从Docker Hub中提取其他镜像是正常的。其中一些(官方镜像)将遵循此最佳实践并作为普通用户帐户运行。但许多镜像都没有这样做。许多镜像只是运行root,并由你决定如何安全地运行它们。有几个选项可以让你安全地运行没有创建自己用户的镜像。创建另一个图像首先,一个选项是使用原始镜像作为FROM图层创建另一个镜像。然后,您可以创建用户帐户,并将原始ENTRYPOINT和CMD指令复制到自己的镜像。此结果镜像现在遵循此处概述的最佳实践,并且默认情况下将安全运行。这里的权衡是你需要在更新基本镜像时重建构建镜像。你必须在重建基本镜像时设置重建过程。你可以在原有的镜像之上修改,也可以从docker镜像复现Dockerfile进行重新编写。启动容器时指定uid最后,你可以在主机上创建用户,并uid在启动容器时将其传递给Docker。例如,重新访问原始示例Dockerfile:FROM alpine:latest MAINTAINER www.linuxea.com mark ENTRYPOINT ["cat","/opt/test.file"]我可以使用或不使用用户id参数运行此容器,并查看不同的结果(用户ID 1001是我自己在此服务器上的linuxea帐户):[linuxea@linuxea.com ~]$ docker run --rm --user=1001 -v /root/test.file:/opt/test.file linuxea:23 cat: can't open '/opt/test.file': Permission denied[linuxea@linuxea.com ~]$ docker run --rm -v /root/test.file:/opt/test.file linuxea:23 www.linuxea.com这与在Dockerfile中创建用户的工作方式相同,但它要求用户可选择安全地运行容器。在Dockerfile中指定非root用户将使容器默认安全运行。进一步了解linuxea:了解uid和gid如何在docker容器中工作linuxea:docker run的十个常用选项linuxea:十个初学Dcoker cli指令学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器docker-compose
2019年02月23日
8,072 阅读
0 评论
0 点赞
2019-02-22
linuxea:docker-compose设置静态ip和link与depends_on的区别
在容器中设置静态ip,这似乎又是一个过时的老话题,但是在讨论群中仍然有朋友为此感到困惑。我致力于解决这些小问题和在使用中容器落地的问题。为此,我又写了这篇文章来描述容器中使用静态ip,和不使用静态ip link的技巧。在正式配置docker-compose之前,我们需要先了解link,因为在我看来在容器中使用固定ip是件没有必要的事情,使用ip只是我们脑中长久的一个使用习惯。而在docker中link已经帮我们解决了这个麻烦事,并提供了更简单的方式。那么,通常来讲,在这个问题上产生疑问的,必然是在使用两个以上的容器。那就有必要了解depends_on。在使用link的同时,我当然也会叙述另外一个常用的选项depends_on,它非常有用。并且我会做简单的比较。阅读本篇文章,你将了解,docker-compose中3版本的使用,以及link使用方式和depends_on的技巧。容器间互联尽管,我们要解决的是单机网络,也不妨先简单介绍下跨主机和不跨主机容器间互联的区别不跨主机互相访问不跨主机互联可以采用默认的bridge网络,docker0桥在物理机上,而后创建的容器,容器内有eth0,另外一侧在物理机的docker0,而docker0可以理解成一个虚拟交换机。这样同一个交换机内的容器就可以直接进行互联。除此之外,还可以使用host网络模式,共用宿主机网络,这样一来容器和宿主机使用同一个网络,不隔离网络名称空间,网卡信息,就不存在网络上的问题。最后还可以采用联盟式容器解决网络问题跨主机容器互联容器跨主机访问实际上是做了DNAT,将容器发布出去,这些规则在iptables中可以看到Chain DOCKER (3 references) pkts bytes target prot opt in out source destination 0 0 ACCEPT tcp -- !br-77c0aabda308 br-77c0aabda308 0.0.0.0/0 172.18.0.2 tcp dpt:26379 0 0 ACCEPT tcp -- !br-77c0aabda308 br-77c0aabda308 0.0.0.0/0 172.18.0.2 tcp dpt:6379 0 0 ACCEPT tcp -- !br-77c0aabda308 br-77c0aabda308 0.0.0.0/0 172.18.0.3 tcp dpt:1194 0 0 ACCEPT tcp -- !br-77c0aabda308 br-77c0aabda308 0.0.0.0/0 172.18.0.3 tcp dpt:443而对于互相双方来讲,是看不到后面的容器的,访问的是通过端口转发到真正的容器端口上。如果你要跨主机访问,就不能使用容器的ip,只能使用宿主机的ip和容器映射的端口通过iptables转发访问。[root@linuxea.com ~]# telnet 172.25.50.250 6379 Trying 172.25.50.250... Connected to 172.25.50.250. Escape character is '^]'.当然,也有例外,如果是叠加的方式就不需要在物理机做端口映射,直接通过隧道访问对端ip和端口link连接我们了解到在容器网络中的分配ip是不固定的,倘若我在第一次使用的ip地址在后面使用中发生了改变,那可能不无法正常使用了!这显然并不是我们想要的。link就解决了这个问题。links似乎将会被弃用,因为它并不重要(不使用link仍然可以通过容器名称访问),我们主要来看这里的别名操作docker-compose如下:version: '3' services: redis: image: marksugar/redis:5.0.0 container_name: redis restart: always privileged: true environment: - REDIS_CONF=on - REQUIRE_PASS=OTdmOWI4ZTM4NTY1M2M4OTZh - MASTER_AUTH=OTdmOWI4ZTM4NTY1M2M4OTZh - MAXCLIENTS_NUM=600 - MAXMEMORY_SIZE=4096M volumes: - /etc/localtime:/etc/localtime:ro - /data/redis-data:/data/redis:Z - /data/logs:/data/logs ports: - '6379:6379' - '26379:26379' softether: image: marksugar/softether:4.27 links: - "redis:redisdb" container_name: softether4.27 restart: always ports: - '443:443' - '1194:1194'请注意,其中 links: - "redis:redisdb"softether和redis的容器ip地址分别是172.18.0.2和172.25.8.0.3,假设此时我们并不知道ip这里在softether中链接了redis,并设置了别名,redisdb。那也就是说我们可以使用redisdb来访问redis本身。[root@linuxea.com /opt/2019/net]# docker exec -it softether4.27 sh / # apk update fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz v3.8.2-56-g4d33ed061d [http://dl-cdn.alpinelinux.org/alpine/v3.8/main] v3.8.2-53-g53558ad6fc [http://dl-cdn.alpinelinux.org/alpine/v3.8/community] OK: 9559 distinct packages available / # apk add redis (1/1) Installing redis (4.0.11-r0) Executing redis-4.0.11-r0.pre-install Executing redis-4.0.11-r0.post-install Executing busybox-1.28.4-r0.trigger OK: 68 MiB in 35 packages / # redis-cli -a OTdmOWI4ZTM4NTY1M2M4OTZh -h redisdb info Warning: Using a password with '-a' option on the command line interface may not be safe. # Server redis_version:5.0.0 redis_git_sha1:00000000 redis_git_dirty:0 redis_build_id:a7a8d032c5a69a3f redis_mode:standalone os:Linux 4.18.12-1.el7.elrepo.x86_64 x86_64 arch_bits:64 multiplexing_api:epoll atomicvar_api:atomic-builtin gcc_version:6.4.0 process_id:10 run_id:c6162ba2b02d70c1defda6073f863af1ccb207a6 tcp_port:6379 uptime_in_seconds:263 uptime_in_days:0 hz:10 configured_hz:10 lru_clock:7302247这说明了什么?我们完全可以使用容器的名称来进行访问,并不需要使用ip地址来指定。因为ip会变,名称却不会变,这是因为容器的hosts/ # cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 172.18.0.3 d374cbffc3b0在hosts中,已经写了ip和容器id的对应关系。你完全可以使用容器名称来进行访问。tips你可以不使用别名,直接使用容器名称进行访问,你会看到redis容器的ip地址/ # ping redis PING redis (172.18.0.2): 56 data bytes 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.093 ms 64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.143 ms 64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.106 ms 64 bytes from 172.18.0.2: seq=3 ttl=64 time=0.152 ms因为在同一个网络内。depends_on我们在看另外一个场景,假如此刻softether依赖于redis,在启动的时候就需要读取redis或者写入,通常情况下,redis必然要先启动,redis启动,softether在启动。这才是正确的方式,如果softether先启动,而redis还没有就绪,程序必然会报错,甚至崩溃。此时depends_on就有了用武之地如上场景中那般,配置如下:redis: image: marksugar/redis:5.0.0 ... softether: image: marksugar/softether:4.27 ... depends_on: - redis这样,softether会在redis启动之后启动。如果你有多个依赖,就可以按照顺序往后写,比如mysqlredis: image: marksugar/redis:5.0.0 ... mysql: image: ... softether: image: marksugar/softether:4.27 ... depends_on: - redis - mysql这样的顺序就是,softether会等待,先启动redis,在启动mysql,依次启动才到softether。由此可见,depends_on和links完全是两个不同的东西。tips:我非常有必要提醒,启动和准备就绪是两个概念 ,启动并不意味着一定就启动完成,就像点击开机并不意味着马上就进入桌面。其中的就绪状态则是另外的问题。请参阅启动顺序策略。docker-compose 静态ip默认情况下,docker会为容器分配随机(某种......)IP地址。通过使用链接,您可以将条目添加到容器的hosts文件中,并使用其IP地址映射另一个容器的名称。这样您就不需要知道其IP地址,只需使用其名称即可通过网络访问它。这种通过容器的hosts文件继而使用容器名称进行访问,这似乎已经解决了一大半人的问题。但是,我们仍然可以使用静态ip。上面我提到,网络是会发生改变的,为了彻底解决这一点,我们将网关,子网都设置好。在上面的默认网络中使用的ip是172.18.0.0网段。现在,我们修改它version: '3.7' services: redis: networks: linuxea: ipv4_address: 172.2.0.10 ... softether: networks: linuxea: ipv4_address: 172.2.0.11 ... networks: linuxea: ipam: driver: default config: - subnet: 172.2.0.0/24docker-compose如下[root@linuxea.com /opt/2019/net]# cat docker-compose.yaml version: '3.7' services: redis: image: marksugar/redis:5.0.0 container_name: redis restart: always privileged: true environment: - REDIS_CONF=on - REQUIRE_PASS=OTdmOWI4ZTM4NTY1M2M4OTZh - MASTER_AUTH=OTdmOWI4ZTM4NTY1M2M4OTZh - MAXCLIENTS_NUM=600 - MAXMEMORY_SIZE=4096M volumes: - /etc/localtime:/etc/localtime:ro - /data/redis-data:/data/redis:Z - /data/logs:/data/logs ports: - '6379:6379' - '26379:26379' networks: linuxea: ipv4_address: 172.2.0.10 softether: image: marksugar/softether:4.27 links: - "redis:redisdb" container_name: softether4.27 restart: always ports: - '443:443' - '1194:1194' networks: linuxea: ipv4_address: 172.2.0.11 networks: linuxea: ipam: driver: default config: - subnet: 172.2.0.0/24现在,你就可以使用静态的ip进行访问[root@linuxea.com /opt/2019/net]# docker-compose -f ./docker-compose.yaml up -d Creating redis ... done Creating softether4.27 ... done[root@linuxea.com /opt/2019/net]# docker exec -it softether4.27 sh / # ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 293: eth0@if294: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP link/ether 02:42:ac:02:00:0b brd ff:ff:ff:ff:ff:ff inet 172.2.0.11/24 brd 172.2.0.255 scope global eth0 valid_lft forever preferred_lft forever安装redis-client/ # apk update fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz v3.8.2-56-g4d33ed061d [http://dl-cdn.alpinelinux.org/alpine/v3.8/main] v3.8.2-53-g53558ad6fc [http://dl-cdn.alpinelinux.org/alpine/v3.8/community] OK: 9559 distinct packages available / # apk add redis (1/1) Installing redis (4.0.11-r0) Executing redis-4.0.11-r0.pre-install Executing redis-4.0.11-r0.post-install Executing busybox-1.28.4-r0.trigger OK: 68 MiB in 35 packages通过静态ip链接/ # redis-cli -a OTdmOWI4ZTM4NTY1M2M4OTZh -h 172.2.0.10 Warning: Using a password with '-a' option on the command line interface may not be safe. 172.2.0.10:6379> info # Server redis_version:5.0.0 redis_git_sha1:00000000 redis_git_dirty:0 redis_build_id:a7a8d032c5a69a3f redis_mode:standalone os:Linux 4.18.12-1.el7.elrepo.x86_64 x86_64 arch_bits:64 multiplexing_api:epoll atomicvar_api:atomic-builtin gcc_version:6.4.0 process_id:10 run_id:cbf1e8eacc74da75c3dfcf797d104a8a2f95076e使用ipam可以为新网络定义特定的CIDR块,然后将每个容器连接到该网络,可以在该范围上指定其IP地址。现在,redis始终使用IP地址172.2.0.10运行,softether运行172.2.0.11,并且我能够在配置文件中对这些地址进行硬编码。延伸阅读linuxea:白话容器之虚拟化网络与容器网络(8)linuxea:白话容器之docker网络(9)学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器docker-compose
2019年02月22日
9,228 阅读
0 评论
1 点赞
2019-02-21
linuxea:docker不能忽视的.dockerignore用法
.dockerignore的文章或许对老司机来说是一个过时的话题,但是我已经写了很多关于dockers使用的文章,并且我在讨论群组里面发现有人对此.dockerignore并不清楚,在这种情况下,我有必要重新复述一次。查看本章节,你将了解.dockerignore的使用和注意事项。祝你愉快Docker镜像可以和普通应用一样运行在云服务上,为什么还要优化它们呢?事实证明使用.dockerignore有很多好处。它可以帮助减少Docker镜像大小,加速docker build并避免意外的秘密曝光(请继续阅读以了解我的意思)。要理解.dockerignore为何如此有效,你必须了解构建上下文。Docker build context该docker build命令用于构建新的Docker镜像。你可以将一个参数传递给build命令构建上下文(build context)。此后的叙述中,都称为“构建上下文”什么是build context?首先,Docker是一个客户端 - 服务器应用程序,它由Docker客户端和Docker服务器(也称为Docker 守护程序)组成。Docker客户端命令行工具与Docker服务器通信并要求它执行操作。其中一个是Docker build:构建一个新的Docker镜像。Docker可以在与客户端,远程计算机或虚拟机上运行,也可以是本地,远程甚至在某些云IaaS上运行。有哪些是重要的?为了创建一个新的Docker镜像,Docker服务器需要访问文件并从中创建Docker镜像。因此,你需要以某种方式将这些文件发送到Docker服务器。这些文件是Docker 构建不可缺少的部分。Docker客户端将所有构建上下文文件打包到tar存档中,并将此存档上传到Docker服务器。默认情况下,客户端将获取当前工作目录中的所有文件(和文件夹),并将它们用作构建上下文。它还可以接受已创建的tar存档或git存储库。在git存储库的情况下 ,客户端将克隆到临时文件夹中,并从中创建构建上下文存档。对Docker构建的影响你看到,运行该docker build命令的第一个输出行是:Sending build context to Docker daemon 4.608kB Step 1/5 : FROM alpine:3.9这应该让事情变得清晰。实际上,每次运行docker build命令时,Docker客户端都会创建一个新的构建上下文存档并将其发送到Docker服务器。因此,总是这样:创建存档,存储和网络流量以及延迟时间所需的时间。提示:如果你在Docker镜像中不需要它们,则不将文件添加到构建上下文中。dockerignore文件该.dockerignore文件是隐藏文件也是一个工具,可以帮助你定义你真正需要的Docker 构建上下文。使用此文件,你可以为这些文件和文件夹规则指定忽略规则和异常,这些规则和异常将不包含在构建上下文中,因此不会打包到存档中并上载到Docker服务器。使用.dockerignore的几个个理由原因#1:Docker图像大小你的系统由多个组件(或微服务)组成,每个组件都在Linux容器内运行。可能有数十或数百个服务甚至更多服务实例。比如:每个代码提交完成,这些服务实例就可以彼此独立地构建和部署。更重要的是,弹性基础设施意味着可以在系统中添加或删除新的计算节点,并且其微服务可以从一个节点移动到另一个节点,以支持规模或可用性要求。这意味着,你的Docker镜像将经常构建和传输。当你持续交付和微服务架构时,镜像大小和镜像构建时间都很重要。理由#2:无意识的秘密暴露不控制构建上下文,也可能导致意外暴露你的代码,提交历史记录和密钥(密钥和凭据)。如果你将文件复制到你docker 镜像与ADD .或COPY .命令,可能会无意地包括源文件,整个git历史(一个.git文件夹),机密文件(如.aws,.env,私钥),缓存和其他文件只进不出docker “构建背景”,这些最终也进入最终的Docker镜像。DockerHub上目前有多个Docker镜像,可以显示应用程序源代码,密码,密钥和证书(例如Twitter Vine)。原因#3:Docker构建 - 缓存失效一种常见的模式是使用如下指令将应用程序的整个代码库注入镜像:COPY . /usr/src/app在这种情况下,我们将整个 构建上下文复制到镜像中。了解每个Dockerfile命令生成一个新层也很重要。因此,如果整个构建上下文中包含的任何文件发生更改,则此更改将使COPY . /opt/myapp图层的构建缓存无效,并且将在下一个构建时生成新的镜像层。如果你的工作目录包含经常更新的文件(日志,测试结果,git历史记录,临时缓存文件等),那么你将为每次docker build运行重新生成此层。dockerignore语法该.dockerignore文件类似于gitignore该git工具使用的文件 。与.gitignore文件类似,它允许你为生成构建上下文时Docker客户端应忽略的文件和文件夹指定模式。虽然.dockerignore用于描述忽略模式的文件语法类似于.gitignore,但它并不相同。该.dockerignore模式匹配的语法是基于filepath.Match()和filepath.clean的功能,包括一些补充。如:Docker还支持一个**匹配任意数量目录(包括零)的特殊通配符字符串。例如,**/*.go将排除.go 在所有目录中找到的以该结尾的所有文件,包括构建上下文的根。以下是完整的语法.dockerignore:pattern: {term} 术语: '*' 匹配任何非分隔符字符序列 '?' 匹配任何单个非分隔符 '['['^'] {character-range}']' 字符类(必须是非空的) c匹配字符c (c!='*','?','\\','[') '\\' c匹配字符c 字符范围: c匹配字符c (c!='\\',' - ',']') '\\' c匹配字符c lo' - 'hi匹配字符c for lo&lt; = c&lt; = hi 补充: '**' 匹配任意数量的目录(包括零) '!' 行开头! (感叹号)可用于排除例外情况 以此字符开头的'#'行将被忽略:将其用于评论!用法可以组合起来用于高级规则示例#ignore除了README-secret.md以外的所有README*.md和旁边的所有markdown文件(md)格式的都不要 *.MD !README*.MD README-secret.md#ignore所有文件夹中的所有*.class文件,包括构建根目录 **/*.class#ignore .git和.cache文件夹 .git .cache# 排除名称以temp根目录的任何直接子目录开头的文件或者目录,如/somedir/tempfile.txt,目录/somedir/temp/ */temp*# temp从以下两级以下的任何子目录开始排除文件和目录。例如,/somedir/subdir/temporary.txt被排除在外。 */*/temp* # 排除根目录中的文件和目录,其名称是单字符扩展名temp。例如,/tempa与/tempb被排除在外 temp?演示我们先看当前的目录下的文件列表。我将会在其中:排除.git文件或者目录排除*.log文件,或者以*-log-*的文件排除*.tag.gz文件*.war文件,以及jenkins_home下的*.txt文件Dockerfile文件.dockerignore文件,包括所有的*.md文件,但是info.md除外。那么最终就剩下jenkins_home下的mark目录和info.md了。[root@linuxea.com /opt/2019/ignore]# tree -a . . ├── 0.log ├── 1.log ├── 9.log ├── Dockerfile ├── .dockerignore ├── .git ├── info.md ├── jenkins-2.165.tar.gz ├── jenkins_home │ ├── 2019-02-21_10:15:47.txt │ └── mark ├── jenkins.war ├── link.md └── readme.md 2 directories, 12 files.dockerignore文件完成预期[root@linuxea.com /opt/2019/ignore]# cat .dockerignore .git **/*.log **/*-log-* **/*.tar.gz **/*.war jenkins_home/*.txt Dockerfile .dockerignore *.md !info.mdDockerfileFROM alpine:3.9 MAINTAINER www.linuxea.com mark WORKDIR /opt/ COPY . /opt/ CMD ["sleep","1000000"]Build[root@linuxea.com /opt/2019/ignore]# docker build -t ignore . Sending build context to Docker daemon 4.608kB Step 1/5 : FROM alpine:3.9 ---> caf27325b298 Step 2/5 : MAINTAINER www.linuxea.com mark ---> Using cache ---> c9603327aeb5 Step 3/5 : WORKDIR /opt/ ---> Using cache ---> 109238b7358f Step 4/5 : COPY . /opt/ ---> ce0c655d1371 Step 5/5 : CMD ["sleep","1000000"] ---> Running in b1e1bdc833bf Removing intermediate container b1e1bdc833bf ---> 012de3caad48 Successfully built 012de3caad48 Successfully tagged ignore:latest验证[root@linuxea.com /opt/2019/ignore]# docker run --rm -it ignore sh /opt # ls -a . .. info.md jenkins_home /opt # ls -a jenkins_home/ . .. mark /opt # ls -a jenkins_home/mark/ . .. /opt # 学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器docker-compose
2019年02月21日
7,247 阅读
0 评论
0 点赞
2019-02-20
linuxea:了解uid和gid如何在docker容器中工作
了解用户名,组名,用户ID(uid)和组ID(gid)如何在容器内运行的进程与主机系统之间进行映射对于构建安全系统非常重要。如果没有提供任何其他选项,容器中的进程将以root身份执行(除非在Dockerfile中提供了不同的uid)。本文将解释这是如何工作的,如何正确授予权限并显示示例来说明。逐步分析uid/gid安全性首先,让我们回顾一下如何实现uid和gid。linux内核负责管理uid和gid空间,它的内核级系统调用用于确定是否应该授予所请求的权限。例如,当进程尝试写入文件时,内核会检查创建该进程的uid和gid,以确定它是否具有足够的权限来修改该文件。这里不使用用户名,使用uid。在服务器上运行Docker容器时,仍然只有一个内核。容器化带来的巨大价值在于所有这些独立的进程可以继续共享单个内核。这意味着即使在运行Docker容器的服务器上,整个uids和gids也由单个内核控制。所以你不能让不同的用户在不同的容器中使用相同的uid。这是因为在常见的linux工具中显示的用户名(和组名)不是内核的一部分,而是由外部工具(/etc/passwd,LDAP,Kerberos等)管理。因此,你可能会看到不同的用户名,但即使在不同的容器内,你也无法为同一个uid/gid拥有不同的权限。这一开始看起来很混乱,所以让我们用几个例子说明一下:简单的Docker运行我将首先以docker组中的普通用户(linuxea)身份登录服务器。这允许我在不使用sudo命令的情况下启动docker容器。然后,从容器外部,让我们看看这个过程是如何出现的。[root@www-linuxea-com ~]# useradd linuxea -G docker [linuxea@www-linuxea-com ~]# su - linuxea[linuxea@www-linuxea-com ~]$ docker run -d alpine:3.9 sleep 10000 e18b6e93b21a30f095ca0c6cbfbd0762dcbc220b3c057a7233c5371fe4f40c47[linuxea@www-linuxea-com ~]$ ps aux|grep sleep root 7145 0.5 0.0 1520 4 ? Ss 15:57 0:00 sleep 10000 linuxea 7203 0.0 0.1 112716 2192 pts/0 S+ 15:58 0:00 grep --color=auto sleep这很有趣。即使我从未输入sudo并且我不是root用户,我执行的sleep命令也以root用户身份启动并具有root权限。我怎么知道它有root权限?容器内的root = =容器外的root吗?是的,因为,正如我所提到的,有一个内核和一个共享的uids和gids池。因为用户名在容器外部显示为“root”,所以我可以肯定地知道容器内的进程是由具有uid = 0的用户启动的。定义用户的Dockerfile当我在Dockerfile中创建不同的用户并以该用户身份启动命令时会发生什么?为了简化这个例子,我没有在这里指定一个gid,但同样的概念适用于组ID。首先,我将这些命令作为用户“linuxea”运行,其uid为1001。[linuxea@www-linuxea-com ~]$ echo $UID 1001Dockerfile[linuxea@www-linuxea-com ~]$ cat Dockerfile FROM alpine:latest MAINTAINER www.linuxea.com mark RUN addgroup www && adduser -u 1001 -S -H -s /sbin/nologin -g 'nginx' -G www www USER www ENTRYPOINT ["sleep","30000"]让我们构建并运行它:[linuxea@www-linuxea-com ~]$ docker build -t test . Sending build context to Docker daemon 6.656kB Step 1/5 : FROM alpine:latest ---> caf27325b298 Step 2/5 : MAINTAINER www.linuxea.com mark ---> Using cache ---> 97c60ecc6438 Step 3/5 : RUN addgroup www && adduser -u 1001 -S -H -s /sbin/nologin -g 'nginx' -G www www ---> Running in dd3eaaa2d820 Removing intermediate container dd3eaaa2d820 ---> 1a81d642cab6 Step 4/5 : USER www ---> Running in fedfcda0d798 Removing intermediate container fedfcda0d798 ---> fc564057218b Step 5/5 : ENTRYPOINT ["sleep","30000"] ---> Running in a87dfbe5edd8 Removing intermediate container a87dfbe5edd8 ---> 0b28fd7c7ad1 Successfully built 0b28fd7c7ad1 Successfully tagged test:latest[linuxea@www-linuxea-com ~]$ docker run -d test 06494798ce210032582e21a2b3f990fcdfe3e9623402c41bd78f4c22fa5ce518[linuxea@www-linuxea-com ~]$ ps aux|grep sleep linuxea 10652 0.1 0.0 1520 4 ? Ss 16:42 0:00 sleep 30000 linuxea 10716 0.0 0.1 112716 2144 pts/0 S+ 16:42 0:00 grep --color=auto sleep[linuxea@www-linuxea-com ~]$ docker exec -i 0649 ps aux|grep sleep 1 www 0:00 sleep 30000?到底发生了什么,这表明了什么?我构建了一个Docker镜像,其用户名为“www”,该用户的定义uid为1001.在我的测试服务器上,我正在使用的帐户名为“linuxea”,它的uid为1001。我启动容器,sleep命令作为www执行,因为Dockerfile包含“ADDUSER www”行。但这实际上并没有使它作为www运行,它使它作为用户的uid运行,Docker镜像是已知www用户是自己的用户的。当我检查在容器外部运行的进程时,我看到它被映射到用户“linuxea”,但是在容器内部它被映射到用户“www”。这两个用户名只显示其执行上下文已知的用户名映射到1001。这不是非常重要的。但重要的是要知道在容器内部,用户“www”从容器外获取用户“linuxea”的权限和特权。在linux主机上向用户linuxea或uid 1001授予权限也将授予容器内www的这些权限。如何控制容器的访问权限另一种选择是运行docker容器并指定用户名或uid,以及运行时的组名或gid。再次使用上面的初始示例。[linuxea@www-linuxea-com ~]$ docker run -d --user 1001 alpine:3.9 sleep 30001 b197a816bdc0a71060331e2b5a6701432cce786161e2d9939dd3a50eabf0fa9b[linuxea@www-linuxea-com ~]$ ps aux|grep sleep linuxea 12071 0.7 0.0 1520 4 ? Ss 17:01 0:00 sleep 30001 linuxea 12131 0.0 0.1 112716 2216 pts/0 S+ 17:01 0:00 grep --color=auto sleep我在这做什么?我创建了容器以作为1001用户启动。因此,当我执行诸如ps或top(或大多数监视工具)之类的命令时,该过程将映射到“linuxea”用户。有趣的是,当我执行到该容器时,你可以看到1001用户在/etc/passwd文件中没有条目,并在容器的bash提示符中显示为“I have no name!”。现在我使用ubuntu[linuxea@www-linuxea-com ~]$ docker run -d --user 1001 ubuntu:19.04 sleep 30001 a658a0a5b49db14831d83b792413dc0cc08112877c64ffb2006c4c0e716a83f4[linuxea@www-linuxea-com ~]$ docker exec -it a65 bash I have no name!@a658a0a5b49d:/$ 需要注意的是,在创建容器时指定用户标志也会覆盖Dockerfile中的该值。还记得第二个例子,我使用的Dockerfile有一个映射到本地主机上不同用户名的uid吗?当我们在命令行上使用用户标志运行它以启动执行“sleep 30000”过程的容器时会发生什么?[linuxea@www-linuxea-com ~]$ docker run -d test 25a0cdc509b2d56185fd42046249d2b53c3a84e393f76c1ca304e552b5ab1058 [linuxea@www-linuxea-com ~]$ ps aux|grep sleep linuxea 14157 0.6 0.0 1520 4 ? Ss 17:18 0:00 sleep 30000 linuxea 14215 0.0 0.1 112716 2144 pts/1 S+ 17:18 0:00 grep --color=auto sleep[linuxea@www-linuxea-com ~]$ docker run -d --user 0 test 5dc20cacd8d8c075c3e0ba910a72b36c16b851ef9a7ef56a4d90a2c97d148dc6 [linuxea@www-linuxea-com ~]$ ps aux|grep sleep linuxea 14157 0.1 0.0 1520 4 ? Ss 17:18 0:00 sleep 30000 root 14275 3.0 0.0 1520 4 ? Ss 17:18 0:00 sleep 30000 linuxea 14330 0.0 0.1 112716 2216 pts/1 S+ 17:18 0:00 grep --color=auto sleep在上面的最后一个例子中,你可以看到我最终有2个容器运行睡眠过程,一个用作“linuxea”,另一个用作“root”。这是因为第二个命令通过--user在命令行上传递标志来更改uid 。权限测试为了更好的验证这些,我们来测试一下。众所周知,普通用户是无法删除root用户创建的文件的[linuxea@www-linuxea-com /opt]$ \rm -rf 2 rm: cannot remove ‘2’: Permission denied如果此时,我们将这些文件挂载到容器内,请注意,我通过不同的用户ID挂载后,进行删除user=0[linuxea@www-linuxea-com ~]$ docker run -d --user=0 -v /opt:/opt test[linuxea@www-linuxea-com ~]$ docker exec -it modest_newton sh / # cd /opt/ /opt # ls 1 2 2019 /opt # \rm -rf 1 /opt # ls 2 2019 /opt # 使用--user=0后进行挂载,可以顺利删除root创建的目录user=1001[linuxea@www-linuxea-com /opt]$ docker run -d --user=1001 -v /opt:/opt test a4663d98c6e89f5c4af0f34516f0c4efe48a6fa881af881da5d8fd4029d9ccf8[linuxea@www-linuxea-com /opt]$ docker exec -it a46 sh / $ cd /opt/ /opt $ ls 2 2019 /opt $ ls -l total 4 -rw-r--r-- 1 root root 3784 Feb 15 06:44 2 drwxr-xr-x 4 root root 174 Feb 19 02:35 2019 /opt $ \rm -rf 2 rm: can't remove '2': Permission denied显而易见,单单从上述来看,普通用户似乎更为安全为了改善这一点,我们可以使用券权限来解决,仍然使用--user=0,挂在的/opt权限是ro,这样就不能被删除了。如下:[linuxea@www-linuxea-com ~]$ docker run -d --user=0 -v /opt:/opt:ro test ac8cc54d81dec6b528519c85f845c15c6b8515ab2777dff6d0a51cae59350ad2 [linuxea@www-linuxea-com ~]$ docker exec -it ac8 sh / # cd /opt/ /opt # ls 2 2019 /opt # \rm -rf 2 rm: can't remove '2': Read-only file system /opt # ls -ll total 4 -rw-r--r-- 1 www root 3784 Feb 15 06:44 2 drwxr-xr-x 4 root root 174 Feb 19 02:35 2019 /opt # \rm -rf 2019 rm: can't remove '2019/1.sh': Read-only file system rm: can't remove '2019/go1.11.4.linux-amd64.tar.gz': Read-only file system rm: can't remove '2019/pipe.go': Read-only file system rm: can't remove '2019/telnet.go': Read-only file system rm: can't remove '2019/telnet.sh': Read-only file system但是普通用户1001赋予rw,仍然无法删除[linuxea@www-linuxea-com /opt]$ docker run -d --user=1001 -v /opt:/opt:rw test a7cc54fd596df7b9f226d2633cd5fdffa5b6784469a2f6b37e944cdac0633db5 [linuxea@www-linuxea-com /opt]$ docker exec -it a7cc sh / $ cd /opt /opt $ ls 2 2019 /opt $ ls -ll total 4 -rw-r--r-- 1 www root 3784 Feb 15 06:44 2 drwxr-xr-x 4 root root 174 Feb 19 02:35 2019 /opt $ \rm -rf 2 rm: can't remove '2': Permission denied /opt $ \rm -rf 2019 rm: can't remove '2019/1.sh': Permission denied rm: can't remove '2019/go1.11.4.linux-amd64.tar.gz': Permission denied rm: can't remove '2019/pipe.go': Permission denied rm: can't remove '2019/telnet.go': Permission denied另外,我们还需要知道一点,尽管是root用户user=0,但是与宿主机的root用户权限还是有与别的,突破这一点,我们需要使用特权模式--privileged/opt # ls -ll /dev total 0 lrwxrwxrwx 1 root root 11 Feb 20 10:01 core -> /proc/kcore lrwxrwxrwx 1 root root 13 Feb 20 10:01 fd -> /proc/self/fd crw-rw-rw- 1 root root 1, 7 Feb 20 10:01 full drwxrwxrwt 2 root root 40 Feb 20 10:01 mqueue crw-rw-rw- 1 root root 1, 3 Feb 20 10:01 null lrwxrwxrwx 1 root root 8 Feb 20 10:01 ptmx -> pts/ptmx drwxr-xr-x 2 root root 0 Feb 20 10:01 pts crw-rw-rw- 1 root root 1, 8 Feb 20 10:01 random drwxrwxrwt 2 root root 40 Feb 20 10:01 shm lrwxrwxrwx 1 root root 15 Feb 20 10:01 stderr -> /proc/self/fd/2 lrwxrwxrwx 1 root root 15 Feb 20 10:01 stdin -> /proc/self/fd/0 lrwxrwxrwx 1 root root 15 Feb 20 10:01 stdout -> /proc/self/fd/1 crw-rw-rw- 1 root root 5, 0 Feb 20 10:01 tty crw-rw-rw- 1 root root 1, 9 Feb 20 10:01 urandom crw-rw-rw- 1 root root 1, 5 Feb 20 10:01 zero而宿主机[linuxea@www-linuxea-com /opt]$ ls /dev autofs cuse fuse lightnvm network_latency raw tty tty16 tty24 tty32 tty40 tty49 tty57 tty8 block disk hidraw0 log network_throughput rtc tty0 tty17 tty25 tty33 tty41 tty5 tty58 tty9 btrfs-control dm-0 hpet loop-control null rtc0 tty1 tty18 tty26 tty34 tty42 tty50 tty59 ttyS0 bus dm-1 hugepages mapper nvram shm tty10 tty19 tty27 tty35 tty43 tty51 tty6 ttyS1 char dm-2 hwrng mcelog port snapshot tty11 tty2 tty28 tty36 tty44 tty52 tty60 ttyS2 console dri initctl mem ppp snd tty12 tty20 tty29 tty37 tty45 tty53 tty61 ttyS3 core fb0 input memory_bandwidth ptmx stderr tty13 tty21 tty3 tty38 tty46 tty54 tty62 uhid cpu fd kmsg mqueue pts stdin tty14 tty22 tty30 tty39 tty47 tty55 tty63 uinput cpu_dma_latency full LBVG net random stdout tty15 tty23 tty31 tty4 tty48 tty56 tty7 urando这意味着什么现在我们已经探讨了这一点,有意义的是,运行具有有限权限的容器的方法都利用来自主机的用户系统:如果有一个已知的uid,即容器内的进程正在执行的用户,它可能就像限制对主机系统的访问一样简单,以便容器中的uid具有有限的访问权限。更好的解决方案是使用已知的uid启动容器--user(你也可以使用用户名,但请记住,它只是从主机的用户名系统提供uid的更友好的方式),然后限制对主机上的uid的访问你决定容器将运行为。因为uid和用户名(以及gid和组名称)从容器映射到主机,指定一个容器进程运行的用户可以使进程看起来由容器内部和外部的不同用户拥有。注意这些测试我在Centos7服务器上运行这些测试。涉及的镜像有Ubuntu 19.04和alpine:3.9 。虽然可以在Docker for OSX上执行相同的命令和测试,但你会看到不同的结果,因为Docker for OSX实际上是在基于Alpine Linux的虚拟机上执行docker-engine,并且你执行的命令是在OSX上执行。学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器docker-compose
2019年02月20日
3,833 阅读
0 评论
0 点赞
2019-02-19
linuxea:docker多阶段构建Multi-Stage与Builder对比总结
此前,在早些时候,我发表过Distroless与多阶段构建,其中介绍了简单的多阶段构建方式。阅读本文,你将跟快的了解多阶段构建带来的便利以及使用方法。本文中主要介绍多阶段构建的方式,这种方式本身就可以节省一部分空间,对于如何缩减镜像大小的几种方式类文章总结将会在此后进行编写发布,那将是后面会发生的事情。我们暂且来看多阶段构建带给我们的便利性为什么镜像会变大?Docker就像一个版本控制系统。每次更改都会创建一个新图层。每次在Docker中运行新命令时,它都会创建一个新层。这就是为什么在Dockerfiles中你会看到链接的多个命令。下面的阶段代表一层:RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py \ && python get-pip.py \ && pip install awscli因此,让我们假设您的容器需要您下载源文件,并构建容器(通常采用静态编译语言)。您必须从容器中删除源文件,否则源将成为镜像的一部分,从而增加了大小。并且,您无法做到,rm -rf $source_files_directory因为它只会创建一个新图层。你怎么解决这个问题?我们将向您展示旧方式和更好的方式。旧的方式:build如果使用最早的叠加累计的方式进行构建,如果是这样,事实上你使用的是两个docker镜像一个镜像来执行构建。一个精简的镜像,用于传送第一个构建的结果,而不会在第一个镜像中产生构建链和工具的损失。您可能已经发现保持图层小的唯一方法是:在进入下一层之前不再需要清理工件。使用shell技巧和其他逻辑来保持层尽可能小,同时确保每个层只有前一层所需的工件。例如,以下是使用构建器模式的常见解决方案,用于调出Tomcat容器并在其上部署应用程序:在甚至于,将常用软件打包在一个镜像中,这些软件包括:git ,jdk,mvn等。这样一来,镜像大小无限扩大,出现问题,排查也不是很方便,如下实例FROM tomcat:9.0.16-jre8 MAINTAINER www.linuxea.com mark WORKDIR /usr/src/project RUN apt-get update \ && apt-get install -y git openjdk-8-jdk \ && git clone http://GITUSER:GITPASS@git.ds.com/mark/java.git \ && wget https://downloads.gradle.org/distributions/gradle-4.8.1-bin.zip \ && unzip gradle-4.8.1-bin.zip \ && PATH=$PATH:$PWD/gradle-4.8.1/bin \ && gradle prod \ && mv /usr/src/project/build/ROOT.war /usr/local/tomcat/webapps/ROOT.war \ && rm -rf /usr/src/project/java COPY entrypoint.sh / COPY tomcat_conf_prod/* /usr/local/tomcat/conf/ RUN chmod +x /entrypoint.sh首先,我们进入/usr/src/project目录(或者COPY指令将我们当前的代码添加到容器中)。然后我们需要安装openjdk和 grande,git克隆代码,编译它并构建ROOT.war。之后,我们将 ROOT.war文件移动到Tomcat目录中,然后我们进行一些最终配置,以使用entrypoint.sh脚本准备和启动容器。但是这样的结果可能并不是我们想要的,容器本身看起来很不错,但是该镜像的大小(934MB)很大,尽管我们做了一些挽救措施,删除克隆的代码,甚至于可以删除git,但是最大的问题是jdk,因为这个镜像本身的大小是464M。那么更好的办法,你可以使用alpine基础镜像包。我们继续往下看。更妥善的方法:Docker Multi-Stage自Docker版本17。05(2017年10月)起可用的Docker Multi-Stage将通过删除不再需要的库,依赖项,包等来减少容器的最终大小。该过程包括:将构建分成不同的阶段,只保留每个阶段的最终结果并将其移动到下一个阶段。在Dockerfile中使用多个FROM语句。每个FROM指令可以使用不同的基础,并且每个指令都开始构建的新阶段。有选择地将工件从一个阶段复制到另一个阶段。我们可以将最后一个Dockerfile片段转换为下一个:我们修改了方式,不在容器中克隆代码,而是复制进容器分段执行FROM openjdk:8 MAINTAINER www.linuxea.com mark COPY . /usr/src/project WORKDIR /usr/src/project RUN wget https://downloads.gradle.org/distributions/gradle-4.8.1-bin.zip \ && unzip gradle-4.8.1-bin.zip \ && PATH=$PATH:$PWD/gradle-4.8.1/bin \ && gradle prod FROM tomcat:9.0.16-jre8 MAINTAINER www.linuxea.com mark COPY --from=0 /usr/src/project/build/ROOT.war /usr/local/tomcat/webapps/ROOT.war COPY entrypoint.sh / COPY tomcat_conf_prod/* /usr/local/tomcat/conf/ RUN chmod +x /entrypoint.sh亦或者这样:FROM maven:3-jdk-11 MAINTAINER www.linuxea.com mark COPY linuxea /linuxea RUN mvn -f /linuxea/pom.xml clean package FROM marksugar/java:jdk1.8.0_131 MAINTAINER www.linuxea.com mark COPY --from=build /linuxea /linuxea EXPOSE 8086 CMD ["/linuxea/target/hello-world-0.0.6.jar"]Docker Multi-Stage与使用Builder模式相比如何?主要区别在于,使用Docker Multi-Stage,我们在同一个Dockerfile中构建了两个不同的镜像。第一个是基于openjdk我们用它来编译我们的代码并生成ROOT.war文件。当我们声明第二个基于Tomcat的镜像时,就会发生改变,我们使用--from=0指令将ROOT.war从第一个镜像复制到第二个镜像。这样做,我们放弃了Gradle(构建工具)用于编译我们的应用程序的所有依赖项,并且只保留最重要的东西,我们的ROOT.war文件。你是否在担心,这个--from=0的问题?事实上这个名称是可以进行命名的 ,如下示例:FROM golang:1.7.3 as builder WORKDIR /go/src/github.com/alexellis/href-counter/ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . FROM alpine:latest WORKDIR /root/ COPY --from=builder /go/src/github.com/alexellis/href-counter/app .总结不必局限于第一个镜像的大小,你可以将所有的操作都在里面运行,因为对于第一个镜像来讲,仅做加工,而后将输出输出到第二个镜像,而第二个镜像才是我们将会使用的镜像,而第二个镜像只有存在编译完成的包,仅用来进行run。从安全方面来讲,有一定的意义你也可以在本地编写脚本执行打包,编译,而后在添加到镜像中,某些时候,脚本来完成似乎更加方便,仅对脚本高手来讲(并不建议)。你的镜像也不一定要使用tomcat的官方包,你甚至可以用来修改成自定义的模样,如:alpine,更小的体积,在此之上安装附带一些能够更快,更高效前提下的操作等。并且你不需要拘于某一种形式来做,只需要快捷提供一个小巧的,满足条件的镜像即可。常用于ci/cd构建,在一些环境下,缓存对于构建有加速的作用学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器
2019年02月19日
2,938 阅读
0 评论
0 点赞
2019-02-18
linuxea:docker特权模式与--cap-add和--cap-drop
你可能希望在容器中执行的一些高级操作(例如Docker-in-Docker(DinD),NTP,安装环回设备等),默认情况下将需要比给予容器的root用户更高的权限。因此,需要允许其他特权才能使容器无问题地运行,因此对于该用例,Docker具有非常简单但非常宽泛的特权模式,可将完整主机的功能添加到容器中。要使用此模式,只是追加--privileged到docker run命令:Docker-in-Docker(通常称为DinD)是一种容器的特殊配置,允许你在已经在Docker引擎上运行但不共享Docker套接字的容器内运行Docker引擎,这允许(如果预防措施是在你的基础架构中构建已经容器化的容器的更安全和强大的方法。此配置的流行程度有点罕见,但在用作持续集成(CI)和持续交付(CD)设置的一部分时非常强大。没有额外特权的NTP守护进程,看看会发生什么[root@www.linuxea.com ~]# run -it \ --rm \ cguenther/ntpd ntpd: can't set priority: Permission denied reset adjtime failed: Operation not permitted creating new /var/db/ntpd.drift adjtimex failed: Operation not permitted adjtimex adjusted frequency by 0.000000ppm ntp engine ready reply from 139.99.97.88: offset -0.010032 delay 0.040738, next query 5s settimeofday: Operation not permitted reply from 210.23.25.77: offset -0.003515 delay 0.043521, next query 9s reply from 139.99.41.7: offset -0.017110 delay 0.044403, next query 9s reply from 139.99.97.88: offset -0.012315 delay 0.045440, next query 6s reply from 210.23.25.77: offset -0.008560 delay 0.053329, next query 6s reply from 139.99.97.88: offset -0.007878 delay 0.036450, next query 8s那现在我使用特权模式[root@www.linuxea.com ~]# docker run -it --rm --privileged cguenther/ntpd creating new /var/db/ntpd.drift adjtimex adjusted frequency by 0.000000ppm ntp engine ready reply from 128.199.87.57: offset -0.007699 delay 0.036460, next query 9s set local clock to Mon Feb 18 08:10:32 UTC 2019 (offset -0.007699s) reply from 210.23.25.77: offset 0.005943 delay 0.029169, next query 8s reply from 183.177.72.201: offset 0.018927 delay 0.052242, next query 7s reply from 183.177.72.201: offset 0.020719 delay 0.066146, next query 8s reply from 210.23.25.77: offset 0.000334 delay 0.056709, next query 9s如你所见,添加此标志会从输出中删除所有错误,因为我们现在可以更改系统时间。通过解释此模式的功能,我们现在可以讨论为什么理想情况下,如果可能,你永远不应该使用特权模式。默认情况下,特权模式允许几乎完全访问大多数主机系统,并且在大多数情况下都不够精确,因此在你发现容器需要其他权限后,你应该选择性地添加它们--cap-add。这些标志是标准的Linux功能标识符,你可以在http://man7.org/linux/man-pages/man7/capabilities.7.html等位置找到它们,并允许微调到你想要的访问级别。如果我们现在将我们之前的NTP守护程序示例转换为这种新样式,它应该看起来更像这样:[root@www.linuxea.com ~]# docker run -it --rm cguenther/ntpd ntpd: can't set priority: Permission denied reset adjtime failed: Operation not permitted creating new /var/db/ntpd.drift adjtimex failed: Operation not permitted adjtimex adjusted frequency by 0.000000ppm ntp engine ready现在增加了SYS_TIME功能[root@www.linuxea.com ~]# docker run -it --rm --cap-add SYS_TIME cguenther/ntpd ntpd: can't set priority: Permission denied creating new /var/db/ntpd.drift adjtimex adjusted frequency by 0.000000ppm ntp engine ready reply from 209.58.172.140: offset 0.028803 delay 0.044228, next query 5s set local clock to Mon Feb 18 08:29:25 UTC 2019 (offset 0.028803s) reply from 202.73.57.107: offset -0.027237 delay 0.113698, next query 7s reply from 103.114.160.109: offset 0.000547 delay 0.210185, next query 8s如果你注意到,由于另一个缺少功能,我们仍然可以看到错误,但settimeofday 错误 消失了,这是我们需要修复此容器运行的最重要问题。有趣的是,--cap-drop如果我们想要提高安全性,我们也可以从我们的容器中删除未使用的功能。对于此标志,还有一个特殊关键字,ALL可用于删除所有可用权限。如果我们使用它来完全锁定我们的NTP容器,但一切正常,让我们看看它会是什么样子:[root@www.linuxea.com ~]# docker run -it \ > --rm \ > --cap-drop ALL \ > --cap-add SYS_TIME \ > --cap-add SYS_CHROOT \ > --cap-add SETUID \ > --cap-add SETGID \ > --cap-add SYS_NICE \ > cguenther/ntpd creating new /var/db/ntpd.drift adjtimex adjusted frequency by 0.000000ppm ntp engine ready reply from 139.162.12.72: offset -0.013927 delay 0.036491, next query 7s set local clock to Mon Feb 18 08:33:24 UTC 2019 (offset -0.013927s) reply from 209.58.172.140: offset 0.021554 delay 0.030414, next query 5s reply from 183.177.72.202: offset 0.006347 delay 0.043784, next query 7s reply from 209.58.172.140: offset 0.029093 delay 0.043691, next query 8s reply from 139.162.12.72: offset 0.000467 delay 0.037517, next query 9s reply from 183.177.72.202: offset 0.013560 delay 0.059300, next query 5s reply from 183.177.72.202: offset 0.014058 delay 0.057387, next query 5s reply from 209.58.172.140: offset 0.028728 delay 0.045038, next query 5s reply from 139.162.12.72: offset 0.000349 delay 0.038627, next query 5s peer 183.177.72.202 now valid reply from 183.177.72.202: offset 0.011139 delay 0.063721, next query 5s peer 209.58.172.140 now valid reply from 209.58.172.140: offset 0.029599 delay 0.043637, next query 9s在这里,我们首先删除了所有功能,然后添加了运行容器所需的几个功能,正如你所看到的,情况正常。在你自己的部署中,我强烈建议如果你有备用开发容量或面向安全性,请花一些时间以这种方式锁定正在运行的容器,因为它们会更加安全,你将更加确信容器以最小特权原则运行。在最小权限原则是在计算机安全,你只允许运行到用户或服务组件所需的最低权限的概念。这个原则是高安全性实现的主要原因,但由于管理访问的假设开销,往往在其他地方找不到,即使它是提高系统安全性和稳定性的好方法。如果你想了解有关此概念的更多信息,请查看最小特权原则。参考: https://docs.docker.com/engine/reference/run/学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器docker-compose
2019年02月18日
8,313 阅读
0 评论
0 点赞
2019-02-16
linuxea:如何利用docker构建缓存快速迭代?
在本文中,你将学习如何使用docker 构建缓存。CachingDocker的优势之一是它提供了缓存,可帮助你更快地迭代镜像构建。构建映像时,Docker会逐步执行Dockerfile中的指令,按顺序执行每个指令。在检查每条指令时,Docker在其缓存中查找现有的中间镜像,它可以重复使用而不是创建新的(重复的)中间镜像。如果缓存无效,则使其无效的指令和所有后续Dockerfile指令都会生成新的中间映像。一旦缓存失效,就可以使用Dockerfile中的其余指令。所以从Dockerfile的顶部开始,如果基本映像已经在缓存中,则重用它。否则,缓存无效。然后将下一条指令与从该基本镜像导出的高速缓存中的所有子镜像进行比较。比较每个缓存的中间镜像以查看指令是否找到缓存命中。如果是高速缓存未命中,则高速缓存无效。重复相同的过程,直到达到Dockerfile的末尾。大多数新指令只是与中间镜像中的指令进行比较。如果匹配,则使用缓存副本。例如,当RUN pip install -r requirements.txt在Dockerfile中找到指令时,Docker会在其本地缓存的中间镜像中搜索相同的指令。旧的和新的requirements.txt文件的内容不进行比较。如果使用新软件包更新requirements.txt文件并使用RUN pip install并希望使用新软件包名称重新运行软件包安装,则此行为可能会出现问题。我马上就会展示一些解决方案。与其他Docker指令不同,ADD和COPY指令确实需要Docker查看文件的内容以确定是否存在缓存命中。将引用文件的校验和与现有中间镜像中的校验和进行比较,在这些校验和中不考虑文件的最后修改时间和最后访问时间。如果文件内容或元数据已更改,则缓存无效。除了ADD和COPY命令之外,缓存检查不会查看容器中的文件来确定缓存匹配。例如,在处理RUN apt-get -y update命令时,不检查容器中更新的文件以确定是否存在缓存命中。在这种情况下,只需使用命令字符串本身来查找匹配项。以下是有效使用缓存的一些提示。缓存可以通过将关闭--no-cache=True与docker build。如果你要对指令进行更改,则后续的每个层都将经常重建。要利用缓存,请将可能更改的指令放在Dockerfile中尽可能低的位置。Chain RUN apt-get update和apt-get install命令,以避免缓存未命中问题。如果你正在使用包装安装程序(如pip和requirements.txt文件),请按照下面的模型进行操作,以确保你没有收到带有requirements.txt中列出的旧包的陈旧中间映像。COPY requirements.txt /tmp/ RUN pip install -r /tmp/requirements.txt COPY . /tmp/一旦高速缓存失效,所有后续Dockerfile命令都会生成新图像,并且不使用高速缓存。如何利用缓存?如何利用缓存来快速的构建?为了演示这一点,我们构建一个镜像,Dockerfile如下:[root@www.linuxea.com /opt/2019/none]# cat Dockerfile FROM alpine:3.9 MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN apk update \ && apk add gcc RUN echo "hello www.linuxea.com see you!" >> /linuxea.txt其中,在update后安装了gcc,并且echo "hello www.linuxea.com see you!" >> /linuxea.txt中,而后开始build[root@www.linuxea.com /opt/2019/none]# docker build -t linuxea.com:t0.1 . Sending build context to Docker daemon 5.12kB Step 1/5 : FROM alpine:3.9 ---> caf27325b298 Step 2/5 : MAINTAINER www.linuxea.com ---> Using cache ---> 5ee27f5e579a Step 3/5 : LABEL maintainer="www.linuxea.com" ---> Using cache ---> a9b2f826f6c9 Step 4/5 : RUN apk update && apk add gcc ---> Running in 79fc02b7c52d fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz v3.9.0-28-g04484bcf48 [http://dl-cdn.alpinelinux.org/alpine/v3.9/main] v3.9.0-27-g022fc53172 [http://dl-cdn.alpinelinux.org/alpine/v3.9/community] OK: 9751 distinct packages available (1/10) Installing binutils (2.31.1-r2) (2/10) Installing gmp (6.1.2-r1) (3/10) Installing isl (0.18-r0) (4/10) Installing libgomp (8.2.0-r2) (5/10) Installing libatomic (8.2.0-r2) (6/10) Installing libgcc (8.2.0-r2) (7/10) Installing mpfr3 (3.1.5-r1) (8/10) Installing mpc1 (1.0.3-r1) (9/10) Installing libstdc++ (8.2.0-r2) (10/10) Installing gcc (8.2.0-r2) Executing busybox-1.29.3-r10.trigger OK: 92 MiB in 24 packages Removing intermediate container 79fc02b7c52d ---> 7a01adbc2a78 Step 5/5 : RUN echo "hello www.linuxea.com see you!" >> /linuxea.txt ---> Running in 52ada0971e54 Removing intermediate container 52ada0971e54 ---> b17ef9b4b4be Successfully built b17ef9b4b4be Successfully tagged linuxea.com:t0.1那么现在就有一个linuxea.com:t0.1的镜像[root@www.linuxea.com /opt/2019/none]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE linuxea.com t0.1 b17ef9b4b4be 2 minutes ago 95.6MB此后,我们在修改了Dockerfile,添加了RUN echo "hello www.linuxea.com see you two!" >> /linuxea.txt[root@www.linuxea.com /opt/2019/none]# cat Dockerfile FROM alpine:3.9 MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN apk update \ && apk add gcc RUN echo "hello www.linuxea.com see you!" >> /linuxea.txt RUN echo "hello www.linuxea.com see you two!" >> /linuxea.txt重新build[root@www.linuxea.com /opt/2019/none]# docker build -t linuxea.com:t0.2 . Sending build context to Docker daemon 5.12kB Step 1/6 : FROM alpine:3.9 ---> caf27325b298 Step 2/6 : MAINTAINER www.linuxea.com ---> Using cache ---> 5ee27f5e579a Step 3/6 : LABEL maintainer="www.linuxea.com" ---> Using cache ---> a9b2f826f6c9 Step 4/6 : RUN apk update && apk add gcc ---> Using cache ---> 7a01adbc2a78 Step 5/6 : RUN echo "hello www.linuxea.com see you!" >> /linuxea.txt ---> Using cache ---> b17ef9b4b4be Step 6/6 : RUN echo "hello www.linuxea.com see you two!" >> /linuxea.txt ---> Running in fe4dc61a4e1b Removing intermediate container fe4dc61a4e1b ---> 4dbddcf438d6 Successfully built 4dbddcf438d6 Successfully tagged linuxea.com:t0.2你看到了什么?在第二次build的时候,并没有看到RUN apk update && apk add gcc的安装过程,而是使用了此前创建的缓存中间层,如下:学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器docker-compose
2019年02月16日
2,520 阅读
0 评论
0 点赞
2019-02-15
linuxea:什么是docker <none><none> image(镜像)?
我们在使用docker build的过程中经常会遇到<none><none>这类的images状态,那么这篇文章主要来简单的解释它是如何产生的,以及它们的影响,我将尝试解释以下几点:1,什么是<none><none>2,为什么会存在<none><none>3,docker images与docker images -a中<none><none>区别以及删除容器出现Error response from daemon: conflict: unable to delete caf27325b298 (cannot be forced) - image has dependent child images是怎么 回事要理解这一点,我们下载一个alpine:3.9的镜像来做示例,重现上述提到的这几种<none>状态。我先尝试解释,docker images -a中的<none><none>docker images -a中的<none><none>编写一个Dockerfile,假设它如下所示:FROM alpine:3.9 MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN echo "hello www.linuxea.com cool!" > /linuxea.txt如上,从Step 1/4到Step 4/4经历了四个阶段,每一个阶段分别有一个标识,分别是:caf27325b298,932697f3313a,21b1f26af8b8,e7367313b8e2如下图:Step 1/4 : FROM alpine:3.9 3.9: Pulling from library/alpine 6c40cc604d8e: Pull complete ---> caf27325b298 Step 2/4 : MAINTAINER www.linuxea.com ---> Running in 0b51600da82c Removing intermediate container 0b51600da82c ---> 932697f3313a Step 3/4 : LABEL maintainer="www.linuxea.com" ---> Running in 6f376b8eeb14 Removing intermediate container 6f376b8eeb14 ---> 21b1f26af8b8 Step 4/4 : RUN echo "hello www.linuxea.com cool!" > /linuxea.txt ---> Running in d5d720c2e07e Removing intermediate container d5d720c2e07e ---> e7367313b8e2这些只读层,从上往下执行,第一个执行完成,执行第二个,一个接一个地运行Dockerfile中的指令,每个层都代表一个Dockerfile指令。这些层是堆叠的,每个层都是前一层变化的增量。在必要时将每条指令的结果提交给新镜像层,最后输出新镜像层的ID。Docker守护程序将自动清理发送的上下文。每条指令创建一个层:FROM alpine:3.9 MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN echo "hello www.linuxea.com cool!" > /linuxea.txtFROM从alpine:3.9Docker镜像创建一个图层。MAINTAINER 添加info信息LABE 添加标签RUN 指定在容器中运行的echo命令。请注意,每条指令都是独立运行的,会导致创建新镜像层 - 因此 echo "hello www.linuxea.com cool!" > /linuxea.txt不会对下一条指令产生任何影响。只要有可能,Docker将重新使用中间图像(缓存),以docker build显着加速该过程。这由Using cache控制台输出中的消息指示。(有关详细信息,请参阅构建高速缓存段的 Dockerfile最佳实践指南)这其中最有意思的还是--cache-from,缓存重用但我们了解上述的表达后,我们使用docker history在看镜像的中间层[root@linuxea.com /opt/2019/none]# docker history linuxea.com:v0.1 IMAGE CREATED CREATED BY SIZE COMMENT e7367313b8e2 2 hours ago /bin/sh -c echo "hello www.linuxea.com cool!… 28B 21b1f26af8b8 2 hours ago /bin/sh -c #(nop) LABEL maintainer=www.linu… 0B 932697f3313a 2 hours ago /bin/sh -c #(nop) MAINTAINER www.linuxea.com 0B caf27325b298 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 2 weeks ago /bin/sh -c #(nop) ADD file:2a1fc9351afe35698… 5.53MB 而这些中间层在构建之后会存放在这里,供给给最上面的读写层。此时,如果使用docker images -a你会看到<none><none> ,这些<none><none>是镜像的中间层,只会在Build的时候产生在本地,并且不能被删除。如果此时,根据IMAGE ID来删除基础镜像便会提示Error response from daemon: conflict: unable to delete caf27325b298 (cannot be forced) - image has dependent child images正确的删除方式是通过镜像REPOSITORY:TAG进行删除依赖的镜像[root@linuxea.com /opt/2019/none]# docker rmi -f alpine:3.9 Untagged: alpine:3.9 Untagged: alpine@sha256:b3dbf31b77fd99d9c08f780ce6f5282aba076d70a513a8be859d8d3a4d0c92b8docker images <none><none>我们在来看第二种场景,docker images中的none我们准备三个Dockerfile,如下FROM alpine:3.9 MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN echo "hello www.linuxea.com cool!" > /linuxea.txtFROM alpine:3.9 MAINTAINER www.linuxea.com LABEL maintainer="www.linuxea.com" RUN echo "hello www.linuxea.com cool!" > /linuxea.txt RUN echo "hello www.linuxea.com good!" >> /linuxea.txtFROM alpine:3.9 MAINTAINER www.linuxea.com RUN echo "hello www.linuxea.com see you!" >> /linuxea.txt LABEL maintainer="www.linuxea.com" RUN echo "hello www.linuxea.com cool!" >> /linuxea.txt RUN echo "hello www.linuxea.com good!" >> /linuxea.txt第一个build命名linuxea.com:v0.1,第二个命名linuxea.com:v0.2,第三个build命名到linuxea.com:v0.3我们会得到三个镜像[root@DT_Node-172_25_50_250 /opt/2019/none]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE linuxea.com v0.3 9ba68f247813 5 seconds ago 5.53MB linuxea.com v0.2 f14e080bbad1 35 seconds ago 5.53MB linuxea.com v0.1 00a0fad355c1 53 seconds ago 5.53MB alpine 3.9 caf27325b298 2 weeks ago 5.53MB而后在将第三个Dockerfile build成linuxea.com:v0.2[root@linuxea.com /opt/2019/none]# docker build -t linuxea.com:v0.2 .此时就出现了一个<none> <none> ,并且iD是之前的f14e080bbad1,也就是之前的linuxea.com:v0.2[root@linuxea.com /opt/2019/none]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE linuxea.com v0.2 9ba68f247813 About a minute ago 5.53MB linuxea.com v0.3 9ba68f247813 About a minute ago 5.53MB <none> <none> f14e080bbad1 About a minute ago 5.53MB linuxea.com v0.1 00a0fad355c1 2 minutes ago 5.53MB alpine 3.9 caf27325b298 2 weeks ago 5.53MB这又是为什么?我们来大胆的想象一下,如下图:上图中标识每次构建都基于上次构建的层之上进行,如果相同就复用。在最后我们使用docker build -t linuxea.com:v0.2 .事实上是将原本的linuxea.com:v0.2标记到linuxea.com:v0.3的IMAGE ID,而原本的linuxea.com:v0.2就变成了<none><none>,这也就解释了<none><none>的IMAGE ID是f14e080bbad1,和之前的linuxea.com:v0.2一样。而<none><none>这样的镜像是可以通过REPOSITORY:TAG删除的。我想我已经解释明白了开头的三点<none><none>。学习更多学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。docker目录白话容器docker-compose
2019年02月15日
5,237 阅读
0 评论
0 点赞
1
2
3
...
10