项目
博客
归档
资源链接
关于我
项目
博客
归档
资源链接
关于我
Docker入门及其提升讲解
2020-11-11
·
softbabet博主
·
转载
·
docker
·
本文共 2,984个字,预计阅读需要 10分钟。
## 基础概念 ### Docker是什么 Docker最初是`dotCloud`公司创始人Solomon Hykes在法国期间发起的一个公司内部项目,是`云服务技术`的一次革新,并于2013年3月以Apache2.0授权协议开源。后来Docker加入Linux基金会,并成立推动`开放容器联盟(OCI`) Docker使用Google公司推出的`Go语言`进行开发的,基于Linux内核的cgroup,namespace以及AUFS类的Union FS等技术,对进行进行封装隔离,属于操作系统层面的虚拟化技术。由于`隔离的进程独立于宿主和其他的隔离的进程`,因此也称为`容器`。 Docker在容器的基础上,进行了进一步的封装,从文件系统,网络互联到进程隔离等,极大的简化了容器的创建和维护,使得`Docker技术比虚拟机技术更为轻便,快捷`。 ### Docker和传统虚拟机   传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程; 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。 ### 为什么要使用Docker #### Docker优势 更`高效`的利用系统资源;更`快速`的启动时间;`一致`的运行环境;`持续交付`和部署;更轻松的`迁移`;更轻松的`维护和扩展`> **对比传统虚拟机总结** | 特性 | 容器 | 虚拟机 | | ---------- | ------------------ | ---------- | | 启动 | 秒级 | 分钟级 | | 硬件使用 | 一般为MB | 一般为GB | | 性能 | 接近原生 | 较弱 | | 系统支持量 | 单机支持上千个容器 | 一般几十个 | #### Docker架构  Docker使用客户端-服务器`(C/S)架构`模式,使用远程API来管理和创建Dcoker容器 #### Dcoker基本概念 三个基本概念:`镜像(Image),容器(Container),仓库(Repository)` #### Dcoker镜像 操作系统分为`内核`和`用户空间`。对应Linux而言,内核启动后,会挂载root文件系统为其提供用户空间支持。而Dcoker镜像(Image),就相当于是一个root文件系统。比如官方镜像centos:7.6就包含了完整的一套centos76.最小系统的root文件系统。 Dcoker是一个特殊的文件系统,处理提供容器运行时需的程序,库,资源,配置等文件外,还包含了一下为运行时准备的一下配置参数(如匿名卷,环境变量,用户等)。进行不包含任何动态数据,其内容在构建之后也不会被改变。 #### Docker镜像分层存储 因为镜像包含操作系统完整的root文件系统,其体积往往是庞大的,因此在Docker设计时将其设计为分层存储的架构。镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。 镜像构建时,会`一层层构建`,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。在构建镜像的时候,需要额外的小心,每一次尽量只包含该层所需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。 分层存储的特征还使得镜像的复用,制定变得更为容易。甚至可用用之前构建好的镜像作为基础层,然后进一步添加新的层,以制定自己所需的内容,构建新的镜像。 #### Dcoker容器 镜像(Image)和容器(Container)的关系,就像java中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可用被创建,启动,停止,删除,暂停等。 容器也是分层存储。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可用称这个容器运行时读写而准备的存储为`容器存储层`。 容器存储层的生命周期和容器一样,容器消亡时,容器村层层也随之消亡,因此,任何保存于容器存储层的信息都会随容器删除而丢失。 按照Docker最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保存无状态化。所有的文件写入操作,都应该使用Volume数据卷,或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。 数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据不会丢失。 #### Dcoker仓库 镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。 一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。 通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。 以 Ubuntu 镜像 为例,ubuntu 是仓库的名字,其内包含有不同的版本标签,如,14.04, 16.04。我们可以通过 ubuntu:14.04,或者 ubuntu:16.04 来具体指定所需哪个版本的镜像。如果忽略了标签,比如 ubuntu,那将视为 ubuntu:latest。 仓库名经常以 两段式路径 形式出现,比如 jwilder/nginx-proxy,前者往往意味着 Docker Registry 多用户环境下的用户名,后者则往往是对应的软件名。但这并非绝对,取决于所使用的具体 Docker Registry 的软件或服务。 #### 公有 Docker Registry Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。 最常使用的 Registry 公开服务是官方的 [Docker Hub](https://hub.docker.com/),这也是默认的 Registry,并拥有大量的高质量的官方镜像。除此以外,还有 [CoreOS ](https://coreos.com/)的 [Quay.io](https://quay.io/repository/),CoreOS 相关的镜像存储在这里;Google 的 [Google Container Registry](https://cloud.google.com/container-registry/),Kubernetes 的镜像使用的就是这个服务。 由于某些原因,在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对 Docker Hub 的镜像服务(Registry Mirror),这些镜像服务被称为加速器。常见的有 [阿里云加速器](https://cr.console.aliyun.com/#/accelerator)、[DaoCloud 加速器](https://www.daocloud.io/mirror#accelerator-doc) 等。使用加速器会直接从国内的地址下载 Docker Hub 的镜像,比直接从 Docker Hub 下载速度会提高很多。 国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 [时速云镜像仓库](https://hub.tenxcloud.com/)、[网易云镜像服务](https://c.163.com/hub#/m/library/)、[DaoCloud 镜像市场](https://hub.daocloud.io/)、[阿里云镜像库](https://cr.console.aliyun.com/) 等。 #### 私有 Docker Registry 除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 [Docker Registry](https://store.docker.com/images/registry/) 镜像,可以直接使用做为私有 Registry 服务。 开源的 Docker Registry 镜像只提供了 [Docker Registry API](https://docs.docker.com/registry/spec/api/) 的服务端实现,足以支持 docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。在官方的商业化版本 [Docker Trusted Registry](https://docs.docker.com/datacenter/dtr/2.0/) 中,提供了这些高级功能。 除了官方的 Docker Registry 外,还有第三方软件实现了 Docker Registry API,甚至提供了用户界面以及一些高级功能。比如,[VMWare Harbor](https://github.com/vmware/harbor) 和 [Sonatype Nexus](https://www.sonatype.com/docker)。 ### Docker安装与加速 #### Docker版本命名 Docker在1.13版本之后,从2017年3月1日开始,版本命名规则变更: | 项目 | 说明 | | ---------- | ------------ | | 版本格式 | YY.MM | | Stable版本 | 每个季度发行 | | Edge版本 | 每月发行 | 同时Docker划分为**CE**和**EE**。CE社区版,EE企业版,强调安全付费使用。 #### Docker安装 官方网站有安装指南:https://docs.docker.com/engine/install/ #### CentOS 安装 Docker **系统要求** Docker CE支持64位版本CentOS 7, 并且要求内核版本不低于3.10。 `uname -r ` =>查询版本 **卸载旧版本** 旧版的Docker称为docker或者docker-engine,使用卸载旧版命令: `sudo yum remove docker docker-common docker-selinux docker-engine` **使用yum安装** `sudo yum install docker-ce` **注意**:如果按照的是centos7 minimal版本,这些按照提示"没有可用软件包"这个时候需要安装必要的软件依赖及更新增加docker-ce yum源。 `yum install -y yum-utils device-mapper-persistent-data lvm2` `yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo` #### **使用脚本安装** 在测试或开发环境中Docker官方为简化安装流程,提供了一套便捷的安装脚本,CentOS系统上可用使用这套脚本安装: `curl -fsSL https://get.docker.com -o get-docker.sh` `sh get-docker.sh --mirror Aliyun` **启动Docker CE** `systemctl enable docker` #设置开机启动 `systemctl start docker ` `docker info` #查看详细信息 `docker --version` #查看docker版本 **建立docker用户组** 默认情况下,docker命令会使用Unix socket与Docker引擎通讯。而只有root用户和docker组的用户才可以访问Docker引擎的Unix socket。一般Linux系统上不会直接使用root混合进行操作。需要使用docker的用户加入docker用户组。 `sudo groupadd docker` #建立docker组 `sudo usermod -aG docker $USER` 将当前用户加入docker组 **测试Docker是否安装正确** `docker run hello-world` #启动一个基于hello-world镜像容器 #### CnetOS删除Docker **删除docker安装包** `sudo yum remove docker-ce` **删除docker镜像** `sudo rm -rf /var/lib/docker` #### 镜像加速器 国内从Docker Hub拉取镜像困难,可配置加速器。Docker官方和国内很多云服务商都提供了国内加速器,例如: `Docker官方提供的中国 registry mirror` `阿里云加速器` `DaoCloud 加速器` `163 加速器` #### CnetOS 7配置镜像加速 对于使用systemd的系统,请在`/etc/docker/daemon.json`中写入(如果文件不存在就新建): ```json { "registry-mirrors":[ "http://hub-mirror.c.163.com" ] } ``` 重新启动服务生效 `sudo systemctl daemon-reload` `sudo systemctl restart docker` 查看镜像:`docker images` 查看容器:`docker ps -a` 删除容器:`docker rm -f f8969a0ae670` 删除镜像:`docker rmi hello-world` 要先删除容器才能删除对应镜像 #### 检查加速器是否生效 使用命令:docker info 查看 ``` ...... Registry Mirrors: http://hub-mirror.c.163.com/ ``` ### Docker常用的命令 ##### Docker镜像操作 Docker运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker会从镜像仓库下载该镜像。 ##### 获取镜像 从Dcoker镜像仓库获取的命令是`docker pull`,其命令格式为: `docker pull [选项] [Docker Registry 地址[:端口号]/] 仓库名[:标签]` 具体的选项可用通过docker pull --help命令看到。镜像名称格式:Dcoker镜像仓库地址:地址的格式一般是<域名/IP>[:端口号]。默认地址是Docker Hub。仓库名:如之前所说,这里仓库是两段式名称,即<用户名>/<软件名>。对于Docker Hub,如果不给出用户名,则more为library,就是官方镜像。 `docker pull ubuntu:16.04` ##### 运行镜像 有镜像就可以启动一个容器,如果我们打算启动里面的bash并进行交互操作,则执行: `docker run -it --rm ubuntu:16.04 bash` -it:这里两个参数,-i:交互式操作,-t:终端 --rm:说明容器退出随之将其删除 ubuntu:16.04:指用ubuntu:16.04镜像为基础来启动容器 bash:放在镜像名后的是命令,这里我们希望有个交互式Shell,因此用bash 最好通过`exit`退出这个容器 ##### 列出镜像 想要列出已经下载下来的镜像,可以使用docker images ls命令。列出了仓库名,标签,镜像ID,创建实践,所占空间。 `docker images ls` 查看镜像,容器,数据卷占用的空间 `docker system df` 仓库名,标签均为
的镜像称为 `虚悬镜像(`dangling image),显示这类镜像: `docker image ls -f dangling=true` 一般来说,虚悬镜像已经失去了存在的加载,是可以随意删除的,可以使用删除命令: `docker image prune` ##### 删除本地镜像 如果要删除本地的镜像,可以使用 `docker image rm`命令,其格式为: `docker image rm [选项] <镜像1>[<镜像2>....]` `docker image rm -f hello-world` 其中,<镜像>可以是镜像短ID,镜像长ID,镜像名或者镜像摘要。 使用docker image ls -q来配合docker images rm,这样可以`批量`删除希望删除的镜像。 `docker image rm $(docker image ls -q ubuntu) ` #删除所有有关ubuntu的镜像,如果在-q后不加任何,就是删除所有 或者删除所有在ubuntu:16.04之前的镜像: `docker image rm $(docker image ls -q -f before=ubuntu:16.04)` ##### Docker容器操作 容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以立即为模拟运行的一套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。 下面结束如何管理一个容器,包括创建,启动,停止等。 ##### 启动容器 启动容器有两种方式,一种是`基于镜像新建一个容器并启动`,另外一个是在`中止状态(stopped)的容器重新启动`。 因为Dcoker的容器是轻量级的,用户可以随时删除和创建容器。 **新建并启动** `docker run` 输出一个”Hello World“,之后中止容器。 `docker run ubuntu:16.04 /bin/echo 'Hello World'` **启动已经中止容器** `docker container start 或者 docker start` 启动一个bash终端,运行用户进行交互。 `docker run -it ubuntu:16.04 /bin/bash` 无bash终端交互的启动:`docker run 容器短ID` -t让Dcoker分配一个伪终端并绑定到容器的标准输入上,-i则让容器的标准输入报仇打开。 当利用docker run来创建容器时,Docker在后台运行的标准操作包括: 检查本地是否存在指定的镜像,不存在就从公有仓库下载 利用镜像创建并启动一个容器 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层。 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去 从地址池配置一个ip地址给容器 执行用户指定的应用程序 执行完毕后容器被中止 ##### 后台运行 可以通过添加`-d`参数来实现。 如果不使用-d参数运行容器,会把日志打印在控制台。 如果使用了-d参数运行容器,不会输出日志,只会打印容器id(输出结构可以用`docker logs`查看) ` docker run -d hello-world` 注意:容器是否会长久运行,是和docker run指定的命令相关,和-d参数无关。 ##### 停止运行的容器 可以使用`docker container stop`来终止一个运行中的容器。 ` docker container stop 088435f31338` 终止状态的容器可以用`docker container ls -a`命令看到 处于终止的容器,可以通过`docker container start`命令来重新启动 此外,`docker container restart`命令会将一个运行态的容器致在,然后再重启。 ##### 进入容器 在使用-d参数时,容器启动后会进入后台,某些时候需要进入容器进行操作,使用docker exec命令可以进入到运行中。 exec命令 -i -t 参数 docker exec后面可以跟多个参数,这里主要说明-i -t参数: 只用-i参数时,由于没有分配伪终端,解密没有我们熟悉的Linux命令提示符,但是命令执行结果仍可返回。当-i -t参数一起使用时,则可以看到我们熟悉的Linux命令提示符。 `docker exec -it 容器ID /bin/bash` `docker exec -it 容器ID bash` `docker exec -it 容器ID sh` ##### 导入和导出容器 **导出容器** 如果要导出本地某个容器,可以使用docker export命令 `docker export 容器ID >导出文件名.tar` 例子:`docker export 5b1642cc43a3 > ubuntu-16.04.zip` **导入容器** 可以使用docker import从容器快照文件中再导入为镜像 `cat 导出文件名.tar | docker import-镜像用户/镜像名:镜像版本` 例子:`cat ubuntu-16.04.zip | docker import - yuan/ubunt-add:1.0` 此外,也可以通过指定UEL或某个目录来导入: `docker import http://study.163.com/images.tgz example/imagesrepo` ##### 删除容器 **删除容器** 可以使用docker container rm 来删除一个处于终止状态的容器。 `docker container rm unbuntu:16.04` 删除容器:**`docker rm -f 容器ID`** 查看容器信息:**`docker ps -a`** 删除镜像: **`docker rmi repository信息`** 查看repository信息:**` docker images`** 如果要删除一个运行中的容器,可以添加`-f`参数。Docker会发生SIGKILL信号给容器。 **清除所有处于终止状的容器** 用docker container ls -a命令可以查看所有已经传解的包括终止状态的容器,如果数量太多要一个个删除可能会麻烦,可以清除掉所有终止的容器命令:`docker container prune`,清除完成之后:`docker ps -a`此时为空了。 #### 利用Dcoker file构建私有镜像 ##### 使用Dockerfile定制镜像 镜像的定制实际是定制`每一层所添加的配置和文件`。我们可以把每一层修改,安装,构建,操作的命令都写入一个脚本,这个脚本就是`Dockerfile`。 Dockerfile是一个文本文件,其内包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。 以nginx镜像为例,使用Dockerfile来定制。 在一个空白目录中,建立一个文本文件,并命名为`Dockerfile`: `mkdir mynginx` `cd mynginx` `touch Dockerfile` 其内容为: `FROM nginx` `RUN echo '
Hello,Dcoker!
' /usr/share/nginx/html/index.html` 这个Dockerfile很简单,一共就两行。涉及了FROM和RUN两条指令。 ###### FROM 指定基础镜像 所谓定制镜像,一定是以一个镜像为基础,在其上进行定制。基础镜像是必须指定的,而FROM就是指定基础镜像,因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。在Dcoker Hub上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如nginx,redis,mysql,tomcat等;可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。 如果·没有找到对应服务的镜像,官方镜像中提供了一些更为基础的操作系统镜像,如ubuntu,debian,centos,alpine等,这些操作系统的软件库为我们提供了广阔的扩展空间。 除了选择现有镜像为基础镜像外,Docker还存在一个特殊的镜像,名为`scratch`。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。 `FROM scratch` 如果你以scratch为基础镜像的话,意味着你`不以任何镜像为基础`,接下来所写的指令将作为镜像第一层开始存在。 对应Linux下静态编译的程序来说,并不需要有操作系统提供运行支持,所需的一切库都已经在可执行文件里了,因此直接FROM scratch会让镜像体积更加小巧。使用Go语言开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为Go是特别适合容器微服务架构的语言的原因之一。 ###### RUN执行命令 RUN指令是用来`执行命令行`命令的。由于命令行的强大能力,RUN指令在定制镜像时是最常用的指令之一。其格式有两种: **`shell格式:RUN <命令>`** `RUN echo '
Hello,Dcoker!
' /usr/share/nginx/html/index.html` **`exec格式: RUN ["可执行文件",“参数1”,“参数2”]`** `RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1` `RUN make -C /usr/src/redis` `RUN make -C /usr/src/redis install` ##### 构建镜像 在Dockerfile文件目录下执行: `docker build -t mynginx:1.0 .` 从命令的输出结果中,我们可以看到镜像构建的过程。在Step2中,RUN指令启动了一个容器8cf1bfb43ff5,执行了所要求的命令,并最后提交了这一层5cb9b41bf60d,随后删除所用定的这个容器8cf1bfb43ff5。 ##### Dockerfile指令详解 ###### COPY复制文件 格式: COPY <源路径>...<目标路径> COPY ["<源路径>",..."<目标路径>"] COPY指令将从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置。例如: `COPY package.json /usr/src/app` <源路径>可以是多个,甚至可以是通配符,如: `COPY hom* /mydir/` `COPY hom?.txt /mydir/` ###### ADD更高级的复制文件 ADD指令和COPY的格式和性质基本一致,但是在COPY基础上增加了一些功能。 比如<源路径>可以是一个URL,这种情况下,Docker引擎会视图去下载这个连接的文件放到<目标路径>去。 官方要求尽量使用COPY,最适合ADD的场景就是所提及的需要自动解压的场合。 `因此遵循所有文件复制均使用COPY,仅在需要自动解压场景使用ADD` 例子: 1.在上面的Dockerfile中添加:`COPY a.html /usr/share/nginx/html` 2.在次目录下创建a.html 3.构建次镜像:`docker build -t mynginx:2.0 .` 4.启动nginx:`docker run --name mynginx2 -p 80:80 mynginx:2.0` 5.查看是容器启动情况:`docker ps -a` 6.进入容器内:`docker exec -it mynginx2 /bin/bash`,进入/sur/share/nginx/html目录下查看到a.html文件 ###### CMD容器启动命令 CMD指令的格式和RUN相似,也有两种格式: shell格式:CMD <命令> exec格式:CMD ["可执行文件","参数1","参数2"] 参数列表格式:CMD ["参数1","参数2"...]。在指定了ENTRYPOINT指令后,用CMD指定具体的参数。 Docker部署虚拟机,容器就是进程。既然是进程,那么在启动容器的适合,需要指定所运行的程序及参数。CMD指令就是指定默认的容器主进程启动命令的。 ###### ENTRYPOINT入口点 ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序及参数。ENTRYPOINT在运行时也可以替代,不过比CMD要略显繁琐,需要通过docker run的参数 --entrypoint来指定。 当指定了ENTRYPOINT后,CMD的含有就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传递给ENTRYPOINT指令,换句话说实际执行时,将变为: `
"
"` ###### ENV设置环境变量 格式两种: `ENV
` `ENV
=
=
...` 这个指令简单,就是`设置环境变量`而已,无论是后面的其他指令,如RUN,还是运行时的应用,都可以直接使用这里订阅的环境变量。 `ENV VERSION=1.0 DEBUG=on NAME="Happy Feet"` `$VERSION #使用环境变量` 下列指令可以支持环境变量展开:ADD,COPY,ENV,EXPOSE,LABEL,USER,WORKDIR,VOLUME,STOPSIGNAL,ONBUILD, ###### ARG构建参数 格式:` ARG <参数名>[=<默认值>]` 构建参数和ENV的效果一样,都是设置环境变量。所不同的是,ARG所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就是使用ARG保存密码之类的信息,因为`docker history`还是可以看到所有值的。 Dcokerfile中的`ARG是定义参数名称,以及定义其默认值`。该默认值可以在构建命令docker build中用 `--build-arg <参数名>=<值>`来覆盖 ###### VOLUME 定义匿名卷 格式为: `VOLUME ["<路径1>", "<路径2>"...]` ` VOLUME <路径>` 之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。 `VOLUME /data` 这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如: `docker run -d -v mydata:/data xxxx` 在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。 ###### EXPOSE 暴露端口 格式为 : `EXPOSE <端口1> [<端口2>...]` EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。 此外,在早期 Docker 版本中还有一个特殊的用处。以前所有容器都运行于默认桥接网络中,因此所有容器互相之间都可以直接访问,这样存在一定的安全性问题。于是有了一个 Docker 引擎参数 --icc=false,当指定该参数后,容器间将默认无法互访,除非互相间使用了 --links 参数的容器才可以互通,并且只有镜像中 EXPOSE 所声明的端口才可以被访问。这个 --icc=false 的用法,在引入了 docker network 后已经基本不用了,通过自定义网络可以很轻松的实现容器间的互联与隔离。 要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。 ###### WORKDIR 指定工作目录 格式为: ` WORKDIR <工作目录路径>` 使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。 之前提到一些初学者常犯的错误是把 Dockerfile 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误: `RUN cd /app` `RUN echo "hello" > world.txt` 如果将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello。原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对 Dockerfile 构建分层存储的概念不了解所导致的错误。 之前说过每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。 因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。 ###### USER 指定当前用户 格式:`USER <用户名>` USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。 当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。 `RUN groupadd -r redis && useradd -r -g redis redis` `USER redis` `RUN [ "redis-server" ]` 如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu。 ```nginx # 建立 redis 用户,并使用 gosu 换另一个用户执行命令 RUN groupadd -r redis && useradd -r -g redis redis # 下载 gosu RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \ && chmod +x /usr/local/bin/gosu \ && gosu nobody true # 设置 CMD,并以另外的用户执行 CMD [ "exec", "gosu", "redis", "redis-server" ] ``` ###### HEALTHCHECK 健康检查 格式: HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令 HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令 HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。 在没有 HEALTHCHECK 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。 而自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。 当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。 HEALTHCHECK 支持下列选项: --interval=<间隔>:两次健康检查的间隔,默认为 30 秒; --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒; --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。 和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。 在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败;2:保留,不要使用这个值。 #### 运行java程序 ##### 定制镜像 准备一个没有第三方依赖的javaweb项目,可以参考maven结果项目:session-web-0.0.1-SNAPSHOT.war 把该war上传到安装有docker软件的服务上宿主目录下。在同级目录下创建Dcokerfile: touch Dcokerfile, vim Dcokerfile 按照前面章节所需的Dcokerfile指定镜像知识来编写Dcokerfile文件内容。 ##### Java程序Dcokerfile Dcokerfile文件内容如下: ``` #基础镜像使用tomcat:7.0.88-jre8 FROM tomcat:7.0.88-jre8 #作者 MAINTAINER yuan
#定义环境变量 ENV TOMCAT_BASE /usr/local/tomcat #复制war包 COPY ./session-web.war $TOMCAT_BASE/webapps/ ``` 执行构建 `docker build -t session-web:latest .` 如果构建成功,则会显示构建的分层信息及结果。 构建成功使用docker images命令查看本地是否有该镜像。 ##### 运行镜像 `docker run --name session-web -d -p 8888:8080 session-web:latest` 启动后使用`netstat -na|grep 8888`检验端口是否在监听状态 浏览器反问:http:ip:8888/session-web/user/login #### 搭建docker私有仓库 ##### Docker Hub 目前Docker官方维护一个公共仓库Docker Hub,其中已经包括了数量超过15000的镜像。 **注册登录**
进入启动docker的centos服务器执行:` docker login`,输入注册从用户名及密码,显示login succeeded表示登录成功。退出登录命令:`docker logout` **拉取镜像** 可以通过`docker search`命令来查找官方仓库中的镜像,并利用`docker pull `命令来将他下载到本地。 举例:`docker search tomcat` **推送镜像** 用户也可以在登录后通过`docker push`命令将自己的镜像推送到Docker Hub。 ##### 私有仓库 有时候使用Docker Hub这样的公共仓库可能不方便,用户可以创建一个本代仓库供私人使用。比如:基于公司内部创项目构建的镜像。 docker-registry是官方提供的攻击,可以用于构建私有的镜像仓库。 按照运行docker-registry 可以通过获取官方registry镜像来运行。默认情况下,仓库会被创建在容器的/var/lib/resitry目录下,可以通过-v参数来将镜像文件存放在本地的指定路径。 ``` docker run --name registry -d \ -p 5000:5000 --restart=always \ -v /opt/data/registry:/var/lib/registry \ registry ``` **在私有仓库上传,搜索,下载镜像** 创建好私有仓库之后,就可以使用docker tag来标记一个镜像,然后推送它到仓库。 先在本机查看已有的镜像。 `docker image ls` 使用docker tag 将session-web:latest这个镜像标记为127.0.0.1:5000/session-web:latest格式为`docker tag IMAGE[:TAG][REGISTRY_HOST[:REGISTRY_PORT]/]REGISTRY[:TAG]` `docker tag session-web:latest 127.0.0.1:5000/session-web:latest` 使用docker push上传标记的镜像 `docker push 127.0.0.1:5000/session-web:latest` 用`curl`查看仓库中的镜像 `curl 127.0.0.1:5000/v2/_catalog` 如果可以看到{"repositories":["session-web"]},表明镜像已经被成功上传了。 先删除已有镜像,再尝试从私有仓库中下载这个镜像。 `docker image rm 127.0.0.1:5000/session-web:latest` `docker pull 127.0.0.1:5000/session-web:latest` 注意事项:如果你不想使用127.0.0.1:5000作为仓库地址,比如想让本网段的其他主机也能把镜像推送到私有仓库。你就得把例如192.168.100.100:5000这样的内务地址作为私有仓库地址,这时你会发现无法成功推送到镜像。 这是因为`Docker默认不允许非HTTPS方式推送镜像。`我们可以通过Docker配置选项来取消这个限制。 **Ubunit16.04+,Debian 8+,centos 7** 对于使用systemd的系统,请在`/etc/docker/daemon.json`中写入如下内容(如果文件不存在新建) ``` { "registry-mirror":[ "https://registry.docker-cn.com" ], "insecure-registries":[ "192.168.100.100:5000" ] } ``` **window.Mac** 在上面系统中设置编辑daemon.json增减和上面一样的内容。 #### 数据挂载 ##### Docker数据管理 在容器中管理数据主要有两种方式: `数据卷(Volumes)` `挂载主机目录(Bingd mounts`  ##### 数据卷 数据卷是一个可供一个或多个容器使用的特殊目录,它绕过UFS,可以提供很多有用的特性: 数据卷可以在容器之间共享和重用 对数据卷的修改会立刻生效 对数据卷的更新,不会影响镜像 数据卷默认会一直存在,即使容器被删除 注意:数据卷的使用,类似于linux下对目录或文件进行mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷。 Docker中提供了两种挂载方式,`-v和-mount`,这两种方式该如何选择呢? Docker新用户应该选择 --mount参数,经验丰富的Dcoker使用者对-v或者--volume已经很熟悉了,但是推荐使用--mount。 创建一个数据卷: `docker volume create my-volume` 查看所有的数据卷: `docker volume ls` 查看指定数据卷的信息: `docker volume inspect my-volume` 启动一个挂载数据卷的容器: 在用docker run命令的时候,使用--mount标记来将数据卷挂载到容器里。在一次docker run中可以挂载多个数据卷。 创建一个名为session-web的容器,并加载一个数据卷到容器的/webapp目录下。 ``` dooker run --name session-web -d -p 8888:8080 \ #-v my-volume:/webapp \ --mount source=my-volume,target=/webapp\ session-web:latest ``` 删除数据卷:`docker volume rm my-volume` 数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷,可以在删除容器的时候使用`docker rm -v`这个命令。 无主的数据卷可能会占据很多空间,要清理请使用一下命令: `docker volume prune` ##### 挂载主机目录 使用--mount标记可以指定挂载一个本地主机的`目录`到容器中去 ``` dooker run --name session-web -d -p 8888:8080 \ #-v my-volume:/weapp \ --mount type=bind,source=/src/webapp/,target=/opt/webapp session-web:latest ``` 上面的命令加载主机的/src/webapp目录到容器的/opt/webapp目录下。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器十分正常工作。 `本地目录的路基必须是绝对路径` 以前----使用-v参数时如果本地目录不存在Docker会自动为你传解一个文件夹 现在---使用--mount参数时如果本地目录不存在,Docker会报错 `Docker挂载主机目录的默认权限是读写,用户也可以通过增加readonly指定为只读。` --mount标记也可以从主机挂载单个`文件`到容器中: ``` docker run --rm -it \ #-v $HOME/.bash_history:/root/.bash_history \ --mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \ ubuntu:17.10 \ bash ``` 这样就可以记录在容器输入过的命令了 #### Compose集成式应用组合