Bootstrap

微服务开发与实战Day02 - Docker

一、Docker快速入门

快速构建、运行、管理应用的工具

安装部署教程:Docs

1. 部署MySQL

测试连接:

镜像和容器

当我们利用Docker安装应用时,Docker会自动搜索并下载应用镜像(image)。镜像不仅包含应用本身,还包含应用运行所需的环境、配置、系统函数库。Docker会在运行镜像时创建一个隔离环境,称为容器(container)

镜像仓库:存储和管理镜像的平台,Docker官方维护了一个公共仓库:Dokcer Hub

2. 命令解读

docker run -d \

 --name mysql \

 -p 3306:3306 \

 -e TZ=Asia/Shanghai \

 -e MYSQL_ROOT_PASSWORD=123 \

 mysql

  • docker run -d :创建并运行一个容器,-d则是让容器以后台进程运行

  • --name mysql : 给容器起个名字叫mysql,你可以叫别的

  • -p 3306:3306 : 设置端口映射。

    • 容器是隔离环境,外界不可访问。但是可以宿主机端口映射容器内到端口,当访问宿主机指定端口时,就是在访问容器内的端口了。

    • 容器内端口往往是由容器内的进程决定,例如MySQL进程默认端口是3306,因此容器内端口一定是3306;而宿主机端口则可以任意指定,一般与容器内保持一致。

    • 格式: -p 宿主机端口:容器内端口,示例中就是将宿主机的3306映射到容器内的3306端口

  • -e TZ=Asia/Shanghai : 配置容器内进程运行时的一些参数

    • 格式:-e KEY=VALUE,KEY和VALUE都由容器内进程决定

    • 案例中,TZ=Asia/Shanghai是设置时区;MYSQL_ROOT_PASSWORD=123是设置MySQL默认密码

  • mysql : 设置镜像名称,Docker会根据这个名字搜索并下载镜像

    • 格式:REPOSITORY:TAG,例如mysql:5.7,其中REPOSITORY可以理解为镜像名,TAG是版本号

    • 在未指定TAG的情况下,默认是最新版本,也就是mysql:latest

二、Docker基础

1. 常见命令

Reference documentation | Docker Docs

Docker最常见的命令就是操作镜像、容器的命令,详见官方文档:https://docs.docker.com/

1.1 常用命令

命令说明
docker pull拉取镜像
docker push推送镜像到DockerRegistry
docker images查看本地镜像
docker rmi删除本地镜像
docker run创建并运行容器(不能重复创建)
docker stop停止指定容器
docker start启动指定容器
docker restart重新启动容器
docker rm删除指定容器
docker ps查看容器
docker logs查看容器运行日志
docker exec 进入容器
docker save保存镜像到本地压缩文件
docker load加载本地压缩文件到镜像
docker inspect查看容器详细信息

补充:默认情况下,每次重启虚拟机我们都需要手动启动Docker和Docker中的容器。通过命令可以实现开机自启动:

# Docker开机自启
systemctl enable docker

# Docker容器开机自启
docker update --restart=always [容器名/容器id]

案例:查看DockerHub,拉取Nginx镜像,创建并运行Nginx容器

需求:

  • DockerHub中搜索Nginx镜像,查看镜像的名称
  • 拉取Nginx镜像
  • 查看本地镜像列表
  • 创建并运行Nginx容器
  • 查看容器
  • 停止容器
  • 再次启动容器
  • 进入Nginx容器
  • 删除容器

①在DockerHub中搜索Nginx镜像,查看镜像的名称

②拉取Nginx镜像

docker pull nginx

③查看本地镜像列表

docker images

④将Nginx镜像保存为一个.tar文件

docker save -o nginx.tar nginx:latest

⑤查看容器

ll

⑥删除镜像

docker rmi nginx:latest

⑦加载Docker镜像

docker load -i nginx.tar

⑧创建并运行Nginx容器

docker run -d --name nginx -p 80:80 nginx

⑨验证是否运行成功

docker ps
或
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"

⑩停止Nginx容器

docker stop nginx

11. 运行容器

docker start nginx

12. 查看nginx日志

docker logs nginx

13. 进入容器内部

docker exec -it nginx bash

退出:exit

1.2 命令别名

给常用的Docker命令起别名,方便我们访问:

i:进入编辑模式;先按Esc,后输入:wq保存退出。

