1 前提须知 

1.1 什么是Docker?它的作用是什么?

docker就是一种容器技术,我们可以用它来实现应用的快捷部署,特别是在分布式服务场景下的应用;

微服务虽然具备各种各样的优势,但服务的拆分通用给部署带来了很大的麻烦。

  • 分布式系统中,依赖的组件非常多,不同组件之间部署时往往会产生一些冲突。
  • 在数百上千台服务中重复部署,环境不一定一致,会遇到各种问题

例如一个项目中,部署时需要依赖于node.js、Redis、RabbitMQ、MySQL等,这些服务部署时所需要的函数库、依赖项各不相同,甚至会有冲突。给部署带来了极大的困难。

1.2 Docker怎么解决依赖兼容问题

Docker为了解决依赖的兼容问题的,采用了两个手段:

  • 将应用的Libs(函数库)、Deps(依赖)、配置与应用一起打包
  • 将每个应用放到一个隔离容器去运行,避免互相干扰

 当一个项目中的工程用到某些依赖时【例如mysql】,只要docker启动包含有这些依赖库的镜像,那么该工程就可以配置这个镜像中的资源,满足工程的需求;

例如:redis集群,docker可以拉取redis镜像之后,构建一个redis容器集群,来解决实际项目中的一些场景问题;而且只要拉取一次镜像,重复构建不同的容器就可以了。

2 docker架构

Docker是一个C/S架构的程序,有两部分组成

  • 服务端(server):Docker守护进程,负责处理Docker指令,管理镜像、容器等
  • 客户端(client):通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令。

2.1 镜像、容器、DockerHub(镜像仓库)

镜像
Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像

相当于电脑上的“程序”

容器:
镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器进程做隔离,对外不可见。

相当于电脑上一个正在运行的程序,即进程

2.1.1镜像和容器的关系:

镜像=程序,那么容器=进程;

镜像相当于是java中的类,容器相当于java中的对象;

一个镜像可以构建多个不同的容器(一个类可以new多个不同属性的对象)

dockerhub:

  • DockerHub是一个官方的Docker镜像的托管平台。这样的平台称为Docker Registry。
  • 国内也有类似于DockerHub 的公开服务,比如 网易云镜像服务阿里云镜像库等。

由于docker默认会从国外的镜像平台去拉取容器,这样网速就会很慢,拉取的速度就会很慢;

所以后面关于docker的使用中,会设置docker的镜像源,设置从国内一些主流的镜像平台:阿里云去拉取镜像;

2.2 架构小结

镜像:

  • 将应用程序及其依赖、环境、配置打包在一起

容器:

  • 镜像运行起来就是容器,一个镜像可以运行多个容器

Docker结构:

  • 服务端:接收命令或远程请求,操作镜像或容器
  • 客户端:发送命令或者请求到Docker服务端

DockerHub:

  • 一个镜像托管的服务器,类似的还有阿里云镜像服务,统称为DockerRegistry

3 Docker的基本操作

3.1Linux系统(CentOS_7)上的Docker安装与卸载

其实阿里云官网就有关于的dokcer在linux系统各版本的安装说明:
Docker CE 镜像源站-阿里云开发者社区

3.1.1 卸载

直接复制执行

yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-selinux \
                  docker-engine-selinux \
                  docker-engine \
                  docker-ce

3.1.2 安装

在安装之前,一定要确保自己的虚拟机能够联网,因为安装包都时根据命令下载的,所以最好使用ping命令检查一下自己的网络环境。(云服务器就当我没说)

openresty docker 镜像下载 docker镜像版本_运维

如上图,能够一直刷新内容不丢包,说明虚拟机已经联网了。

【1】安装yum包
yum install -y yum-utils device-mapper-persistent-data lvm2
【2】设置阿里云镜像源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
【3】设置自己的镜像仓库远程地址(自己之后的镜像会云备份存到哪里,这里设置阿里云)
sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
【4】刷新缓存
yum makecache fast
【5】安装
yum install docker-ce docker-ce-cli containerd.io -y  #安装最新版
  
