docker入门

Docker基础

docker保姆级教程
https://github.com/yeasy/docker_practice/blob/master/SUMMARY.md

Docker系统有两个程序:

docker服务端和docker客户端。
其中docker服务端是一个服务进程,管理着所有的容器。
docker客户端则扮演着docker服务端的远程控制器,可以用来控制docker的服务端进程。
大部分情况下,docker服务端和客户端运行在一台机器上。

常用命令

// 查看docker版本信息
docker version

// 搜索可用的镜像
docker search mysql

//拉取镜像-默认是拉取的最新版本
docker pull mysql

//拉取镜像-特定版本拉取(版本号一定要是官方放出的版本号,否则是查找不到的)
docker pull mysql:8.0.22

// 列举本机所有镜像
docker images -a

// 列出镜像结果,并且只包含镜像ID和仓库名
docker images --format "{{.ID}}: {{.Repository}}"

// 删除镜像('xxx'可以为镜像id或镜像REPOSITORY)
docker rmi xxx

// 保存对容器的修改(如安装了ping)
参见https://www.docker.org.cn/book/docker/docer-save-changes-10.html
查看被修改的容器 :docker ps -l
提交指定容器保存为新的镜像: docker commit  

// 查看容器详细信息
docker inspect xxx

// 查看所有容器
docker ps -a  
[or]
docker container ls -a

// 查看运行中的容器
docker ps

// 删除指定容器(Docker 会发送 SIGKILL 信号给容器。 加-f是删除处于运行中的容器)
docker rm -f xxx

// 查看docker容器日志
docker logs xxx

// 新建并启动容器
docker run

// docker run命令实用参数举例
-d 让docker处于后台运行
-P 容器内部端口随机映射到主机的端口
-p 容器内部端口绑定到主机指定的端口(使用:docker run -d -p <主机端口>:<容器端口> ...)
--name 给容器起别名(使用:docker run -d -P --name <容器别名> <镜像>)
-it 进入交互式终端(这是两个参数,一个是 -i 交互式操作,一个是 -t 终端。通常还会在镜像名后跟一个命令,如我们希望有个交互式 Shell,因此在镜像后会跟一个 bash)
--rm 这个参数是说容器退出后随之将其删除。
-v 添加数据卷(-v /my/own/datadir:/var/lib/mysql /my/own/datadir是主机的数据库路径 /var/lib/mysql是容器中的数据库路径)


// 查看指定容器的启动命令
docker ps -a --no-trunc | grep xxx
[or]
在docker inspect的Path参数也可见

// 查看容器端口绑定情况
docker port xxx

// 查看docker网络
docker network ls

// 创建网络
docker network create -d bridge test-net
(说明:-d用于指定docker网络类型,分为bridge、overlay)

// 连接容器(运行一个容器并连接到新建的 test-net 网络)
docker run -itd --name test1 --network test-net ubuntu /bin/bash
docker run -itd --name test2 --network test-net ubuntu /bin/bash
(而后,test1、test2容器间便可以互相ping通了)


// 进入容器后,若发现有的命令不支持,如:ping www.baidu.com,提示:`bash: ping: command not found`
// 这时就需要手动安装对应命令了。但执行apt-get install ping时,又提示:`E: Unable to locate package ping`
// 解决方式:先执行apt-get update,再执行apt-get install ping即可


// 构建镜像
docker build -t xxx .




拓展

redis-shake是一个用于做redis数据迁移的工具,并提供一定程度的数据清洗能力。
参见:https://github.com/alibaba/RedisShake/blob/v3/README_zh.md

docker简介

参考:https://docs.kilvn.com/docker_practice/introduction/why.html

Docker 技术比虚拟机技术更为轻便、快捷。

  • 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
  • 而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
** Q:docker优势 **

1、更高效的利用系统资源
由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。
无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。
因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。

2、更快速的启动时间
Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,
因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。

3、一致的运行环境
开发过程中一个常见的问题是环境一致性问题。
由于开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。
而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,
从而不会再出现 “这段代码在我机器上没问题啊” 这类问题。

4、持续交付和部署
对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。
使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。
开发人员可以通过 Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系统进行集成测试,
而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(Continuous Delivery/Deployment) 系统进行自动部署。

5、更轻松的迁移
由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。
因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。

6、更轻松的扩展和维护
Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,
也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。

基本概念

镜像

操作系统分为内核和用户空间。
对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。
而 Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:14.04 就包含了完整的一套 Ubuntu 14.04 最小系统的 root 文件系统。

Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

