Docker
Posted forever_elf
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Docker相关的知识,希望对你有一定的参考价值。
Docker使用Go语言进行开发,基于Linux内核的cgroup,namespace以及AOT的类的Union FS等技术。对进程进行封装隔离,属于操作系统层面的虚拟化技术,由于隔离的进程独立于宿主和其他的隔离,因此也称其为容器。
Docker有三个基本概念:镜像(Image),容器(Container)和仓库(Repository)。
操作系统分为内核和用户空间,对于Linux而言,内核启动后,会挂在root文件系统为其提供用户空间支持,而Docker Image就相当于root文件系统。Docker Image是一个特殊的文件系统。除了里提供容器运行时所需的程序库,资源,配置文件外,还包含了一些为运行时准备的一些配置参数。镜像不包含任何动态数据,其内容在构建之后也不会改变。
Docker被设计为分层存储。由多层文件系统联合组成,镜像构建时,会一层层构建,前一层是后一层的基础,删除前一层文件的操作并不会真的删除前一层的文件,而是尽在当前层的标记位该文件已删除,实际上该文件会一直跟随镜像,因此在构建镜像时,每一层尽量值包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。
镜像和容器的关系就像面向对象程序设计中的类和实例一样。镜像时静态的定义。容易是运行时的实体。容器可以被创建,启动,停止,删除和暂停等。
容器的实质是进程,但它有属于自己的独立的命名空间。因此容器可以拥有自己的rooot文件系统,自己的网络配置,进程空间,甚至用户ID空间,市的容器就像独立了与宿主的系统。每一个容器运行时,是以镜像为基础的,在其上创建一个当前的容器的存储层,成为容器的存储层。容器存储层的声明周期和容器一样。容器消亡时,任何保存于容器存储层的信息都会随容器删除而丢失。容器不应该向其存储层内写入任何数据。容器存储层应该保持无状态。所有的文件写入操作都应该使用数据卷(Volume)或绑定宿主目录,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生命周期独立于容器。容器消亡但数据卷不会。
一个Docker Registry中可以包含多个仓库。每个仓库都可以包含多个标签。每个标签对应一个镜像。通常一个仓库会包含一个软件不同版本的镜像。而标签就常用于对应软件的各个版本。如Ubuntu:14.04或Ubuntu:16.04。若省略了标签,则将lastest作为默认标签。仓库名经常以两段式的形式出现。如hello/kitty。前者通常意味着Docker Registry多用户环境下的用户名。Docker Registry公有服务允许用户免费上传,下载公开的镜像。默认的Registry是Docker Hub。
docker会使用Unix socket与Docker引擎通讯。而只有root用户和docker组的应用才可以访问Docker引擎的Unix socket。因此需要将使用docker的用户加入Docker用户组。
$ sudo groupadd docker //创建docker组
$ sudo usermod -aG docker $USER //将当前用户加入docker组
获取镜像: $ docker pull [Options] [Docker Registry Address(<ip>:<port>)] <Repository>:<Tag> 下载过程中也是一层层的。同时给出了每层ID的前12位。并且在下载结束后给出该镜像完成的sha256摘要。确保下载一致性。
$ docker pull ubuntu:14.04
运行 -i交互式操作 -t终端 --rm容器退出后将其删除
$ docker run -it -rm ubuntu:14.04 bash
列出镜像 $docker images 列表包含了仓库名,标签,镜像ID,创建时间以及所占用的时间。镜像体积在Docker Hub是压缩的。而Docker Images显示的是其镜像下载到本地后,各层所占空间的总和。由于Docker镜像时多层存储结构,可以继承和复用。因此相同的层只保存一份即可。
悬虚镜像(dangling image):这个镜像没有仓库名,也没有标签。均为none。这个镜像原本是有镜像名和标签的,随着官方镜像的维护,发布了新版本后,重新docker oull时,镜像名被转移到了新下载的镜像上,而旧镜像上的名字被取消了,从而成为none。docker build也可能导致这种现象。由于新旧镜像同名,旧镜像被取消,从而出现仓库名,标签均为none的镜像。
$ docker-images -f dangling=true //查看悬虚镜像
$ docker rmi $(docker images -q -f dangling=true) //删除悬虚镜像
默认docker images只会显示顶层的镜像。docker images -a会显示包括中间层镜像在内的所有镜像
$ docker image repoName //根据仓库名列出所有镜像
$ docker image repo:tag
$ docker images -f label=com.hellokitty.version=0.1 // docker image 还支持filter --fliter或-f
$ docker images -f since/before=mongo:3.2
$ docker images --format "{{.ID}}:{{Repository}}" //列出镜像结果并且只包含镜像ID和仓库名
$ docker images --format "table {{.ID}}\t {{.Repository}}\t {{.tag}}"
$ docker run --name webserver -d -p 80:80 nginx //用ngnix镜像启动一个容器,命名为webserver,并映射了80端口
$ docker exec -it webserver bash //进入webserver容器
$ docker diff webserver //查看容器的存储层有哪些改变
$ docker commit [Option] <containerId/containerName> [<Repo>[:<tag>]] //将容器的存储层保存下来成为镜像
$ docker commit --author "cherry" --message "change index.html" webserver nginx:v2
$ docker history <Repo>[:<Tag>] //查看历史修改
$ docker history nginx:v2
使用docker commit对所有镜像都是黑箱操作,生成的镜像也别成为黑箱镜像。即除了制作镜像的人之外,其余人都不知道执行了什么命令,做了什么修改。并且使用docker commit制作镜像,任何修改的结果仅仅是在当前层进行标记,添加,修改而不会改动上层。因此镜像会越来越臃肿。
dockerfile是一个文本文件,其中包含了一条条的指定(Instruction)。每一条指定构建一层。
$ mkdir mynginx $ cd mynginx $ touch Dockerfile $ vi Dockerfile From nginx RUN echo ‘<h1>Hello, Docker</h1>‘ /usr/share/nginx/html/index.html
定制镜像是以一个镜像为基础,在其上进行定制。因此基础镜像是必须的。FROM就是指定基础镜像。一次你From是Dockerfile的第一条命令。
scratch表示一天空白的镜像,此时不以任何镜像为基础。
RUN命令时用来执行命令行命令的。其格式有两种:shell和exec。
shell: RUN <COMMAND>
exec: [".exe", "arg1", "arg2"]
Dockerfile中每一个指令都会建立一层。UnionFS最多不超过127层。Dockerfile支持Shell类的行添加\的命令换行方式以及行首#注释格式
FROM debian:jessie RUn buildDeps: ‘gcc libc6-dev make‘ && apt-get update && apt-get install -y &bulidDeps && wget -O redis.tar.gz "http://" && mkdir -p /usr/src/redis && tar -xzf redis.tar.gz -C /usr/src/redis --strip-component s=1 && make -C /usr/src/redis && make -C /urs/src/redis install && rm -rf /var/lib/apt/lists && rm redis.tar.gz && apt-get purge -y --auto-remove $buildDeps
$ docker build -t nginx:v3 . ///.表示当前目录,实际上是指定上下文路径 --output: Sending build context to Dicker daemon
Docker build原理
Docker在运行时分为Docker引擎和客户端工具。Docker引擎提供了一组REST API,被称为Docker Remote API。而像docker这样的命令时通过这组API与Docker引擎交互,从而完成各种功能的。当构建时,用户会指定构建镜像上下文的路径。docker build命令得到这个路径后,会将路径下的所有内容打包,然后上传给Docker引擎。因此在Dockerfile中使用COPY命令时,是复应用上下文中的文件。因此,COPY这类指令中的源文件的路径都是相对路径。
一般来说,应将Dockerfile放到一个空目录下或项目根目录下。若该目录没有所需文件,则应该讲他们复制过来,若不想发送某些文件,可以生成一个.docerignore文件。
$ docker build https://github... //直接从gitRepo上构建
$ docker build http://server/context.tar.gz //Docker引擎会下载这个压缩包并自动解压,以其作为上下文开始构建
$ docker build -< Dockerfile //从标准输入中读取Dockerfile进行构建
$ cat Dockerfile | docker build //若标准输入是文件,则将其视为context,并开始构建
$ docker build -< context.tar.gz //若标准输入的文件格式是gzip.bzip2以及x2,则将其视为应用上下问的压缩包
Dockerfile指令
COPY <源路径> <目标路径> COPY指令将从构建上下文目录中的<源路径>的文件目录复制到新的一层镜像内的<目标路径>位置。原路径可以使多个,甚至是通配符。其通配符需要满足GO的filepath.Match规则。<目标路径>可以使容器内的绝对路径,也可以是相对工作目录事的相对路径。若目标目录不存在,会在复制前先创建缺失目录。
ADD 若源目录是一个tar压缩文件的话,压缩格式为gzip,bzip2以及xz的情况下,ADD指令将会自动解压文件到目标路径。ADD指令会令镜像构建缓存失效,从而可能会令镜像构建比较慢,所有的文件复制均使用COPY指令。仅在需要自动解压场合使用ADD。
CMD 用于指定默认容器主进程的启动命令。有shelll和exec两种格式。exec在指定ENTRYPOINT指令后,用CMD指定具体的参数。在指令格式中,一般推荐使用exec格式。这类格式在解析是会被解析为JSON数组。因此一定要使用双引号。若使用shell格式,实际命令会被包装为sh -c的参数的形式进行执行。
CMD echo $HOME => CMD ["sh", "-c", "echo $HOME"]
Docker不是虚拟机,容器中的应用都应该以前台执行,而不是虚拟机,物理机里。
ENTRYPOINT和CMD一样,都是在指定容器启动程序及参数。有exec和shell两种指令格式。ENTRYPOINT在运行时也可以替代。需要通过doker run --entrypoit来指定。当指定ENTRYPOINT后,CMD不再直接运行命令,而是将CMD的内容作为参数传给ENTRYPOINT指令。即<ENTRYPOINT> "<CMD>"。entrypoint有两种应用场景。
让镜像变成像命令一样使用
FROM ubuntu:16.04 RUN apt-get update && apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* ENTRYPOINT ["curl", "-s", "http://ip.cn"]
可以使用 docker run myip -i来获取HTTP头消息。CMD的内容会作为参数传给ENTRYPOINT,-i会作为参数传递给curl。
应用运行前的准备工作
FROM alpine:3.4 ... RUN addgroup -S redis && adduser -S -G redis redis ... ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 6379 CMD ["redis-server"]
docker run -it redis id
ENV设置环境变量。有两种格式
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>
ENV VERSION=1.0 DEBUG=on NAME="Happy Feet"
ENV NODE_VERSION 7.2.0 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc && grep "node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 && rm "node-v%NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt && ln -s /usr/local/bin/node /urs/local/bin/nodejs
ARG 格式:AGR <参数名>[=<默认值>]。ARG所设置的构建环境的环境变量在将来容器运行时是不会存在这些环境变量的。但docker history还是可以看到所有值的。Dockerfile中的ARG指定是定义参数名称,以及定义其默认值。该默认值可以在构建命令docker build中用--build-arg<argName><value>来覆盖。
VOLUME 有两种格式:VOLUME ["<路径1>", "<路径2>"]; VOLUME <路径>,为了防止运行时用户忘记将动态文件索堡村目录挂载为卷,在Dockerfile中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时即使用户不挂载,其应用也可以正常运行。可以在运行时通过docker run -d -v mydata:/data dir覆盖整个挂载。
EXPOSE 格式: EXPOSE <端口1> [<端口2>...] EXPOSE指令是声明运行时容器提供服务端口。在运行时并不会因为这个声明应用就会开启这个端口的服务。声明运行时容器提供的端口有两个好处:一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处是在运行时使用随机端口映射时,即使用docker run -P时,会自动随机映射EXPOSE的端口。
WORKDIR格式为WORKDIR <工作目录路径>。使用WORKDIR可以指定工作目录(当前目录),以后各层的当前目录就被改为指定的目录,若目录不存在,WORKDIR会建立目录。
USER格式:USER<用户名>。USER命令和WORKDIR相似,都是改变环境状态并影响以后的层。WORKDIR是改变工作目录,USER则是改变之后层的执行RUN,CMD以及ENTRYPOINT之类命令的身份。USER只是帮你切换指定用户而已,这个用户必须是事先建立好的,否则无法切换。
RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN ["redis-server"]
HEALTHCHECK有两种指令:
HEALTHCHECK[选项] CMD <命令>:设置检查容器健康状况的命令
HEALTHCHECK NONE : 若基础镜像有命令检查,使用该命令也以屏蔽掉其健康检查指令
当在一个镜像制定了HEALTHCHECK指令后,用其启动容器,初始状态会为starting,在HEALTHCHECK指令检查成功后变为healthy。若连续一定次数失败后,则会变为unhealthy。
HEALTHCHECK支持下列选项:
--interval=<间隔>:两次健康检查的间隔,默认为30秒
--timeout=<时长>:健康检查命令运行超时时间,若超过这个时间,本次健康检查就被是位失败,默认30秒
--retries=<次数>:当连续失败指定次数后,就将容器视为unhealthy,默认3次
HEALTHCHECK只可出现一次,若写多个,只有最后一个生效。
HEALTHCHECK有两种格式:shell和exec。命令的返回值决定了该次健康检查的成功与否。0成功;1失败;2保留,不要使用这个值。
From nginx RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s CMD curl -fs http://localhost// || exit 1
健康检查命令的输出都会被存储于健康状态里,可以用docker inspect查看。
$ docker inspect --format ‘{{json .State.Health}}‘ containerName | pthyon -m json.tool
ONBUILD后面会跟着其他指令,如RUN, COPY等。这些指令在当前镜像构建时并不会执行,只有当以当前镜像为基础镜像,去构建下一级镜像时才会被执行。
FROM node:slim RUN mkdir /app WORKDIR /app ONBUILD COPY ./pakage.json /app ONBUILD RUN ["npm", "install"] ONBUILD COPY ./app/ CMD ["npm", "start"]
Docker还支持从rootfs压缩包导入。格式为
docker import [选项] <文件>|<URL>|- [<仓库名>[:<标签>]]
压缩包可以使本地文件,远程web文件,甚至是从标准输入中得到的。压缩包将会在镜像/目录展开。并直接作为第一层提交。
$ docker import \ http://download.openvz.org/template/precreated/ubuntu-14.04-x86_64-minimal.tar.gz openvz/ubuntu:14.04
Docker还提供了docker load和docker save命令,用以将镜像保存为一个tar文件,然后传输到另一个位置上,在加载进来。现已被Docker Registry所替代。
docker save可以将镜像保存为归档文件。
$ docker save alpine | gzip > alpine-latest.tar.gz //压缩镜像
$ docker load -i alpine-latest.tar.gz //加载镜像
$ docker save <镜像名> | bzip2 | pv | ssh <用户名>@<主机名> ‘cat | docker load‘可以将镜像从一个机器迁移到另一个机器并且带有进度条
docker rmi [选项] <镜像1>[<镜像2>...]可以删除本地镜像,docker rm是删除容器。我们可以根据镜像短ID,镜像长ID,镜像名或镜像摘要删除镜像。当我们删除某个镜像时,实际上是删除某个标签的镜像。所以首先要做的是将满足要求的所有镜像标签取消,因此会有Untagged信息。因此一个镜像可以对应多个标签,当删除了所指定的标签后,可能还有别的标签指向了这个镜像,此时就不会有Delete行为了。并非所有的docker rmi都会删除镜像的行为,有可能只是取消了某个标签而已。当改镜像的所有标签都取消了,该镜像会被删除。若该镜像是多层存储结构,在删除时,也是从上层向基础层方面依次进行判断删除。若其他镜像正依赖于当前层,则不会触发删除该层的行为。若有容器以该镜像为基础并在启动,则该容器仍然不会被删除。将docker images -q和docker rmi组合起来可是批量删除镜像。
$ docker rmi $(docker images -q -f dangling=true)
$ docker rmi $(docker images -q redis)
$ docker rmi $(docker images -q -f before=mongo:3.2)
Docker使用Union FS将不同的层结合到一个镜像去。通过Union FS有两个用途。一方面可以实现不借助LVM,RAID将多个disk挂到同一个目录下,另一个是将一个只读的分支和一个可写的分支联合在一起。Live CD基于此方法允许在景象不变的基础上允许用户在其上进行写操作。
以上是关于Docker的主要内容,如果未能解决你的问题,请参考以下文章