0%

Docker基础

前言

  1. 简单介绍Docker的概念
  2. 介绍Docker的安装
  3. Docker镜像操作
  4. Docker容器操作
  5. Docker数据卷,数据映射
  6. Docker自定影镜像
  7. DockerCompose批量启动镜像
  8. 搭建DockerRegistry私服(UI版本)

Docker概述

项目部署的问题

大型项目组件多,运行环境也较为复杂,部署时会碰到一些依赖:如Node,Redis,MQ等。

  • 依赖关系复杂,容易出现兼容性问题
  • 开发、测试、生产环境有差异

Docker解决问题

Docker技术

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

Docker问题

  1. 问题: 但是依然会存在一个问题,基于不同的操作系统的应用,就算一起打包,在别的操作系统上也难以运行,原因是:不同操作系统自己有函数库,这些函数库再去调用linux内核。这些应用会不可避免地使用系统自带的函数库。

    • 解决:Docker将用户程序与所需要的调用的系统(如Ubuntu)函数库一起打包。
  2. 这样可以保证:不同操作系统的应用在Docker中,只要使用相同的linux内核就能无痛使用。相反,如果操作系统内核不一样,这个容器内的应用就可能会失效。(比如基于linux内核的Redis镜像生成的容器,一样不可以在Windows上使用)

一句话概括:Docker是基于内核的沙箱隔离技术,和虚拟机不同。

问题提问总结:

  • Docker如何解决大项目依赖关系复杂,不同组件依赖的兼容性问题?

    • Docker允许开发中将应用、依赖、函数库、配置一起打包,形成可移植镜像
    • Docker应用运行在容器中,使用沙箱机制,项目隔离
  • Docker如何解决开发、测试、生产环境有差异的问题

    • Docker镜像中包含完整运行环境,包括系统函数库,仅依赖系统的Linux内核,因此可以在任意Linux操作系统上运行

Docker是什么

Docker是一个快速交付应用、运行应用的技术:

  • 可以将程序及其依赖、运行环境一起打包为一个镜像,可以迁移到任务Linux操作系统
  • 运行时利用沙箱机制形成隔离容器,各个应用互不干扰
  • 启动、移除都可以通过一行命令完成,方便快捷

Docker和虚拟机

虚拟机运用了 Hypervisor技术,模拟了操作系统的硬件(CPU,内存等),虚拟机相当于创造了一个完成的系统生态。

Docker是基于操作系统内核的,不能跨平台。

Docker优点:

  • 性能好,接近原生性能
  • 占用内存小和硬盘空间小
  • 启动快,秒级启动

总结

  • docker是一个系统进程;虚拟机是在操作系统中的操作系统
  • docker体积小、启动速度快、性能好;虚拟机体积大、启动速度慢、性能一般

Docker架构

镜像和容器

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

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

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

镜像是只读的,不可写。

镜像平台

  • DockerHub:DockerHub是一个Docker镜像的托管平台。称为Docker Registry
  • 国内也有DockerHub的公开服务
  • 也可以自己构建私有平台

架构

C/S架构。

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

安装

Docker分为CE和EE两个版本,CE是社区版本,免费使用。

这里显示Docker CE在CentOS上的安装。

版本

DockerCE支持64位版本CentOS7,并且要求内核版本不低于3.10,CentOS7满足最低内核的要求,所里我们在CentOS7安装。

补充:笔者在mac装了,就不装虚拟机了,麻烦。

**特别注意:**Docker作为linux技术,实际是不适配Mac的,Mac上的docker本质上是运行在VM上,所以一些实际数据的挂载位置,并不和docker记录的相同,我们得先进入虚拟环境才行,比如数据卷的挂载位置`/var/lib/docker`mac上是没有的,这里不展开了,建议各位在Linux上进行。

You need to keep in mind that Docker is still running inside a VM. The system paths are still relative to the VM, and not to your Mac.

启动Docker

因为Docker会用到各种各样的端口,因此自己练习是最好关闭防火墙。

1
2
3
4
5
6
systemctl start docker
systemctl restart docker
systemctl stop docker

# 查看版本
docker -v

配置镜像

阿里镜像源:阿里云官网教程

Docker操作

帮助:docker --help或者docker [opt] --help

操作镜像

  • 镜像名称一般分为两部门组成:【repository】:【tag】(mysql:5.7)
    • 没有指定tag,默认为latest

镜像操作命令

这里建议用docker [opt] --help一个个查看如何使用

  • 创建镜像:docker build
  • 拉取镜像:docker pull
  • 查看镜像:docker images
  • 删除镜像:docker rmi
  • 分享镜像
    • docker服务器:docker push
    • 用U盘拷贝:docker save,随后docker load使用镜像
  • 修改镜像名称:docker tag