# 修改/root/.bashrc文件
vi /root/.bashrc
内容如下:
# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias dps='docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"'
alias dis='docker images'

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

然后,质量命令使别名生效

source /root/.bashrc

2. 数据卷

数据卷(volume)是一个虚拟目录,是容器内目录宿主机目录之间映射的桥梁,方便我们操作容器内文件,或者方便迁移容器产生的数据。

以Nginx为例,我们知道Nginx中有两个关键的目录:

  • html:放置一些静态资源

  • conf:放置配置文件

如果我们要让Nginx代理我们的静态资源,最好是放到html目录;如果我们要修改Nginx的配置,最好是找到conf下的nginx.conf文件。

但遗憾的是,容器运行的Nginx所有的文件都在容器内部。所以我们必须利用数据卷将两个目录与宿主机目录关联,方便我们操作。如图:

在上图中:

  • 我们创建了两个数据卷:confhtml

  • Nginx容器内部的conf目录和html目录分别与两个数据卷关联。

  • 而数据卷conf和html分别指向了宿主机的/var/lib/docker/volumes/conf/_data目录和/var/lib/docker/volumes/html/_data目录

这样以来,容器内的confhtml目录就 与宿主机的confhtml目录关联起来,我们称为挂载。此时,我们操作宿主机的/var/lib/docker/volumes/html/_data就是在操作容器内的/usr/share/nginx/html/_data目录。只要我们将静态资源放入宿主机对应目录,就可以被Nginx代理了。

小提示

/var/lib/docker/volumes这个目录就是默认的存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为/数据卷名/_data

为什么不让容器目录直接指向宿主机目录呢

  • 因为直接指向宿主机目录就与宿主机强耦合了,如果切换了环境,宿主机目录就可能发生改变了。由于容器一旦创建,目录挂载就无法修改,这样容器就无法正常工作了。

  • 但是容器指向数据卷,一个逻辑名称,而数据卷再指向宿主机目录,就不存在强耦合。如果宿主机目录发生改变,只要改变数据卷与宿主机目录之间的映射关系即可。

不过,我们通过由于数据卷目录比较深,不好寻找,通常我们也允许让容器直接与宿主机目录挂载而不使用数据卷。

命令说明
docker volume create创建数据卷
docker volume ls查看所有数据卷
docker volume rm删除指定数据卷
docker volume inspect查看某个数据卷的详情
docker volume prune清除数据卷

注意:容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,是不能设置数据卷的。而且创建容器的过程中,数据卷会自动创建。

  • 在执行docker run命令时,使用 -v 数据卷:容器内目录 可以完成数据卷挂载
  • 当创建容器时,如果挂载了数据卷且数据卷不存在,会自动创建数据卷。 

案例1:利用Nginx容器部署静态资源

需求:

  • 创建Nginx容器,修改nginx容器内的html目录下的index.html文件,查看变化
  • 将静态资源部署到nginx的html目录

①进入nginx容器

docker exec -it nginx bash

②进入index.html文件存放目录

cd /usr/share/nginx/html

③查看目录下的所有文件

ls

④试图编辑index.html文件 -> command not found.

vi index.html

⑤先删除nginx容器

docker rm -f nginx

⑥创建并运行容器

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

⑦查看容器列表

docker ps

⑧查看数据卷列表

docker volume ls

⑨查看数据卷的详情信息

docker volume inspect html

⑩进入目录,查看文件

cd /var/lib/docker/volumes/html/_data
ll

11. 打开index.html

cat index.html

案例2:mysql容器的数据挂载

  •  在执行docker run命令时,使用 -v 本地目录:容器内目录 可以完成本地目录挂载
  • 本地目录或文件必须以 /./开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名
  • 例如:
-v mysql:/var/lib/mysql # 会被识别为一个数据卷叫mysql,运行时会自动创建这个数据卷
-v ./mysql:/var/lib/mysql # 会被识别为当前目录下的mysql目录,运行时如果不存在会创建目录

需求:

  • 查看mysql容器,判断是否有数据卷挂载
  • 基于宿主机目录实现MySQL数据目录、配置文件、初始化脚本的挂载

        ①挂载/root/mysql/data到容器内的/var/lib/mysql目录

        ②挂载/root/mysql/init到容器内的/docker-entrypoint-initdb.d目录,携带课前资料准备的SQL脚本

        ③挂载/root/mysql/conf到容器内的/etc/mysql/conf.d目录,携带课前资料准备的配置文件

