# Docker虚拟化管理:30分钟教你学会用Docker
关于Docker的官方介绍网上太多了我就不贴了,就实际体验来说Docker可以极大的简化环境搭建及服务部署的操作流程,大大降低部署的时间成本,解放你的双手。
本文不会深入讲解Docker底层架构及运行原理,也不会有一堆架构图贴在这里。该篇旨在让你以最快的速度学会使用Docker,关于Docker的架构及其底层的一些知识,你可以在学会Docker的基本使用之后再去了解。开门见山讲架构聊底层有点容易让人犯迷糊,但在使用Docker之前你至少应该了解他的三大核心组件:仓库、镜像和容器,以及他们之前的关系。本文将通过一个MySQL示例带你了解并使用Docker,待你对Docker有一个基本了解后你再回头去看他的体系架构会容易理解。
# 三大核心组件
仓库:仓库是集中存储镜像的地方,我们本地安装Docker之后,需要从仓库中拉取镜像。可以类比于Maven,有公有仓库和私有仓库之分。
镜像:是一个Linux的文件系统,它里面存放着可以再Linux内核中运行的程序及其数据。
容器:是镜像创建的运行实例,可以把它理解为一个精简版的Linux系统,里面运行着镜像里的程序。
为了更好的让你理解这三者的关系,我打一个不恰当但很形象的比方,镜像就相当于你weixin.exe文件,容器相当于你安装好的微信程序,微信程序(容器)需要你的weixin.exe文件(镜像)来安装(创建),那么仓库就相当于应用商店了,你可以从商店下载你要的.exe文件(镜像)。
微信程序安装完成后你可以选择运行或者关闭,Docker容器一样可以运行和停止,微信程序你可以从系统卸载,Docker容器你同样可以选择删除。
但有一点不同的地方是,weixin.exe文件安装完成你就可以删除了,它和你的微信程序并没有关系,删掉安装文件不影响你微信程序的运行。但是镜像不同,如果有容器正在使用这个镜像,那么这个镜像是不能删除的(删除时会报Error不让你删)。
# 安装Docker
CentOS安装Docker要求:
- 必须是64位操作系统
- 内核版本在3.8以上
你可以通过uname -r
查看你的系统内核:
[root@localhost ~]# uname -r
3.10.0-1062.18.1.el7.x86_64
[root@localhost ~]#
yum方式安装:
yum install docker -y
安装完成你可以通过docker version
查看你的docker版本信息:
[root@localhost ~]# docker version
Client:
Version: 1.13.1
API version: 1.26
Package version: docker-1.13.1-109.gitcccb291.el7.centos.x86_64
Go version: go1.10.3
Git commit: cccb291/1.13.1
Built: Tue Mar 3 17:21:24 2020
OS/Arch: linux/amd64
Server:
Version: 1.13.1
API version: 1.26 (minimum version 1.12)
Package version: docker-1.13.1-109.gitcccb291.el7.centos.x86_64
Go version: go1.10.3
Git commit: cccb291/1.13.1
Built: Tue Mar 3 17:21:24 2020
OS/Arch: linux/amd64
Experimental: false
[root@localhost ~]#
看到如上的信息说明你的Docker安装成功,你可以用 docker info
命令查看更详细的信息。
# 配置镜像加速
为了更愉快的使用Docker你可能还需要配置镜像加速,可以类比于Maven的私服,使用国内的镜像仓库能让你更快的拉取镜像。
执行vim /etc/docker/daemon.json
,修改为如下配置:
{
"registry-mirrors":[
"https://reg-mirror.qiniu.com/",
"https://hub-mirror.c.163.com/"
]
}
重新加载配置及重启Docker服务:
systemctl daemon-reload
systemctl restart docker
执行docker info
你可以看到镜像仓库配置已经生效了。
# 拉取镜像
Docker安装和配置都搞定了,现在你要从苍鹭下载镜像了,这里以 MySQL 5.7 为例:
# 5.7为版本号,你也可以安装其他版本
docker pull mysql:5.7
拉取成功后通过docker images
命令查看本地镜像:
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/mysql 5.7 f965319e89de 3 hours ago 448 MB
[root@localhost ~]#
# 创建容器
有了镜像,你需要用它创建一个容器才能运行,创建并运行MySQL容器:
docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
命令参数说明(后面会有更加详细的说明): -d:后台运行 -p:端口映射,前面的为宿主机端口,后面的为容器端口,这里我将宿主机的3306端口指向了容器的3306端口 --name:为启动的容器命名 -e:指定容器内的环境变量,这里指配置MySQL的Root用户密码为:123456
执行成功后会返回容器ID,查看已创建的容器:docker ps -a
[root@localhost ~]# docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
8e1cd060075db23c61cb31cecb3a3321df92cf56ea7086476cc21e8709382d19
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8e1cd060075d mysql:5.7 "docker-entrypoint..." 3 seconds ago Up 1 second 0.0.0.0:3306->3306/tcp, 33060/tcp mysql
[root@localhost ~]#
可以看到刚才创建的MySQL容器已经在运行了,现在你可以通过${IP}:3306
连接MySQL数据库了(记得放行端口或者关闭防火墙)。
# 挂载目录
通过上面的步骤,你已经通过Docker运行起你的第一个容器MySQL了,而且你也能通过宿主机的端口连接到容器中的MySQL。但这样做还不是很安全,关于这个我们要先简单了解一下容器和宿主机之间的关系。
容器内和宿主机的文件系统是独立的(虽然整个容器也是以文件的形式存放在宿主机的某个目录上的),包括他们之间的网络,也是独立的。刚才运行的MySQL容器有一个 -p 3306:3306
的参数,这个参数就是映射宿主机和容器之间的端口的,你也可以配置成比如 -p 1234:3306
,这样你通过访问宿主机的1234端口就能访问到容器的3306端口。
那么再回到文件系统,容器本身也是一个精简版的Linux系统,只不过他运行在宿主机上依赖于宿主机的硬件。容器内部也是有着一套独立的文件系统的,且随着容器的删除,所有存在于容器内的所有文件都将被清除。刚才我们创建的那个MySQL容器,只要我们删除容器,数据库里的所有数据都将清除,这显然不是我们想看到的。
Docker的run
命令提供了一个-v
的参数,允许我们容器内部目录挂载为宿主机的本地目录,这样容器内的程序在操作这个目录时,其实操作的是宿主机的目录。那么我们可以把程序运行的关键数据挂载到宿主机上的目录,比如MySQL的数据库文件,程序运行的日志文件等等。这样一来当我们在删除容器时,因为这些目录是存在于宿主机的,所以不会随着容器一起删除,从而实现了容器数据的持久化。
还是以刚才的MySQL容器为例,我们先删掉刚才的容器:
# mysql为容器名称,也可以是容器ID,通过 docker ps -a 查看容器信息
docker stop mysql
docker rm mysql
接着用下面的命令创建并运行MySQL容器,增加了一个 -v
参数:
# 在宿主机创建挂载目录
mkdir -p /usr/local/mysql/conf
mkdir -p /usr/local/mysql/logs
mkdir -p /usr/local/mysql/data
# 创建并运行容器
docker run -p 3306:3306 --name mysql \
-v /usr/local/mysql/conf:/etc/mysql \
-v /usr/local/mysql/logs:/var/log/mysql \
-v /usr/local/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql:5.7
-v /usr/local/mysql/data:/var/lib/mysql
表示将宿主机的
/usr/local/mysql/data
目录挂载到容器内的/var/lib/mysql
目录,那么容器内的MySQL程序操作的数据实际上是写入了宿主机的/usr/local/mysql/data
目录了,其他两项同理。这里挂载的三个目录分别为数据库运行的配置、日志和数据,也是MySQL程序最重要的数据,即便这个容器删除了,只要这些数据还在,我们就能通过重新创建容器恢复全部数据。
而且这样挂载以后,我们无需进入容器,直接在宿主机操作这几个目录里的文件就能同步体现到容器内部,比如修改一些配置,导出数据之类的,不用进入容器直接在宿主机操作即可。
用以上命令运行容器之后,在你的宿主机的/usr/local/mysql/data
目录就能看到MySQL运行生成的数据库文件了:
Docker的数据卷挂载(我习惯称之为挂载目录)功能非常重要,我们运行的任何程序都有需要持久化的数据,如果这些数据直接放在容器内部是非常不安全的。而且挂载目录还可以实现直接在宿主机操作容器内的数据,也能做到容器间的数据共享,用Docker一定要养成挂载重要数据到宿主机的习惯。
# 错误排查
上面这个命令是直接后台运行容器的,如果需要调试可以把 -d
参数修改为 -it
参数以在前台运行,在某些情况下你很可能会遇到类似于下面这些错误(可以通过前台运行查看到):
chown: changing ownership of '/var/lib/mysql/': Permission denied
# 或者
mysqld: Can't create/write to file '/var/lib/mysql/is_writable' (Errcode: 13 - Permission denied)
如果出现上述问题,那么你需要关闭SELINUX
,方法如下:
临时关闭:
setenforce 0
永久关闭:
vim /etc/selinux/config
,修改SELINUX
的值为disabled
然后重启机器即可,看图:
再次运行容器就能看到成功提示了。
# 常用命令
通过上面的示例基本已经知道了Docker是怎样工作的,下面是一些基本命令,包括最常用的目录挂载功能等命令说明:
docker version|info
# 显示Docker信息,常用于查看Docker版本或检测Docker是否正确安装。
docker images
# 列出机器上的镜像(images)及信息:REPOSITORY、TAG、IMAGE ID、CREATED、SIZE。
# IMAGE ID列其实是缩写,要显示完整则带上--no-trunc选项。
# -a 列出本地所有的镜像(含中间映像层,默认情况下,过滤掉中间映像层)
# -no-trunc 显示完整的镜像信息
# -q 静默模式,只显示镜像ID
docker search tomcat
# 在docker index中搜索image,搜索的范围是官方镜像和所有个人公共镜像。NAME列的 / 后面是仓库的名字。
docker pull tomcat
# 从docker registry server 中下拉image或repository。
# 语法:docker pull [OPTIONS] NAME[:TAG|@DIGEST]
# -a 拉取所有 tagged 镜像
# --disable-content-trust 忽略镜像的校验,默认开启
# 上面的命令没有指定参数,在docker v1.2版本及以前,会下载官方镜像的tomcat仓库里的所有镜像
# 而从v.13开始只会下载tag为latest的镜像,也可以明确指定具体的镜像
# 如:docker pull tomcat:8,后面的8为tomcat版本tag。
docker run -d --name tomcat -p 8081:8080 tomcat:8
# 启动容器,语法为:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
# -d 后台运行容器,并返回容器ID,不使用-d我们会看到tomcat启动日志,此时Ctrl+C容器会停止运行。
# -e 指定容器内的环境变量
# -name 指定容器的名称,如果不指定docker会帮我们取一个名字。
# -p 端口映射,宿主机端口:容器端口,上面我们就将宿主机的8081映射到容器的8080端口
# 此时我们访问宿主机的ip:8081就能访问容器内的tomcat了。
# -i 以交互模式运行容器,通常与 -t 同时使用。
# -t 为容器重新分配一个伪输入终端,通常与 -i 同时使用
# -v 目录挂载,本地目录:容器目录
docker ps
# 查看容器的信息,默认显示当前正在运行中的容器
# -a 查看所有容器
# -l 显示最新启动的一个容器(包括已停止的)
docker start|stop|restart CONTAINER_ID
# 启动/停止/重启容器,需要用到容器ID,可以使用docker ps -a 查看所有容器的ID。
docker attach CONTAINER_ID|NAME
# 进入容器,后面跟容器ID或者NANE,可以使用docker ps -a 查看所有容器的ID。
# 可以认为这是一个过时的命令,更多的docker用户会考虑使用docker exec来实现相同的功能
# 但是出于docker官方并没有删除这个命令,我们还是有必要学习一下的。
# 进入容器后输入exit命令可以退出,同时容器停止运行,如若想退出但不停止容器,可以用快捷键Ctrl+P+Q退出。
docker exec -i -t CONTAINER_ID|NAME /bin/bash
# 进入容器,推荐使用这种方式。
# 语法:docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
# -i 以交互模式运行容器,通常与 -t 同时使用。
# -t 为容器重新分配一个伪输入终端,通常与 -i 同时使用
# 示例:docker exec -it mycentos /bin/sh /root/start.sh
# 上面这条命令表示在容器 mycentos 中以交互模式执行容器内 /root/start.sh 脚本
docker rm CONTAINER_ID|NAME
# 删除一个或多少容器,如:docker rm $(docker ps -a -q),为删除所有停止的容器
# -l 移除容器间的网络连接,而非容器本身
# -v 删除与容器关联的卷
# -f 通过SIGKILL信号强制删除一个运行中的容器
docker rmi CONTAINER_ID|NAME
# 删除本地一个或多少镜像
# -f 强制删除;
# --no-prune 不移除该镜像的过程镜像,默认移除;
docker build
# 使用 Dockerfile 创建镜像,语法:docker build [OPTIONS] PATH | URL | -
# -f 指定要使用的Dockerfile路径
# -t,--tag 指定镜像的名字及标签:name:tag或者name,可以在一次构建中为一个镜像设置多个标签
# Dockerfile
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
初学者一开始可不必关注Dockerfile,待你熟悉Docker的整个体系结构及其运行方式后,再回头看这个就会一目了然了。
FROM
# 指定基础镜像,必须为第一个命令
FROM <image>
FROM <image>:<tag>
FROM <image>@<digest>
# 示例
FROM docker.io/centos:latest
MAINTAINER
# 维护者信息
MAINTAINER <name>
# 示例
MAINTAINER guitu "xianjian-mail@qq.com"
RUN
# 用于在镜像容器中执行命令,其有以下两种命令执行方式
#shell执行
RUN <command>
#exec执行
RUN ["executable", "param1", "param2"]
#示例
RUN apk update
RUN ["executable", "param1", "param2"]
RUN ["/etc/execfile", "arg1", "arg1"]
# RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。
# 如果不想使用这些缓存镜像,可以在构建时指定--no-cache参数,如:docker build --no-cache
ADD
# 将当前目录下的文件复制到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget
ADD <src>... <dest>
ADD ["<src>",... "<dest>"]
# 第二中形式用于支持包含空格的路径
# 示例
ADD hom* /mydir/ # 添加所有以"hom"开头的文件
ADD hom?.txt /mydir/ # ? 替代一个单字符,例如:"home.txt"
ADD test relativeDir/ # 添加 "test" 到 `WORKDIR`/relativeDir/
ADD test /absoluteDir/ # 添加 "test" 到 /absoluteDir/
# <src>可以是Dockerfile所在目录的一个相对路径,也可以是一个URL,还可以是一个tar文件(会自动解压为目录)
# 如果文件或目录不与Dockerfile在同一目录会提示 no such file or directory
COPY
# 功能类似ADD,但是是不会自动解压文件,也不能访问网络资源
CMD
# 构建容器后调用,也就是在容器启动时才进行调用。
CMD ["executable","param1","param2"] (执行可执行文件,优先)
CMD ["param1","param2"] (设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数)
CMD command param1 param2 (执行shell内部命令)
# 示例
CMD echo "This is a test." | wc -
CMD ["/usr/bin/wc","--help"]
# CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
ENTRYPOINT
# 配置容器,使其可执行化。配合CMD可省去"application",只使用参数。
ENTRYPOINT ["executable", "param1", "param2"] (可执行文件, 优先)
ENTRYPOINT command param1 param2 (shell内部命令)
#示例
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
# ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。
# Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。
LABEL
# 用于为镜像添加元数据
LABEL <key>=<value> <key>=<value> <key>=<value> ...
# 示例
LABEL version="1.0" description="这是一个Web服务器" by="IT笔录"
# 使用LABEL指定元数据时,一条LABEL指定可以指定一或多条元数据,指定多条元数据时不同元数据之间通过空格分隔。
# 推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。
ENV
# 设置环境变量
ENV <key> <value>
# <key>之后的所有内容均会被视为其<value>的组成部分,因此,一次只能设置一个变量
ENV <key>=<value> ...
# 可以设置多个变量,每个变量为一个"<key>=<value>"的键值对
# 如果<key>中包含空格,可以使用\来进行转义,也可以通过""来进行标示;另外,反斜线也可以用于续行
# 示例
ENV JAVA_HOME /docker/jdk
VOLUME
# 用于指定持久化目录
VOLUME ["/path/to/dir"]
# 示例
VOLUME ["/data"]
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
# 一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:
# 卷可以容器间共享和重用
# 容器并不一定要和其它容器共享卷
# 修改卷后会立即生效
# 对卷的修改不会对镜像产生影响
# 卷会一直存在,直到没有任何容器在使用它
WORKDIR
# 工作目录,类似于cd命令
WORKDIR /path/to/workdir
# 示例
WORKDIR /a (这时工作目录为/a)
WORKDIR b (这时工作目录为/a/b)
WORKDIR c (这时工作目录为/a/b/c)
# 通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。
# 在使用docker run运行容器时,可以通过-w参数覆盖构建时所设置的工作目录。
USER
# 指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。
# 使用USER指定用户时,可以使用用户名、UID或GID,或是两者的组合。
# 当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户
USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group
# 示例
USER www
# 使用USER指定用户后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT都将使用该用户。
# 镜像构建完成后,通过docker run运行容器时,可以通过-u参数来覆盖所指定的用户。
ARG
# 用于指定传递给构建运行时的变量
ARG <name>=[<default value>]
# 示例
ARG site
ARG build_user=www
ONBUILD
# 用于设置镜像触发器
ONBUILD [INSTRUCTION]
# 示例
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
# 当所构建的镜像被用做其它镜像的基础镜像,该镜像中的触发器将会被钥触发
# 构建镜像
了解了上面的命令之后,我们可以尝试着创建我的第一个自制镜像了,以下是我的Dockerfile示例:
#使用的基础镜像
FROM docker.io/centos:latest
#作者信息
MAINTAINER guitu "xianjian-mail@qq.com"
#安装SVN
RUN yum install -y subversion
#添加JAVA环境,下方的文件请换成你Dockerfile目录下的文件,压缩包在构建镜像时会自动解压
ADD jdk-8u231-linux-x64.tar.gz /docker/
ADD apache-tomcat-8.0.53.tar.gz /docker/
#添加环境变量
ENV JAVA_HOME /docker/jdk1.8.0_231
ENV TOMCAT_HOME /docker/apache-tomcat-8.0.53
ENV PATH $PATH:$JAVA_HOME/bin:$TOMCAT_HOME/bin
#指定工作目录
WORKDIR /docker/apache-tomcat-8.0.53
#暴露8080端口
EXPOSE 8080
#启动时运行tomcat
CMD ["bin/startup.sh"]
通过Dockerfile创建镜像:
docker build -t mytomcat8:v0.1 .
通过Dockerfile文件构建镜像完成。