注意,当我们运行一个容器的时候(如果不使用卷,也没绑定宿主机目录的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 docker commit 命令,可以将容器的存储层保存下来成为镜像。
换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。

// docker commit 的语法格式为:
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
// 举例
$ docker commit \
    --author "Tao Wang " \
    --message "修改了默认网页" \
    webserver \
    nginx:v2
// 查看镜像的历史版本提交信息
docker history nginx:v2

需慎用 docker commit去生成镜像,其生成的镜像被称为黑箱镜像。换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知,这种黑箱镜像的维护工作是非常痛苦的。
回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到™。这会让镜像更加臃肿。
镜像的定制行为应该使用 Dockerfile 来完成。

容器

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样。
镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全

每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为容器存储层
容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。
数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器可以随意删除、重新 run,数据却不会丢失。

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

仓库

一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。

仓库名经常以 两段式路径 形式出现,比如 jwilder/nginx-proxy,前者往往意味着 Docker Registry 多用户环境下的用户名,后者则往往是对应的软件名。

注册服务器(Registry)是管理仓库(Repository)的具体服务器,仓库是集中存放镜像的地方。

利用DockerFile定制镜像

参见:https://docs.kilvn.com/docker_practice/image/build.html

从之前的 docker commit 的学习中,我们可以了解到:镜像的定制实际上就是定制每一层所添加的配置、文件。
如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层

在docker build 命令最后有一个 . 表示当前目录,其实这是在指定上下文路径
当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。
在默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。
这只是默认行为,实际上 Dockerfile 的文件名并不要求必须为 Dockerfile,而且并不要求必须位于上下文目录中,比如可以用 -f …/Dockerfile.php 参数指定某个文件作为 Dockerfile。

常规构建方法

docker build -t xxx .

其它构建方法

  1. 直接用 Git repo 进行构建
    docker build 还支持从 URL 构建,比如可以直接从 Git repo 中构建:
    docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14
    说明:这行命令指定了构建所需的 Git repo,并且指定默认的 master 分支,构建目录为 /8.14/,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建。

  2. 用给定的 tar 压缩包构建
    docker build http://server/context.tar.gz
    如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。

  3. 从标准输入中读取 Dockerfile 进行构建

  4. 从标准输入中读取上下文压缩包进行构建

Dockerfile指令详解

// Dockerfile指令详解
- FROM 指定基础镜像
所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。而 FROM 就是指定基础镜像。
因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。
(Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。因此直接 FROM scratch 会让镜像体积更加小巧。
如果以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。)

- RUN 执行命令
其格式有两种:
    1. shell 格式:RUN <命令>
    2. exec 格式:RUN ["可执行文件", "参数1", "参数2"]
每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。
对于有多个命令的情况,建议将命令间用‘&&’连接,避免多层构建镜像。正确示例:
    RUN buildDeps='gcc libc6-dev make' \
        && apt-get update \
        && apt-get install -y $buildDeps

- COPY 复制文件
格式:
    COPY <源路径>... <目标路径>
    COPY ["<源路径1>",... "<目标路径>"]
COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。
比如:
    COPY package.json /usr/src/app/
    COPY hom* /mydir/
<源路径> 可以是多个,甚至可以是通配符
<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。
    目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。

- ADD 更高级的复制文件
ADD 指令和 COPY 的格式和性质基本一致,但也有所不同。
比如 <源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。
    下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整;
    另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。
    所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。
    因此,这个功能其实并不实用,而且不推荐使用。
如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。
在 Docker 官方的最佳实践文档中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。
另外需要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

- CMD 容器启动命令
CMD 指令的格式和 RUN 相似,也是两种格式:
    shell 格式:CMD <命令>
    exec 格式:CMD ["可执行文件", "参数1", "参数2"...]
Docker不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。
CMD 指令就是用于指定默认的容器主进程的启动命令的。
在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。
如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:
    CMD echo $HOME
    在实际执行中,会将其变更为:
    CMD [ "sh", "-c", "echo $HOME" ]
提到 CMD 就不得不提容器中应用在前台执行和后台执行的问题。
容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 upstart/systemd 去启动后台服务,容器内没有后台服务的概念。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
举例:
    CMD service nginx start
    使用 service nginx start 命令,是希望以后台守护进程形式启动 nginx 服务。
    而刚才说了 CMD service nginx start 会被理解为 CMD [ "sh", "-c", "service nginx start"],因此主进程实际上是 sh。
    那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。
    正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:
        CMD ["nginx", "-g", "daemon off;"]

- ENTRYPOINT 入口点
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:
 ""
其使用场景及好处参见:https://docs.kilvn.com/docker_practice/image/dockerfile/entrypoint.html

- ENV 设置环境变量
格式有两种:
    ENV  
    ENV = =...
这个指令很简单,就是设置环境变量而已。
环境变量可以使用的地方很多,很强大。通过环境变量,我们可以让一份 Dockerfile 制作更多的镜像,只需使用不同的环境变量即可。

- VOLUME 定义匿名卷
格式为:
    VOLUME ["<路径1>", "<路径2>"...]
    VOLUME <路径>
之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。
为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,
这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
VOLUME /data 这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,
从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:
    docker run -d -v mydata:/data xxxx
    在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。

- EXPOSE 声明端口
格式为 EXPOSE <端口1> [<端口2>...]
指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。
在 Dockerfile 中写入这样的声明有两个好处:
    一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;
    另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

- WORKDIR 指定工作目录
格式为 WORKDIR <工作目录路径>
使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。

- USER 指定当前用户
格式:USER <用户名>
USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。
    WORKDIR 是改变工作目录,
    USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。

- HEALTHCHECK 健康检查
格式:
    HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
    HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。
自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。
    背景:
        在没有 HEALTHCHECK 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。
        很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。
        在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。
当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。
HEALTHCHECK 支持下列选项:
    --interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
    --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
    --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。
和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。
在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。
  命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败
举例:
    假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写:
    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
    这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),
    如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1 作为健康检查命令。

- ONBUILD 为他人做嫁衣裳
参见:https://docs.kilvn.com/docker_practice/image/dockerfile/onbuild.html

保存镜像

使用 docker save 命令可以将镜像保存为归档文件。
保存镜像为镜像存储文件的命令:
docker save mynginx | gzip > mynginx-latest.tar.gz
加载镜像存储文件到本地镜像库的命令:
docker load -i mynginx-latest.tar.gz

如果我们结合这两个命令以及 ssh 甚至 pv 的话,利用 Linux 强大的管道,我们可以写一个命令完成从一个机器将镜像迁移到另一个机器,并且带进度条的功能:
docker save <镜像名> | bzip2 | pv | ssh <用户名>@<主机名> 'cat | docker load'

导出容器快照到本地的命令:
docker export myubuntu > myubuntu.tar
导入容器快照到本地镜像库的命令:
cat myubuntu.tar | docker import - myubuntu:v1.0

镜像存储文件与容器快照文件的区别:
这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

你可能感兴趣的:(docker,docker,容器,运维)