yum install docker-ce-20.10.1-3.el7  #安装指定版本

第一行命令是安装了3个软件,其中=》

docker-ce为社区免费版本;

docker-ce-cli和containerd.io 这2个在docker-compose会用到;

【6】设置阿里云镜像加速(创建配置文件、填写专属链接)

登录阿里云,进入 “容器镜像服务” 控制台

【控制台>镜像工具>镜像加速器>copy地址】

注册后,可以在“镜像工具-镜像加速器” 来获得个人的 docker 加速链接地址,地址找到后,执行下面的命令:

#1 创建目录地址(etc文件夹一般用来存放linux的系统配置文件)
  mkdir -p /etc/docker
  
  #2 创建 daemon 文件,并编辑内容
  vi /etc/docker/daemon.json

 这个文件的内容格式如下,但是里面的https地址,需要自己去注册阿里云官网账号,然后去拿自己的专属链接写进去。

这一步最好设置一下,不然拉取镜像会非常慢,除非搭建私服dokerhub

{
    "registry-mirrors": ["https://t3wkb3f3.mirror.aliyuncs.com"]
 }
 【7】系统加载配置文件
# 加载配置文件  
systemctl daemon-reload

 执行命令:docker info

如图可以看到自己的刚才配置的专属链接地址,说明配置成功了

openresty docker 镜像下载 docker镜像版本_容器_02

【8】docker 自动补全

依次执行下面的命令

yum install -y bash-completion
  
  source /usr/share/bash-completion/bash_completion
  
  source /usr/share/bash-completion/completions/docker

这样在后面学习docker指令的时候,可以方便指令的补全

【9】关闭防火墙

如果docker无法启动,说明防火墙没有关闭,所以我们可以关闭防火墙,并且设置防火墙永久开机不自启

# 关闭
  systemctl stop firewalld
  # 禁止开机启动防火墙
  systemctl disable firewalld
【10】开启、关闭、状态、开机自启
systemctl start docker  # 启动docker服务

systemctl stop docker  # 停止docker服务

systemctl restart docker  # 重启docker服务

systemctl status docker # 检查docker状态

systemctl enable docker # 设置docker服务开机自启

3.2 镜像基本命令

3.2.1 镜像名称

镜名称一般分两部分组成:[repository]:[tag],在没有指定tag时,默认是latest,代表最新版本的镜像

例如:mysql:5.7 表示这个镜像名称为mysql,版本为5.7

在拉取、导出、查看、构建等命令时,都需要配合镜像名称实用。

3.2.2 镜像常用指令

openresty docker 镜像下载 docker镜像版本_docker_03

docker images				查看所有镜像
docker images | grep imageName 查看指定镜像
docker pull nginx:1.14.1	下载镜像(若不加版本号,则下载最新版)
docker rmi 605c77e624dd		按照镜像的image id 删除镜像,也可以写镜像和版本号删除,如:
docker rmi nginx:latest 	删除指定镜像
docker save -o [保存的目标文件名称] [镜像名称] 	导出镜像
docker load -i nginx.tar 	加载本地镜像
docker build 				创建镜像
docker push 			 	推送镜像

3.3 容器基本命令

1 创建并运行容器:
	docker run --name [自定义容器名字,如nginx] -p 80:80 -d nginx:1.14.1   
		-d:后台运行。 
		80:80: 宿主机端口:容器端口
  
2 进入容器:
	docker exec -it nginx bash
		-it : 给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互
		bash:进入容器后执行的命令,bash是一个linux终端交互命令
  
3 修改容器内文件内容:如修改nginx的index.html内容:(此命令仅做展示,日后有更好的方法)
 先进入指定目录:目录可以在docker官网该镜像文档中的How to use this image栏查询。
	cd /usr/share/nginx/html
	sed -i -e 's#Welcome to nginx#欢迎您#g' -e 's#<head>#<head><meta charset="utf-8">#g' index.html
 