实操:创建nginx镜像

访问DockerHub官网:

  • 拉取:pull
1
2
3
4
# 拉取
docker pull nginx
# 查看
docker images
  • 把镜像打包:save && remove
1
2
3
4
5
6
7
8
9
10
# 1. save
docker save -o {PATH}/nginx.tar nginx:latest

--- 遇到问题 ----
Error response from daemon: reference does not exist 【FUCK 原来是我的单词拼错了】
--- 问题解决 ---
将ngnix:latest换成ngnix的IMAGE_ID

# 2. remove
docker rmi {IMAGE_ID}
  • 加载别人的镜像:load
1
2
3
4
5
# 3. load
docker load -i {PATH}/nginx.tar

# 4. 修改name和tag,因为用IMAGE_ID来save,name和tag会丢失
docker tag {IMAGE_ID} nginx:latest

操作容器

基本操作

1
2
3
4
5
6
7
8
# 创建并且启动
docker run
# 暂停/恢复
docker pause/unpause # 挂起,容器状态会缓存
# 停止
docker stop
# 启动
docker start

进阶操作

1
2
3
4
5
6
7
8
# 进入容器执行命令
docker exec
# 查看容器运行日志
docker logs
# 查看所有运行的容器级状态
docker ps
# 删除指定容器
docker rm

案例:操作Ngnix容器

运行和查看日志

  1. 去dockerhub查看Nginx容器的运行命令
1
docker run --name {containerName} -p {hostPort}:{containerPort} -d {imageName:[TAG]}
  • docker run:创建并运行一个容器
  • --name:容器的名称
  • -p:将宿主端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口
    • 容器的端口取决于容器中应用的本身,nginx一般是80
  • -d:后台运行容器
  • {imageName:[TAG]}:镜像名称,不加tag默认是latest
1
2
3
4
5
6
7
8
9
# 基于nginx启动容器
docker run --name my-nginx-container -p 80:80 -d nginx

# 查看容器运行状态
docker ps

---- 显示 ----
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
35c60d877196 nginx "/docker-entrypoint.…" 18 seconds ago Up 17 seconds 0.0.0.0:80->80/tcp my-nginx-container
  1. 查看日志
1
docker logs {containerName} # 和tail差不多,可以 -f -n

修改容器中ngnix配置

  1. 进入容器
1
docker exec -it {containerName} bash
  • docker exec:进入容器内部,执行一个命令
  • -it:给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互

  • bash:进入容器后执行的命令,bash是一个linux终端交互命令

  1. 修改配置
  • 在dockerhub里面找到nginx的配置在哪里
  • 因为容器里面没有vi和vim,我们目前只能用docker cp来同步(后面可以数据卷直接在宿主机中修改然后容器同步)
    • docker cp ngnix.conf my-nginx-container:/etc/nginx/nginx.conf
  1. 删除容器:docker rm
    • 运行中的容器-f删除

注意

不推荐在容器内做文件的修改!!

练习:创建redis容器

1
2
3
4
5
6
7
# 创建redis容器
docker run --name my-redis -p 6379:6379 -d redis redis-server --appendonly yes
# 进入redis容器
docker exec -it my-redis bash
root@0bea0ae0b357:/data# redis-cli
# 可以直接执行redis-cli命令
docker exec -it my-redis redis-cli

容器数据操作

问题

容器与数据太过耦合

  • 不便于修改
  • 数据不可复用,数据修改对外不可见
  • 升级维护困难,容器一旦删除,数据就丢失了

数据卷概念

数据卷(volume)是一个虚拟目录,指向宿主机文件系统中某个目录。

容器中的文件通过volume与宿主机中的文件形成映射,不管哪一方修改文件,都能自动同步。

  • 配置同步
  • 数据同步

数据卷操作

docker volume [COMMAND]

  • create :创建一个volume
  • inspect :显示一个或多个volume的信息
  • ls :列出所有的volume
  • prune :删除所有未使用的volume
  • rm :删除一个或多个指定的volume

实操

1
2
3
4
5
6
7
8
# 创建一个名为html的数据卷
docker volume create html
# 查看
docker volume ls
# 查询挂载点
docker volume inspect html
# 删除数据卷
docker volume prune 或者 docker rm html

基于数据卷挂载的文件映射

类似具名挂载。

  1. 创建容器时,可以通过-v参数来挂载一个数据卷到某个容器目录
