一、概述
Docker是什么?
Docker是一种工具,类似于一个虚拟箱子,可以把软件和它运行所需要的环境打包放进这个箱子里。这样,无论这个箱子放到哪里,软件都能像在原来的地方一样运行,不会因为换了地方就出问题。
假设你有一个很喜欢的玩具,但是玩这个玩具需要你房间里的电源插座、电视和游戏手柄。如果你去了别的地方,没有这些东西,玩具就没法玩了。Docker就像是一个能装下所有这些东西的超级玩具箱。你只要带着这个玩具箱去任何地方,都能打开箱子玩你的玩具,不用担心少了电视或者插座。
同样的,在软件开发中,开发者可以使用Docker把软件应用程序和它所有运行所需的环境(例如特定版本的程序语言运行时、库文件等等)全部打包进一个可移植的容器里。这样这个“容器”就可以被运送到任何安装了Docker的电脑上去运行,而不会因为换了环境而出现问题。
这个技术方便了软件开发者、测试人员和运维人员,减少了那些“它在我的电脑上可以正常工作啊,怎么到了服务器上就出错了”的情况。
Docker的优势
作为一种流行的容器化平台,Docker有着许多显著的优点:
- 隔离性:保证应用之间相互独立,避免冲突。
- 轻量级:无需额外的操作系统,资源占用少,启动快。
- 便携性:在任何支持Docker的环境中都能一致运行。
- 可重复性:通过Dockerfile确保环境一致性和重复构建。
- 微服务架构支持:便于构建和维护微服务架构。
- 加速CI/CD:适合持续集成和持续部署,快速迭代和部署。
- 社区和生态系统:强大的社区支持和丰富的资源库。
二、使用
下面是在centos虚拟机安装Docker,首先需要准备一个linux系统,建议使用VMware安装虚拟机,安装虚拟机这里就不做演示了
VMware官网:VMware Desktop Hypervisors for Windows, Linux, and Mac
centos7的迷你镜像:https://pan.baidu.com/s/1tVMfWsQTePlw8nFPxpbtMQ?pwd=7633
提取码:7633
安装Docker
1、卸载Docker
安装之前先学卸载,假如系统中已经有了docker,可以用下面的命令卸载:
yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine
2、配置Docker的yum库
首先要安装一个yum工具
yum install -y yum-utils
安装成功后,执行命令,配置Docker的yum源:
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
3、安装Docker
yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
4.启动和校验
# 启动Docker
systemctl start docker# 停止Docker
systemctl stop docker# 重启
systemctl restart docker# 设置开机自启
systemctl enable docker# 执行docker ps命令,如果不报错,说明安装启动成功
docker ps
5.配置镜像加速
以阿里云镜像加速为例。
阿里云-计算,为了无法计算的价值首先访问阿里云网站:阿里云-计算,为了无法计算的价值
注册一个账号
在首页的产品中,找到阿里云的容器镜像服务:
往下滑可以看到配置的文档说明:
具体命令如下
# 创建目录
mkdir -p /etc/docker# 复制内容,注意把其中的镜像加速地址改成你自己的
tee /etc/docker/daemon.json <<-'EOF'
{"registry-mirrors": ["https://xxxx.mirror.aliyuncs.com"]
}
EOF# 重新加载配置
systemctl daemon-reload# 重启Docker
systemctl restart docker
使用docker一键部署软件
这里拿部署mysql举例:
如果是利用传统方式部署MySQL,大概的步骤有:
-
搜索并下载MySQL安装包
-
上传至Linux环境
-
编译和配置环境
-
安装
而使用Docker安装,仅仅需要一步即可,在命令行输入下面的命令(建议采用CV大法):
docker run -d \--name mysql \-p 3306:3306 \-e TZ=Asia/Shanghai \-e MYSQL_ROOT_PASSWORD=123 \mysql
在输入这些命令之后,docker就会自动搜索下载镜像,然后创建并运行容器。全程全自动,非常方便好用!如果这些换成手动按照的话,还必须手动解决安装包不同、环境不同的、配置不同的问题!而使用Docker,这些完全不用考虑。就是因为Docker会自动搜索并下载MySQL。
注意:这里下载的不是安装包,而是镜像。镜像中不仅包含了MySQL本身,还包含了其运行所需要的环境、配置、系统级函数库。因此它在运行时就有自己独立的环境,就可以跨系统运行,也不需要手动再次配置环境了。这套独立运行的隔离环境我们称为容器。
那么docker是从哪里下载这些镜像的呢?
实际上,Docker官方提供了一个专门管理、存储镜像的网站,并对外开放了镜像上传、下载的权利:Docker Hub Container Image Library | App Containerization
Docker官方提供了一些基础镜像,然后各大软件公司又在基础镜像基础上,制作了自家软件的镜像,全部都存放在这个网站。这个网站就成了Docker镜像交流的社区
基本上我们常用的各种软件都能在这个网站上找到,我们甚至可以自己制作镜像上传上去。
像这种提供存储、管理Docker镜像的服务器,被称为DockerRegistry,可以翻译为镜像仓库。DockerHub网站是官方仓库,阿里云、华为云会提供一些第三方仓库,我们也可以自己搭建私有的镜像仓库。
官方仓库在国外,下载速度较慢,一般我们都会使用第三方仓库提供的镜像加速功能,提高下载速度。而企业内部的机密项目,往往会采用私有镜像仓库。
总之,镜像的来源有两种:
-
基于官方基础镜像自己制作
-
直接去DockerRegistry下载
总结:Docker本身包含一个后台服务,我们可以利用Docker命令告诉Docker服务,帮助我们快速部署指定的应用。Docker服务部署应用时,首先要去搜索并下载应用对应的镜像,然后根据镜像创建并允许容器,应用就部署完成了。
图示:
所以使用docker部署软件确实是非常的实用方便,那么刚才执行的命令真正的意思到底是什么呢?
这里提供一些docker的常用命令:
任务 | Docker命令 |
---|---|
列出所有运行的容器 | docker ps |
列出所有容器 | docker ps -a |
运行一个容器 | docker run [options] image [command] |
停止一个运行中的容器 | docker stop [container_id or name] |
启动一个容器 | docker start [container_id or name] |
删除一个容器 | docker rm [container_id or name] |
拉取一个镜像 | docker pull [image name] |
构建一个镜像 | docker build -t [name]:[tag] . |
列出本地镜像 | docker images |
删除一个镜像 | docker rmi [image_id] |
查看镜像、容器或卷的详细信息 | docker inspect [name or id] |
执行容器中的命令 | docker exec [options] [container] [command] |
查看容器日志 | docker logs [container_id or name] |
具体查看官方文档:Use the Docker command line | Docker Docs
而刚才使用的其实就是运行一个容器,其中image就代表镜像名,也就是刚才命令中的mysql,--name代表运行的容器名,-p代表端口映射:将宿主机端口映射到容器内端口,也就是将虚拟机的3306端口映射到容器内的3306端口(每一个创建的容器都是隔离的封闭的环境),-e TZ=Asia/Shanghai是设置时区,-e MYSQL_ROOT_PASSWORD=123是设置mysql的默认密码,这些都属于options(
可选参数),
镜像的名称不是随意的,而是要到DockerRegistry中寻找,镜像运行时的配置也不是随意的,要参考镜像的帮助文档,这些在DockerHub网站或者软件的官方网站中都能找到。安装一个镜像之前,可以到DockerRegistry中寻找对应的镜像名称和版本,阅读相关配置进行安装。
数据卷
什么是数据卷?
数据卷(volume)是一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。
容器提供程序的运行环境,但是程序运行产生的数据、程序运行依赖的配置都应该与容器解耦,此时就可以使用数据卷将容器内的指定目录挂载到容器外,方便进行数据管理和配置更改,即使容器被删除,挂载到数据卷的数据仍然存在,并且多个容器可以挂载同一个数据卷,共享数据。
数据卷命令:
命令 | 说明 | 文档地址 |
---|---|---|
docker volume create | 创建数据卷 | docker volume create |
docker volume ls | 查看所有数据卷 | docs.docker.com |
docker volume rm | 删除指定数据卷 | docs.docker.com |
docker volume inspect | 查看某个数据卷的详情 | docs.docker.com |
docker volume prune | 清除数据卷 | docker volume prune |
那么如何进行容器与数据卷的挂载呢?
很简单,这里拿nginx举例:
docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
只需要在创建容器时多加一个选项指定数据卷就行了。
注意:容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,是不能设置数据卷的。而且创建容器的过程中,数据卷会自动创建。/var/lib/docker/volumes
这个目录就是默认的存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为/数据卷名/_data
上面那行命令中,-v html:/usr/share/nginx/html 便是将容器内的/usr/share/nginx/html目录挂载到/var/lib/docker/volumes/html/_data下,此时对/var/lib/docker/volumes/html/_data目录进行操作,容器内的/usr/share/nginx/html目录也会随之变化,这就是在容器目录和宿主机目录构建映射的桥梁。
然而默认存放数据卷的目录太深,对其进行操作往往比较麻烦,我们也可以直接将容器目录与宿主机指定目录挂载,当然文件也是可以的:
# 挂载本地目录
-v 本地目录:容器内目录
# 挂载本地文件
-v 本地文件:容器内文件
需要注意的是:本地目录或文件必须以 /
或 ./
开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名。
例:
-v mysql:/var/lib/mysql # 会被识别为一个数据卷叫mysql,运行时会自动创建这个数据卷
-v ./mysql:/var/lib/mysql # 会被识别为当前目录下的mysql目录,运行时如果不存在会创建目录
构建镜像
前面我们一直在使用别人准备好的镜像,那如果我要部署一个Java项目,把它打包为一个镜像该怎么做呢?
镜像结构
要想自己构建镜像,必须先了解镜像的结构。
之前我们说过,镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境、配置,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖。
因此,自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包而成。
举个例子,我们要从0部署一个Java应用,大概流程是这样:
-
准备一个linux服务(CentOS或者Ubuntu均可)
-
安装并配置JDK
-
上传Jar包
-
运行jar包
那因此,我们打包镜像也是分成这么几步:
-
准备Linux运行环境(java项目并不需要完整的操作系统,仅仅是基础运行环境即可)
-
安装并配置JDK
-
拷贝jar包
-
配置启动脚本
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一id,称为Layer(层)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。
例如,第一步中需要的Linux运行环境,通用性就很强,所以Docker官方就制作了这样的只包含Linux运行环境的镜像。我们在制作java镜像时,就无需重复制作,直接使用Docker官方提供的CentOS或Ubuntu镜像作为基础镜像。然后再搭建其它层即可,这样逐层搭建,最终整个Java项目的镜像结构如图所示:
Dockerfile
由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以Docker就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给Docker去执行即可。
而这种记录镜像结构的文件就称为Dockerfile,其对应的语法可以参考官方文档:
Dockerfile reference | Docker Docs
其中的语法比较多,比较常用的有:
指令 | 说明 |
---|---|
FROM | 指定基础镜像 |
ENV | 设置环境变量,可在后面指令使用 |
COPY | 拷贝本地文件到镜像的指定目录 |
RUN | 执行Linux的shell命令,一般是安装过程的命令 |
EXPOSE | 指定容器运行时监听的端口,是给镜像使用者看的 |
ENTRYPOINT | 镜像中应用的启动命令,容器运行时调用 |
例如,要基于Ubuntu镜像来构建一个Java应用,其Dockerfile内容如下:
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录、容器内时区
ENV JAVA_DIR=/usr/local
ENV TZ=Asia/Shanghai
# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
# 设定时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 安装JDK
RUN cd $JAVA_DIR \&& tar -xf ./jdk8.tar.gz \&& mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 指定项目监听的端口
EXPOSE 8080
# 入口,java项目的启动命令
ENTRYPOINT ["java", "-jar", "/app.jar"]
因为Linux系统环境、JDK环境这两层基本上都是不变的,只有上面的3层不同(因为jar包不同)。如果每次制作java镜像都重复制作前两层镜像,很麻烦。
所以,就有人提供了基础的系统加JDK环境,我们在此基础上制作java镜像,就可以省去JDK的配置了:
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
当Dockerfile文件写好以后,就可以利用命令来构建镜像了。
# 进入镜像目录
cd /root/demo
# 开始构建
docker build -t docker-demo:1.0 .
命令说明:
-
docker build
: 就是构建一个docker镜像 -
-t docker-demo:1.0
:-t
参数是指定镜像的名称(repository
和tag
) -
.
: 最后的点是指构建时Dockerfile所在路径,由于我们进入了demo目录,所以指定的是.
代表当前目录,也可以直接指定Dockerfile目录:
# 直接指定Dockerfile目录
docker build -t docker-demo:1.0 /root/demo
网络
容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很可能容器的IP会发生变化,连接会失败。
docker network | Docker Docs所以,我们必须借助于docker的网络功能来解决这个问题,官方文档:docker network | Docker Docs
常见命令有:
命令 | 说明 | 文档地址 |
---|---|---|
docker network create | 创建一个网络 | docker network create |
docker network ls | 查看所有网络 | docs.docker.com |
docker network rm | 删除指定网络 | docs.docker.com |
docker network prune | 清除未使用的网络 | docs.docker.com |
docker network connect | 使指定容器连接加入某网络 | docs.docker.com |
docker network disconnect | 使指定容器连接离开某网络 | docker network disconnect |
docker network inspect | 查看网络详细信息 | docker network inspect |
-
在自定义网络中,可以给容器起多个别名,默认的别名是容器名本身
-
在同一个自定义网络中的容器,可以通过别名互相访问
DockerCompose
部署一个简单的java项目,其中可能只有几个容器,还不算麻烦,但是稍微复杂的项目,其中还会有各种各样的其它中间件,需要部署的东西远不止几个。如果还像之前那样手动的逐一部署,就太麻烦了。
而Docker Compose就可以帮助我们实现多个相互关联的Docker容器的快速部署。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器。
基本语法,参考官方文档:概览 |Docker 文档
docker-compose文件中可以定义多个相互关联的应用容器,每一个应用容器被称为一个服务(service)。由于service就是在定义某个应用的运行时参数,因此与docker run
参数非常相似。
举例来说,用docker run部署MySQL的命令如下:
docker run -d \--name mysql \-p 3306:3306 \-e TZ=Asia/Shanghai \-e MYSQL_ROOT_PASSWORD=123 \-v ./mysql/data:/var/lib/mysql \-v ./mysql/conf:/etc/mysql/conf.d \-v ./mysql/init:/docker-entrypoint-initdb.d \--network hmallmysql
如果用docker-compose.yml
文件来定义,就是这样:
version: "3.8"services:mysql:image: mysqlcontainer_name: mysqlports:- "3306:3306"environment:TZ: Asia/ShanghaiMYSQL_ROOT_PASSWORD: 123volumes:- "./mysql/conf:/etc/mysql/conf.d"- "./mysql/data:/var/lib/mysql"networks:- new
networks:new:name: hmall
可以看到docker run 参数和docker compose 指令其实是一一对应的,对比如下:
docker run 参数 | docker compose 指令 |
---|---|
--name | container_name |
-p | ports |
-e | environment |
-v | volumes |
--network | networks |
明白了其中的对应关系,编写docker-compose
文件就非常简单了。
编写好docker-compose.yml文件,就可以部署项目了。常见的命令:docker compose CLI 概述 |Docker 文档
基本语法如下:
docker compose [OPTIONS] [COMMAND]
其中,OPTIONS和COMMAND都是可选参数,比较常见的有:
类型 | 参数或指令 | 说明 |
---|---|---|
Options | -f | 指定使用的 docker-compose 文件,后面跟文件路径 |
-p | 指定项目名称,避免与其他项目使用默认路径时的冲突 | |
Commands | up | 创建并启动所有在 docker-compose.yaml 文件中定义的服务 |
down | 停止并移除所有容器及网络,也可选用来移除 volumes | |
ps | 列出所有在当前目录的 docker-compose.yaml 文件中定义的服务 | |
logs | 查看一个或多个服务的日志 | |
stop | 停止一个或多个运行中的服务 | |
start | 启动一个或多个已经存在但被停止的服务 | |
restart | 重启一个或多个服务 | |
top | 显示一个或多个服务的运行中的进程 | |
exec | 在一个运行的容器中执行命令 |
举例:
# 启动所有, -d 参数是后台启动
docker compose up -d