4 查看容器状态:

	docker ps
	docker ps -a  	查看所有容器,包括已经停止的

5 查看容器日志:
	docker logs   (加 -f 可以持续查看日志) 

6 容器开启
	docker start
7 容器停止
	docker stop
8 容器暂停
	docker pause
9 容器继续
	docker unpause
10 退出容器
	exit
11 强制删除容器(区别于dockers rmi imageName 删除镜像)
	docker rm -f -v nginx
 3.3.1 举例——docker run 构建容器

将镜像构建成容器

docker run 
--name containerName 
-p XX:XX 
-d 
imageName:tag 创建并运行容器


## 命令解读:
docker run :创建并运行一个容器
--name : 给容器起一个名字,比如:mysql、nginx
-p :将宿主机端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口
-d:后台运行容器
imageName:tag => 镜像名称:版本号,例如nginx:1.9.1
##
3.3.2 举例——docker exec -it  进入容器

 进入一个构建好的容器

例如,nginx默认端口是8080,但是如果我想修改端口号为80怎么办呢?

修改linux上的nginx:进入conf文件夹=》修改nginx.conf=》wq保存退出

但是容器它是隔离于linux系统的,所以要想容器的conf文件夹,首先我们需要进入nginx容器内部

docker exec -it containerName bash 进入容器

命令解读:
docker exec :进入容器内部,执行一个命令
-it : 给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互
containerName :要进入的容器的名称
bash:进入容器后执行的命令,bash是一个linux终端交互命令

4 数据卷挂载和目录挂载

4.1 数据卷挂载

4.1.1 什么是数据卷?

数据卷是一个虚拟目录,同时它也对应着宿主机(服务器)下的某一个实际路径。

也就是说,数据卷可以理解为宿主机某一个路径的代名词,进入数据卷查看它的信息就可以查看它到底对应宿主机下的哪一个文件夹

4.1.2 数据卷的作用是什么?

数据卷可以在容器构建时(执行docker run时)被挂载到容器内的某一个文件夹,这样在容器构建完成后,数据卷对应的宿主机目录就会同步容器内映射文件夹下的所有数据。

这样就方便了之后在容器运行时,修改这个映射文件夹下数据的操作。因为不需要执行:

docker exec -it 容器名称 bash

而是直接进入对应文件的宿主机目录,就可以同步到容器内了。

这样就很方便实际项目场景中,基建升级,灰度发布!!!

openresty docker 镜像下载 docker镜像版本_容器_04

如上图Nginx容器的构建:

一旦完成数据卷挂载,对容器的一切操作都会作用在数据卷对应的宿主机目录了。

这样,我们操作宿主机的/var/lib/docker/volumes/html目录,就等于操作容器内的/usr/share/nginx/html目录了

4.1.3 如何挂载数据卷

-v 数据卷名称:容器目录

这个容器目录是一定到存在的,不能瞎写。而且一般的实际场景中,挂载的动作肯定是有目的性的,所以不要抬杠说,这个容器目录不知道在哪儿!

4.2 目录挂载

4.2.1 目录挂载和数据卷挂载有啥区别?

目录挂载:将宿主机目录映射到容器目录

数据卷挂载:将数据卷映射到容器目录

聪明的人已经能想到,数据卷的概念之前聊到过,数据卷虽然是一个虚拟目录但是也对应着服务器中的某一个目录==》也就是说,数据卷挂载只是在宿主机目录与容器目录之间的映射做了一层封装而已。

而且通过数据卷挂载的容器,如果要进行修改文件,还得先查看数据卷的宿主机目录,再进入这个目录,最后修改文件;

但是通过目录挂载的容器,我们能很直观的看到目录之间的映射关系,就能方便我们去操作容器里面的文件。

4.2.2 目录挂载和数据卷挂载对比

数据卷挂载nginx