1
docker run --name {containerName} -v {volumeName}:{containerDir} -p 8080:80 {IMAGE_NAME}
  • -v html:/root/html:把html数据卷挂载到容器内的/root/html这个目录中

注意:Mac的数据挂载位置不是docker volume inspect html中显示的挂载位置,因为Docker在mac上本身就是挂载在虚拟机上的。

要解决这个问题,可以查看:Where is /var/lib/docker on Mac/OS X2021 Update: Easiest option is Justin’s repo and image。本质就是直接去访问Docker VM。

1
2
docker run -it --rm --privileged --pid=host justincormack/nsenter1
ls /var/lib/docker

Just run this from your Mac terminal and it’ll drop you in a container with full permissions on the Docker VM. This also works for Docker for Windows for getting in Moby Linux VM (doesn’t work for Windows Containers).

PS:

  1. 所以说,在mac上搞数据卷挂载,外部修改这一套就是画蛇添足,我们直接用docker cp把数据同步到docker VM里面就好(或者用下面将的目录直接挂载),别想着在mac外部改了。挂载数据卷的唯一好处就是可以在Docker Desktop for Mac中查看数据卷同步的内容。

宿主机目录直接挂载

  • 类似匿名挂载。
    • 注意:若有重名文件,这种方式会导致容器内内容被覆盖。
  • 如果container启动时使用-v {hostDir}:{containerDir},则docker会直接将宿主机的目录挂载到容器上

    • 可以是目录,也可以是文件
  • mac用这种方式是完全ok的,猜想是Docker VM完成了这样的文件映射

案例:创建并运行一个Mysql容器,将宿主机目录直接挂载到容器

提示:目录挂载与数据卷挂载的语法是类似的

  • -v 【宿主机目录】:【容器内目录】
  • -v 【宿主机文件】:【容器内文件】

步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 拉取mysql
docker pull mysql:5.7.25

# 运行容器
docker run \
--name my-mysql \
-e MYSQL_ROOT_PASSWORD=1234 \
-p 3309:3306 \
-v /Users/memoforward/Project/docker/test/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /Users/memoforward/Project/docker/test/mysql/data:/var/lib/mysql \
-d \
mysql:5.7.25
# 可以进入mysql容器里面看一下cnf进去没
docker exec -it my-mysql bash
cd /etc/mysql/conf.d/

两种挂载方式对比

  • 数据卷:docker管理数据,宿主机目录docker创建
  • 直接挂载:自己管理数据(Mac只推荐这一种)

数据卷管理总结

  1. docker run的命令中通过-v参数挂载文件或目录到容器中:

    • -v {volumne名称}:{容器内目录}
    • -v {宿主机文件}:{容器内文件}
    • -v {宿主机目录}:{容器内目录}
  2. 数据卷挂载和目录直接挂载

    • 数据卷挂载耦合度低,由docker管理目录,但是目录较深,不好找(mac和windows直接歇菜,需要现金DockerVM里面)
    • 目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找查看

Dockerfile自定义镜像

镜像结构

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

  • 镜像是分层结构,每一层称为一个Layer
    • BaseImage层:包含基本的系统函数库,环境变量、文件系统
    • Entrypoint:入口,镜像中应用启动的命令
    • 其他:在BaseImage上添加依赖、安装程序、完成曾哥应用的安装和配置

Dockerfile

Dockerfile就是一个文本文件,其中包含一个个的指令(Instruction),用指令来说明要执行什么操作来构建镜像。每一个指令都形成一层Layer。

参考官网:

实践:基于Ubuntu镜像构建一个新镜像,运行java项目

  • 本地新建文件夹docker-demo
  • 拷贝一个jar包到文件夹内
  • 拷贝一个jdk8.tar.gz到文件夹内
  • 生成一个Dockerfile到文件夹内
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
####### Dockerfile #######
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local

# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar

# 安装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 8090
# 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar
  • 在文件夹下运行docker build -t cxy-docker-image-demo:1.0 ./
    • -t:指定镜像的名称
    • ./:指定Dockerfile所在的目录
  • 此时镜像就已经被docker管理了,可以save也可以load
  • 使用docker run --name my-docker-demo -p 8090:8090 -d cxy-docker-image-demo:1.0

问题

如果用以上的Dockerfile构建微服务镜像,会有一个问题:对于每个java微服务,都要安装基础镜像和配置java环境,是不是过于繁琐了?如果对于每个微服务,我们期望只把jar包放进去就行,有没有好的方法?

解决:基于java:8-alpine镜像,将一个java项目构建为镜像