①查看mysql容器详情(匿名数据卷,不方便管理)

docker inspect mysql

②创建文件夹

mkdir mysql # 根目录下
cd mysql/
mkdir data
mkdir conf
mkdir init

③把资料中的两个文件夹拖拽上传到mysql目录下

④删除原来的mysql容器

cd ~
docker rm -f mysql

⑤创建并运行mysql容器,挂载本地目录

docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e TZ=Asia/Shanghai \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v ./mysql/data:/var/lib/mysql \
  -v ./mysql/conf:/etc/mysql/conf.d \
  -v ./mysql/init:/docker-entrypoint-initdb.d \
  mysql

3. 自定义镜像

镜像就是包含了应用程序、程序运行的系统函数库、运行配置等文件的文件包。构建镜像的过程其实就是把上述文件打包的过程。

3.1 镜像结构

第一步中需要的Linux运行环境,通用性很强,所以Docker官方就制作了这样的只包含Linux运行环境的镜像。我们在制作java镜像时,就无需重复制作,直接使用Docker官方提供的CentOS或Ubuntu镜像作为基础镜像。然后再搭建其它层即可,这样逐层搭建,最终整个Java项目的镜像结构如图所示:

3.2 Dockerfile

Dockerfile就是一个文本文件,其中包含一个个的指令(Instruction),用指令来说明要执行什么操作来构建镜像。将来Docker可以根据Dockerfile帮我们构建镜像。常见指令如下:

指令说明示例
FROM指定基础镜像FROM centos:6
ENV设置环境变量,可在后面指令使用ENV key value
COPY拷贝本地文件到镜像的指令目录COPY ./jrell.tar.gz /tmp
RUN执行Linux的shell命令,一般是安装过程的命令RUN tar -zxvf /tmp/jrell.tar.gz && EXPORTS path=/tmp/jrell:$path
EXPOSE指定容器运行时监听的端口,是给镜像使用者看的EXPOSE 8080
ENTRYPOINT镜像中应用的启动命令,容器运行时调用ENTRYPOINT java -jar xx.jar

官方文档:https://docs.docker.com/engine/reference/builder

我们可以基于Ubuntu基础镜像,利用Dockerfile描述镜像结构,也可以直接基于JDK为基础镜像,省略前面的步骤:

# 指定基础镜像
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"]
# 基础镜像
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,可以利用下面的命令来构建镜像:

docker build -t myImage:1.0 .
  • -t:是给镜像起名,格式依然是repository:tag的格式,不指定tag时,默认为latest
  • .:是指定Dockerfile所在目录,如果就在当前目录,则指定为 "."

①上传资料里的demo文件夹到root目录下,删除其中的logs文件夹

②上传资料里的images/jdk.tar到root目录下

③加载镜像

docker load -i jdk.tar

④进入demo文件夹,执行构建命令

cd demo/
docker build -t docker-demo .

⑤运行

docker run -d --name dd -p 8080:8080 docker-demo

⑥查看运行状态

dps

⑦查看运行日志

docker logs -f dd