docker run --restart=always -d --name nginx -v html:/usr/share/nginx/html -p 9999:80 nginx

-v html:/usr/share/nginx/html  注意:html是一个数据卷(虚拟目录),不是一个宿主机目录,所以它的前面没有/

目录挂载redis

docker run --restart=always -p 6379:6379 --name redis -v /home/service/redis/conf/redis.conf:/etc/redis/redis.conf  -v /home/service/redis/data:/data -d redis:6.2.6 redis-server /etc/redis/redis.conf --appendonly yes

-v /home/service/redis/conf/redis.conf:/etc/redis/redis.conf 注意:很明显能看出来-v 执行的是2个目录之间的映射

总结:

数据卷挂载和目录挂载最终的目的,都是为了方便我们能够在不进入容器的前提下,去修改容器中的数据;

数据卷挂载:在容器构建完成后,会同步映射目录的内容;

目录挂载:在容器构建完成后,不会同步映射目录的内容,因此采用这类方式去构建容器时,我们需要事先准备好容器中的文件备份,并提前放进宿主机目录下。这样在容器构建完成后,我们才可以通过宿主机目录的文件备份,来实现修改容器内部数据的同步。

(这也是为什么,一些中间件:mysql、redis、nacos等,在构建容器之前,需要创建2个目录:data、conf,并且准备一个配置文件的原因。具体细节,需要自己动手去实操,就明白了。)

4.3 数据卷常用指令

#数据卷命令  docker volume ...
创建数据卷  create
docker volume create html
查看所有数据 ls
docker volume ls
查看数据卷映射的本地目录,方便修改容器文件   inspect
docker volume inspect html
删除指定数据卷 rm
docker volume rm
删除所有未使用的数据卷  prune
docker volume prune

挂载指令

挂载命令:
-v [宿主机目录]:[容器内目录]
-v [宿主机文件]:[容器内文件]

 4.3.1案例:nginx挂载到数据卷

docker run --name mn -v html:/usr/share/nginx/html -p 80:80 -d nginx

命令解读:
--name mn:【容器名称】

-v html:【数据卷】名称

/usr/share/nginx/html:被挂载的【容器目录】

-p 80:80 :宿主机端口:容器端口

-d nginx:后台镜像

 以上命令只是将 容器目录 挂载到 数据卷 ,我们可以通过命令:

docker volume inspect

openresty docker 镜像下载 docker镜像版本_docker_05

  4.3.2案例:mysql挂载到本地目录 

docker run \   // 创建容器
  --name mysql \ // 给容器起名
  -e MYSQL_ROOT_PASSWORD=root \ // 设置root账户密码
  -p 3306:3306 \ // 端口映射
  -d \ // 后台运行
  -v /tmp/mysql/data:/var/lib/mysql \ // 绑定宿主机上的文件夹
  -v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/my.cnf \ // 绑定宿主机上的文件
  --privileged \ // 设置超级管理员远程访问权限
  mysql:5.7 // 镜像名称

5 Dockerfile部署单体应用

5.1 Dockerfile自定义镜像

企业开发中,特别是分布式事务,我们经常需要把一个微服务的项目代码打成镜像部署到linux上进行测试。此时的镜像是我们自定义构造的,而在自定义镜像之前,我们首先得清楚镜像的结构是什么?

5.1.1 镜像结构

镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。

我们以MySQL为例,来看看镜像的组成结构:

openresty docker 镜像下载 docker镜像版本_数据_06

总结:

镜像是层级结构,基本可以分成:

  1. 基础镜像层:最底层核心的依赖,是大多数镜像的公用层级;
  2. 项目依赖层:工程依赖的函数、配置、jar包等;
  3. 入口层:工程启动运行的入口,例如一些:启动参数、启动命令等等;

了解了镜像结构之后,可以着手去学习如何构建一个镜像了,大致步骤如下: 

openresty docker 镜像下载 docker镜像版本_容器_07

 

 这些步骤就是一条条的指令,最终都会集成到一个脚本文件中交由Docker内核去处理。