java:8-alpine是一个体积非常小的JDK镜像,包含了基础环境

  • 生成Dockerfile
1
2
3
4
5
6
7
# 指定基础镜像
FROM java:8-alpine
COPY ./docker-demo.jar /tmp/app.jar
# 暴露端口
EXPOSE 8090
# 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar

DockerCompose

  • Docker Compose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器!

  • Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: "3.8" # DockerCompse 版本
services:
# 第一种方式:通过镜像创建容器
mysql: # 容器名
image: mysql:5.7.25 # 镜像
environment:
MYSQL_ROOT_PASSWORD: 12434 # 配置环境变量
volumes: # 数据配置
- /tmp/mysql/data:/var/lib/mysql
- /tmp/mysql/cnf/hmy.conf:/etc/mysql/conf.d/hmy.cnf
# 第二种方式:临时创建镜像并直接运行
web:
build: . # 从当前目录,通过dockerfile创建镜像
ports:
- 8090:8090
  • DockerCompose的详细语法参考官网:

总结:DockerCompose能够帮助我们快速部署分布式应用,无需一个个服务去构建镜像和部署。

案例:利用DockerCompose部署微服务

环境整理

  • 整理目录:

  • 每个微服务下文件夹有dokcerfile
1
2
3
FROM java:8-alpine
COPY ./app.jar /tmp/app.jar
ENTRYPOINT java -jar /tmp/app.jar
  • Docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
version: "3.2"

services:
nacos:
image: nacos/nacos-server
environment:
MODE: standalone
ports:
- "8848:8848"
mysql:
image: mysql:5.7.25
environment:
- MYSQL_ROOT_PASSWORD=123
volumes:
- "$PWD/mysql/data:/var/lib/mysql"
- "$PWD/mysql/conf:/etc/mysql/conf.d/"
ports:
- 3309:3306
userservice:
build: ./user-service
orderservice:
build: ./order-service
gateway:
build: ./gateway
ports:
- "10010:10010"
  • 注意:这里Dockerpose底层做了域名封装,用服务名代替ip,如我们每个微服务配置nacos地址要从【ip:8848】改成【nacos:8848】;mysql的url中的【ip】要改成【mysql】
  • 另外:不建议用docker部署数据库

操作

  1. Maven打包成app.jar
    • 打包时发现问题:多模块引用,找不到模块jar。
      • 原因:因为maven会第一时间去本地和配置文件中查询,但是这两个地方都没有模块jar,只要先把父工程install就行了。
  2. 拷贝进微服务文件夹
  3. cloud-demo文件夹上传到虚拟机,利用docker-compose up -d部署

  • PS:一般会先启动nacos,不然微服务启动比nacos快,会注册不上。如果没注册上用docker-compose重启。

搭建私有的镜像仓库

镜像仓库(Docker Registry)有公有和私有的两种形式:

  • 公共仓库:例如Docker Hub
  • 在本地搭建私有的Docker Registry,registry是Docker官放提供的一个做镜像仓库的镜像
    • 第三方有一个依赖于Docker Registry的joxit/docker-registry-ui:static镜像来做图形化的管理

配置Docker信任地址

私服采用http协议,默认不被Docker信任,所以需要一个配置(Mac在桌面版配置就行)

1
2
3
4
5
6
7
8
9
10
11
# 找到docker的配置文件
vim /etc/docker/daemon.json

# 添加内容
"insecure-registries":["http://{UI-IP}:{UI-PORT}"]

# 重新加载
systemctl deamon-reload

# 重启docker
sysemctl restart docker

DockerCompose构建私服

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.0'
sevices:
registry:
image: registry
volumes:
- /Users/memoforward/Project/docker/test/registry-data:/var/lib/registry
ui:
image: joxit/docker-registry-ui:static
ports:
- 8080:80
envirinment:
- REGISTRY_TITLE=Memoforward Docker Repo
- REGISTRY_URL=http://registry:5000
depends_on:
- registry

推送镜像到私服

  • 一定要记得重命名)重新tag本地镜像,名称前缀为私有仓库地址: {ip:port}/
1
docker tag nginx:latest 127.0.0.1:8080/nginx:1.0
  • 推送拉取
1
2
docker push 127.0.0.1:8080/nginx:1.0
docker pull 127.0.0.1:8080/nginx:1.0

再次强调

  1. 推送本地镜像到仓库前都必须重命名(docker tag)镜像,以镜像仓库地址为前缀
  2. 镜像仓库推送前需要把仓库地址配置到docker服务的deamon.json文件中,被docker信任
  3. 推送使用docker push命令
  4. 拉取使用docker pull命令