⑧通过网址访问(记得加上/hello/count

http://192.168.126.151:8080/hello/count

3.3 总结

1. 镜像的结构是怎么样的?

答:镜像中包含了应用程序所需要的运行环境、函数库、配置,以及应用本身等各种文件,这些文件分层打包而成。

2. Dockerfile是做什么的?

答:Dockerfile就是利用固定的指令来描述镜像的结构和构建过程,这样Docker才可以一次来构建镜像。

3. 构建镜像的命令是什么?

答:docker build -t 镜像名 Dockerfile目录

4. 容器网络

默认情况下,所有容器都是以bridge方式连接到Docker的一个虚拟网桥上:

容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很可能MySQL容器的IP会发生变化,连接会失败。

加入自定义网络的容器才可以通过容器名互相访问,Docker的网络操作命令如下:

命令说明
docker network create创建一个网络
docker network ls查看所有网络
docker network rm删除指定网络
docker network prune清除未使用的网络
docker network connect使指定容器连接加入某网络
docker network disconnect使指定容器连接离开某网络
docker network inspect查看网络详细信息

①创建一个网络

docker network create heima

ip addr

②使mysql容器连接加入黑马

docker network connect heima mysql
docker inspect mysql

现在mysql有两个network

③创建并运行容器的同时加入网络

docker run -d --name dd -p 8080:8080 --network heima  docker-demo
docker inspect dd

只有默认网桥

④进入dd容器

docker exec -it dd bash
ping mysql

ping nginx

三、项目部署

1. 部署Java应用

需求:将课前资料提供的hmall项目打包为镜像并部署,镜像名为hmall

①在IDEA打开hmall项目,修改pom.xml里lombok的版本号为1.18.30 (我用的JDK22)

<org.projectlombok.version>1.18.30</org.projectlombok.version>

②在application-dev.yaml中把数据库连接信息改成自己的

hm:
  db:
    host: mysql #容器名称
    pw: 123456

在application-local.yaml中修改相关信息

hm:
  db:
    host: 192.168.126.151 # 修改为你自己的虚拟机IP地址
    pw: 123456 # 修改为docker中的MySQL密码

③使用package打包(如果下载下来的项目中有一些文件名多了一些日期字段记得删除)

④把打包后的jar包和Dockerfile上传到虚拟机的root目录下

⑤构建

docker build -t hmall .

⑥查看

⑦删除dd容器,避免端口冲突

docker rm -f dd

⑧运行项目

docker run -d --name hm -p 8080:8080 --network heima hmall

⑨查看日志

docker logs -f hm

⑩访问测试

2. 部署前端

需求:创建一个新的nginx容器,将课前资料提供的nginx.conf、html目录与容器挂载

①把资料里的整个nginx文件夹上传到虚拟机的root目录下

注意,nginx.conf里的names字段要与虚拟机里配置的一致(记得修改两处

②先删除之前的nginx容器

docker rm -f nginx

③创建并运行容器

docker run -d \
  --name nginx \
  -p 18080:18080 \
  -p 18081:18081 \
  -v /root/nginx/html:/usr/share/nginx/html \
  -v /root/nginx/nginx.conf:/etc/nginx/nginx.conf \
  --network heima \
  nginx

④查看容器列表

⑤访问测试

docker logs -f hm

3. DockerCompose

Docker Compose通过一个单独的docker-compose.yml模板文件(YAMl格式)来定义一组相关联的应用容器,帮助我们实现多个相互关联的Docker容器的快速部署

docker compose的命令格式如下:

docker compose [OPTIONS] [COMMAND]
类型参数或指令说明
Options-f指定compose文件的路径和名称
-p指定project名称
Commandsup创建并启动所有service容器
down停止并移除所有容器、网络
ps列出所有启动的容器
logs查看指定容器的日志
stop停止容器
start启动容器
restart重启容器
top查看运行的进程
exec在指定的运行中容器中执行命令

步骤:

①把资料里的docker-compose.yml上传到虚拟机root目录下

version: "3.8"

services:
  mysql:
    image: mysql
    container_name: mysql
    ports:
      - "3306:3306"
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
    volumes:
      - "./mysql/conf:/etc/mysql/conf.d"
      - "./mysql/data:/var/lib/mysql"
      - "./mysql/init:/docker-entrypoint-initdb.d"
    networks:
      - hm-net
  hmall:
    build: 
      context: .
      dockerfile: Dockerfile
    container_name: hmall
    ports:
      - "8080:8080"
    networks:
      - hm-net
    depends_on:
      - mysql
  nginx:
    image: nginx
    container_name: nginx
    ports:
      - "18080:18080"
      - "18081:18081"
    volumes:
      - "./nginx/nginx.conf:/etc/nginx/nginx.conf"
      - "./nginx/html:/usr/share/nginx/html"
    depends_on:
      - hmall
    networks:
      - hm-net
networks:
  hm-net:
    name: hmall

②把root/nginx/nginx.conf里的内容改回来(proxy-pass)

③删除之前创建的所有容器

docker rm -f nginx hm mysql/

④删除镜像hmall和docker-demo

docker rmi hmall docker-demo

⑤使用DockerCompose一键部署

docker compose up -d

注:有警告是因为在新的Docker Compose格式中version字段被移除了。

⑥查看所有启动的容器

docker compose ps

⑦查看容器列表

dis

⑧访问测试

⑨停止并移除所有容器、网络

docker compose down

;