但是要怎么告诉docker呢?就得用到Dockerfile语法

5.1.2 Dockerfile语法——编写Dockerfile文件

常见指令: 

指令

说明

示例

FROM

指定基础镜像

FROM java:8.0

ENV

设置环境变量的安装目录

ENV key=value

COPY

拷贝本地jar包到镜像中的指定目录

COPY ./app.jar /tmp/app.jar 

RUN

指定Linux的shell命令,一般是安装过程的命令

RUN yum install && Run tar -zxvf

EXPOSE

指定容器运行时监听的端口,是给镜像使用者看的

EXPOSE:8080

ENTERPOINT

镜像中应用的启动命令,容器运行时调用

ENTRYPOINT java -jar /tmp/app.jar 

FROM:指定基础镜像

ENTERPOINT:jar包启动命令

注意:所有指令必须大写

5.1.2.1 Java工程基于Ubuntu基础镜像通用demo
# 1 指定基础镜像
FROM ubuntu:16.04
# 2 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local

# 3 拷贝jdk
COPY ./jdk8.tar.gz $JAVA_DIR/

# 4 拷贝java工程包(不同工程发布,需要修改这里就行了)
COPY ./docker-demo.jar /tmp/app.jar

# 5 安装JDK
RUN cd $JAVA_DIR \
 && tar -xf ./jdk8.tar.gz \
 && mv ./jdk1.8.0_144 ./java8

# 6 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin

# 7 暴露端口
EXPOSE 8090
# 8 项目启动命令(不同工程发不,这里的启动命令也需要根据jar包名称来变动)
ENTRYPOINT java -jar /tmp/app.jar

根据上面的描述,清楚了,不同的工程在发布时,dockerfile文件内容几乎都是一致的,只有在【拷贝java工程jar包】和【项目启动命令】那里不一样。因此,基于上面的通用demo ,可以针对公用的指令抽离成一个工具镜像,就像maven依赖一样。所以,上面的通用demo可以继续优化成像下面这样:

# 1 基础镜像
FROM openjdk:11.0-jre-buster
# 2 拷贝java工程jar包
COPY docker-demo.jar /app.jar
# 3 项目启动命令
ENTRYPOINT ["java","-jar","/app.jar"]

当然,这也只是举例,实际的项目部署,最好还是用通用的稍作修改即可 。

5.1.3 构建镜像命令

构建容器:docker build -t   myImageName:version .  【最后的.表示在当前目录下】

 -t:镜像取名;

        举例:docker build -t javaweb:1.0 .   

        运行容器:docker run --name web -p 8090:8090 -d javaweb:1.0

5.1.4 实践-基于Ubuntu自定义镜像

资料连接:docker-demo

【1】准备项目jar包+Dockerfile脚本

在Linux中自定义一个文件夹,上传程序jar包和对应的Dockerfile文件。准备完成后,linux文件夹中的文件内容如下:

openresty docker 镜像下载 docker镜像版本_容器_08

        jar包注意事项:

引入打包的插件,并且可以指定打包后文件名称

<build>
        <finalName>app</finalName>  //指定打包的文件名称
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>//打包插件
            </plugin>
        </plugins>
</build>

        Dockerfile文件内容:

# 1 基础镜像
FROM openjdk:11.0-jre-buster
# 2 拷贝jar包
COPY ./docker-demo.jar /tmp/app.jar
# 3 暴露端口
EXPOSE 8090
# 4 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar

 【2】打包镜像

        docker build -t javaweb:1.0 .

        解析:执行上述命令后,linux就会执行Dockerfile脚本文件——

        i:由于linux中没有hubuntu的相关资源,所以第一步下载基础镜像ubuntu

openresty docker 镜像下载 docker镜像版本_容器_09

         ii:Dockerfile文件中每条命令在linux中都是一个步骤,linux会逐步执行

openresty docker 镜像下载 docker镜像版本_数据_10

         iii:镜像构建完成提示

openresty docker 镜像下载 docker镜像版本_容器_11

【3】构建容器

        docker run --name web -p 8090:8090 -d javaweb:1.0

openresty docker 镜像下载 docker镜像版本_运维_12

虚拟机ip:项目接口/项目路径

5.2 Docker-compose部署分布式微服务应用

上一节讲述了如何通过Dockerfile如何部署单体应用,在微服务的背景下,如果还是采用Dockerfile的形式,就意味着我们要重复的写shell脚本,重复按照流程打包镜像、构建容器。

此时,操作的冗余度就上来了,就需要用到Docker-compose,它可以说进一步加强了Dockerfile的功能和操作,能够快速帮我们快速的部署分布式应用

【1】准备完成后,linux文件夹中的文件内容如下: 

openresty docker 镜像下载 docker镜像版本_数据_13

【2】内容说明

gateway:网关微服务;                             内容:jar包和对应的dockefile脚本文件

openresty docker 镜像下载 docker镜像版本_运维_14

mysql:数据脚本;                                    内容:所有微服务所涉及的数据库.sql脚本 

openresty docker 镜像下载 docker镜像版本_docker_15

order-service:订单微服务;                    内容:jar包和对应的dockefile脚本文件

user-service:用户微服务;                      内容:jar包和对应的dockefile脚本文件

docker-compose.yml

案例——————————————————————————————————

openresty docker 镜像下载 docker镜像版本_数据_16

根据上图可以了解这里我们的工程仅仅依赖 web和redis;所以在虚拟机中我们要构建2个容器环境

5.2.1 安装DockerCompose

MAC下或者Windows下的Docker自带Compose功能,无需安装

Linux下需要通过命令安装:

# 安装
curl -L https://github.com/docker/compose/releases/download/1.24.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# 修改权限
chmod +x /usr/local/bin/docker-compose
# 加载
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

5.2.2 构建Java项目和其依赖环境

【1】idea导入需要项目 or 工程 >> 打包【包名自定义为:app.jar】

openresty docker 镜像下载 docker镜像版本_运维_17

【2】编写Dockerfile【新建一个文件:Dockerfile,无后缀名,填入以下代码】

# 当前工程所依赖的基础镜像:jdk8 
FROM java:8-alpine    

# COPY 【包名】 【镜像目录】   将项目包拷贝到镜像指定文件夹中
COPY ./app.jar /tmp/app.jar  #【包名是项目打包后可以自定义的,进项目录也是自定义的】

# 向外暴露的接口【一定要和项目的yml配置文件里一样】
EXPOSE 9090  #这里可以通过【ip:9090/controller路径】来访问项目

# 入口 【java项目,以jar形式启动,项目文件在镜像中的地址(就是第二步的镜像目录)】
ENTRYPOINT ["java","-jar","/tmp/app.jar"]

5.2.3 编写docker-compose[新建一个文件:docker-compose.yml,填入以下代码]

version: '3'  
services:
  web:
    build: .
    ports:
      - "9090:9090"
  redis:
    image: "redis:6.0"

命令解读:

  • version:compose的版本
  • services:服务列表,包括两个服务:
  • web:自己写的Java项目
  • build:这个服务镜像是临时构建的,构建目录是当前目录,会利用当前目录的Dockerfile来完成构建。
  • ports:端口映射,对外开放8080端口
  • redis:redis服务

5.2.4 dockercompose部署

在docker中新建文件夹:mkdir -p /tmp/docker-compose ;

将以上准备好的三个文件【app.jar;Dockerfile;docker-compose.yml】复制进去,此时的docker结构如下:

openresty docker 镜像下载 docker镜像版本_容器_18

5.5 启动测试

进入docker项目部署的文件夹【cd /tmp/docker-compose】;

执行命令:docker-compose up;

浏览器访问:虚拟机ip:端口:controller路径