因为项目需要,需要同一台机器部署多个tomcat,或者多个版本的数据库,在一个服务器上部署很不方便,之前有使用VMware创建虚拟机,后来发现使用docker部署应用确实非常方便,比WMware节省资源,而且操作简单。然后就考虑仔细学一下Docker的知识和应用。
Docker基础知识
在日常使用中,当你只想要一个应用有单独的运行环境,而不想虚拟出一个完整的计算机,不想浪费资源,也不想再配置一次环境,这就是使用Docker的优势了。

传统虚拟机与容器对比
传统虚拟机技术
虚拟机是虚拟出一套硬件,在这之上运行一个完整的操作系统,例如之前使用的VMware,指定系统镜像,然后安装操作系统,最后配置并使用应用。
在使用VMware创建虚拟机的时候,需要指定cpu核数,内存大小和磁盘空间等资源,性能也有所限制。

容器技术
容器内的应用程序直接运行在宿主机的内核上,互相隔离,容器内没有自己的内核,也没有对硬件进行虚拟,因此容器比起虚拟机更为轻便,性能也比虚拟机好。
而且一套模板可以放到不同的机器上使用,可以跨系统部署,非常方便。

Docker的底层原理
Docker是基于Google公司推出的Golang语言开发而来的,基于Linux内核的Cgroups、NameSpace,以及Union FS等技术,对进程进行封装和隔离,属于操作系统的虚拟化技术。
对进程进行封装隔离,使用Docker可以实现开发、测试、生产环境的部署一致性,极大的减少运维成本。
Docker基础组件

Docker Daemon
安装使用Docker,得先运行Docker Daemon进程,用于管理Docker,如:
- 镜像 Images
- 容器 Containers
- 网络 Network
- 数据卷 Data Volumes
Rest接口
提供和Daemon交互的API接口
Docker Client
客户端使用REST API和Docker Daemon进行访问,就是Docker提供的各种命令行操作。

image
image镜像,构建容器。将应用程序运行所需的环境,打包为镜像文件。
镜像是一个只读模板,用于创建容器,也可以通过Dockerfile文本描述镜像的内容。
镜像的概念类似于编程开发里面向对象的类,从一个基类开始(基础镜像Base Image)
构建容器的过程,就是运行镜像,生成容器实例。
Docker镜像的描述文件是Dockerfile,包含了如下的指令:
- FORM 定义基础镜像
- MAINTAINER 作者
- RUN 运行Linux命令
- ADD 添加文件/目录
- ENV 环境变量
- CMD 运行进程
Contaioner
容器,应用程序跑在容器中。
容器时一个镜像的运行实例,镜像>容器。
创建容器的过程
- 获取镜像,如docker pull centos,从镜像库拉取
- 使用镜像创建容器
- 分配文件系统,挂载一个读写层,在读写层加载镜像
- 分配网络/网桥接口,创建一个网络连接,让容器和宿主机通信
- 容器获取IP地址
- 执行容器命令,如bin/bash
- 反馈容器启动结果
Registry
Docker镜像需要进行管理,Docker提供了Registry仓库,其实他是一个容器。可以基于该容器运行私有仓库。也可以使用Docker Hub互联网共有镜像仓库
Docker Hub
官方的镜像仓库(hub.docker.com),类似于GitHub
Docker 基础命令
pull
镜像的获取,是从配置好的docker镜像站中拉取。
docker pull hiromuhota/webspoon
search
获取之前也可以查看镜像文件是否存在
docker search hiromuhota/webspoon
image
可以查看本地Docker镜像有哪些
docker image ls
docker images
rmi
删除镜像
docker rmi 5afc9cadd006(IMAGE ID)
run
docker run -d -p 8080:8080 -v D:\kettle_code\kettle_file:/app/data -v D:\kettle_code\jre:/docker-java-home/jre hiromuhota/webspoon:0.8.2.17
-d : 后台运行容器
-p : 端口映射 宿主机:容器端口,访问宿主机的端口就是访问容器对应端口
docker run -it 5afc9cadd006(IMAGE ID) bash
-i : 交互式命令操作
-t : 开启一个终端
bash : 进入容器后执行的命令,意思就是执行了bash解释器,可以执行命令
输入exit可以退出容器
docker run -it --rm 5afc9cadd006(IMAGE ID) bash
--rm : 容器退出时删除该容器
exec
docker exec -it 5afc9cadd006(IMAGE ID) bash
exec : 进入正在运行的容器内
ps
查看有哪些容器在运行
docker ps
stop
停止容器
docker stop 84587c4e049a(CONTAINER ID)
start
启动容器
docker start 84587c4e049a(CONTAINER ID)
info
查看Docker服务信息
Docker info
其中有信息Docker Root Dir: /var/lib/docker,这就是Docker的数据目录。
ls /var/lib/docker/image/overlay2/imagedb/content/sha256/ -l 这就是Docker镜像存储目录。
这些文件的作用是记录镜像和容器的配置关系。
Docker 生命周期

Docker的镜像原理
在获取镜像的时候,发现下载了多行信息,最终获得了一个完整的镜像文件。
就像linux操作系统,是由linux的内核+发行版。
利用Docker容器,可以获取不同的发行版镜像,然后基于改镜像运行各种容器使用。
当我们获取docker镜像的时候,并不是获取完整版,只是获取发行版。镜像就是一个发行版的作用,我们只需要准备好一个Linux内核,在上层使用不同的发行版就可以了。这样就可以自由的使用各个版本系统,兼容多种环境。

Docker是一层一层搭建起来的下层是上层的父级。
镜像分层的好处之一就是共享资源,例如有多个镜像都来自同一个base镜像,那么在Docker host只需要存储一份base镜像。
base镜像提供的是rootfs服务,也就是Linux的基础文件系统。如/etc /opt /目录下的各种文件。
即使多个容器共享一个base镜像,某个容器修改了base镜像的内容,例如修改/etc/下的配置文件,其他容器的/etc/下内容是不会被修改的,修改动作只限制在单个容器内,这就是容器的写入时复制特性(Copy-on-write)

- 添加文件:在容器中创建文件时,新文件会被添加到容器中。
- 读取文件:在容器中读取某个文件时,Docker会从上往下依次在各个镜像中查找此文件。一旦找到,立刻将其复制到容器层,然后打开并读入内存。
- 修改文件:在容器中修改已存在的文件时,Docker会从上往下依次在各个镜像层查找此文件。一旦找到,立即将其复制到修改器,修改掉。
- 删除文件:在容器中删除文件时,Docker也是从上往下一次在镜像层中查找此文件,找到后,会在容器层中记录下此删除操作。(只是记录删除操作)
Docker镜像的内容
Docker镜像层级管理的方式大大便捷了Docker镜像的分发和存储,Docker hub是全世界的镜像仓库。
- Docker镜像代表一个容器的文件系统内容
- 镜像层级技术属于联合文件系统UnionFS
- 容器是一个动态的环境,每一层镜像里的文件都会落实到容器环境里
Dockerfile
自定义Docker镜像的每一层应用
Docker镜像管理操作
获取镜像
- 从Docker Hub获取镜像
- 本地镜像导出、导入
- 私有的Docker仓库
搜索Docker Hub的镜像
docker search 镜像名
-- 下载镜像
docker pull 镜像名
查看镜像
Docker images
Docker images centos
可以指定具体的名称
Docker images -q
-q 也就是 --quiet 只列出镜像的id
可以格式化显示镜像
docker images --format "{{.ID}}--{{.Repository}}"
5afc9cadd006--webspoon_image
c8bfa64b5b20--hiromuhota/webspoon
f5fead3018a5--hiromuhota/webspoon
以表格形式显示
docker images --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"
IMAGE ID REPOSITORY TAG
5afc9cadd006 webspoon_image latest
c8bfa64b5b20 hiromuhota/webspoon latest
f5fead3018a5 hiromuhota/webspoon 0.8.2.17
删除镜像
删除镜像的时候,可以根据镜像的id、名称、摘要等方式删除。
删除之前可以查看当前有哪些容器在执行,或者哪些容器执行过
docker ps
docker ps -a
也可以组合使用命令参数,q就是之前说的只显示id
docker ps -aq
-- 删除容器记录
docker rm f8eec38b8873
-- 删除镜像,删除时,镜像不能有被依赖的容器记录
docker rmi hello-world
-- 指定id的前三位也可以删除
docker rmi d2c
镜像管理
批量删除镜像用法(Linux)
docker rmi `docker images -aq`
-- 批量删除容器
docker rm `docker images -aq`
导出镜像
导出的镜像为压缩文件,可以发给其他人使用。
docker commit
导出镜像的命令
docker image save hiromuhota/webspoon > /home/data/webspoon.tgz
导入镜像的命令
docker image load -i /home/data/webspoon.tgz
查看镜像信息
docker image inspect 镜像id
列出的就是镜像的json信息。
Docker 容器管理
docker run 的命令等于创建+启动
docker run 镜像名,如果镜像不存在本地,则会在线下载该镜像。
注意:容器内的进程必须处于前台运行状态,否则容器就会直接退出
如果容器内什么都没做,那么容器也会挂掉。
- 运行一个挂掉的容器,比如
docker run contos
这个写法,会产生多个容器记录,每运行一次就生成一条,因为容器中没有程序在运行,所以直接挂掉了。可以通过docker ps查看所有容器运行情况。
- 运行容器且进入容器内,且在容器内执行某个命令
docker run -it contos sh
- 开启一个容器执行某个程序,属于前台运行,会卡住一个终端-
docker run contos ping baidu.com
- 运行一个活着的容器,docker ps可以看到的容器
-d : 参数,让容器在后台执行(针对宿主机而言)
docker run -d contos ping baidu.com
- 其他运行的参数
–rm :容器挂掉后自动被删除(docker ps -a看不到)
-v 宿主机路径:容器路径 : 数据目录映射
–name :容器命名
docker run -d --rm --name baidu contos ping baidu.com
- 查看容器日志
docker logs 容器id
docker logs -f 容器id 这就是刷新日志
- 进入正在运行的容器控件内
docker exec -it 容器id bash
exit可以退出
- 查看容器详细信息,用于高级调试
docker container inspect 容器id
- 容器的端口映射
-p 宿主机端口:容器端口
-P :随机端口映射
docker run -d -p 82:80 --name lmk_nginx nginx
查看容器内的端口转发情况
docker port 容器id
- 容器的提交
docker commit 容器id 新的镜像名(一般是dockerhub账号/镜像名)
DockerFile
定制Docker镜像的方式有两种
- 手动修改容器内容,导出新的镜像
- 基于DockerFile自行编写指令,基于指令流程创建镜像
主要组成部分
- 基础镜像信息FROM contos:7.1
- 制作镜像操作指令RUN yum install openssh-server -y
- 容器启动时执行命令 CMD [“/bin/bash”]
DockerFile指令
例如,我们要基于容器运行Mysql
- 运行宿主机
- 安装docker容器软件
- 获取Mysql镜像即可,docker pull mysql:tag(这个镜像无法自由控制,该mysql的基础镜像是什么发行版,我们获取的镜像就是什么发行版,这是别人定制好的,下载的是debain,如果想要换成contos7的发行版,就要自定义)
- 直接运行该镜像,通过端口映射,运行mysql, docker run mysql:5.7(容器能够运行,必须在容器内,有一个进程在前台运行,该容器内,有mysql在前台运行)
- 访问宿主机的一个映射端口,访问到容器内的mysql
FROM
指定基础镜像
MAINTAINER
指定维护者信息,可以没有
RUN
在命令前面加上RUN即可
ADD
添加宿主机的文件到容器内,COPY文件,自动解压
源文件可以是一个URL,此时,docker引擎会下载该链接,放入目标路径,且权限自动设置为600,如果这不是期望结果,还需要加一层RUN指令进行调整。
源文件是一个URL,而且是一个压缩包,不会解压缩,需要加RUN命令自己解压
源文件是一个压缩文件可以解压,但是只有gzip、bzip2、xz、tar可以自动解压。
ADD mp.tar /home/
RUN linux 命令
COPY
复制文件,作用和ADD是一样的,都是添加宿主机的文件到容器内。不解压
copy map.py /home/
-- 支持多个文件,也可以使用通配符,语法要满足Golang的filepath.Match
copy map* /tmp/cc?.txt. /home/
-- COPY指令可以保留源文件的元数据,如权限、访问时间等等。
WORKDIR
设置当前工作目录,更改工作目录,类似cd。
WORKDIR /opt
USER
用于改变环境,用于切换用户
USER root
USER lmk
VOLUME
设置卷,挂载主机目录
容器再运行时,应该保证存储层不写入任何数据,运行在容器内产生的数据,我们推荐时挂载,写入到宿主机上,进行维护。
-- 将容器内的/data文件夹,在容器运行时,改目录挂载为匿名卷,任何向该目录写入数据的操作,都不会被容器记录,保证容器存储层无状态理念。
VOLUME /data
-- Dockerfile
FROM centos
MAINTAINER LMK
VOLUME ["/data1","/data2"]
-- 该容器运行是,这两个目录自动和宿主机做好映射关系
-- 运行该镜像
docker run 32d
-- 可以使用docker inspect查看信息
docker inspect 32d
EXPOSE
指定对外的端口
帮助使用该镜像的人快速理解该容器的端口业务
docker port 容器
docker run -p 宿主机端口:容器端口
docker run -P
-- 随机端口
CMD
指定容器启动后要干的事情
用法:CMD [“参数1″,”参数2”]
在指定了entrypoint指令后,用CMD指定具体参数
docker不是虚拟机,容器就是一个进程,既然是进程,那么程序在启动的时候就需要指定运行参数,这就是CMD指令的作用。
例如:
centos镜像默认的CMD是/bin/bash,直接docker run -it centos会直接进入bash解释器。
也可以启动容器的时候指定参数,docker run -it centos cat /etc/os-release
CMD ["cat","/etc/os-release"]
需要注意的是,docker不是虚拟机的概念,虚拟机里的程序运行,基本上都是后台运行,利用systemctl运行,但是容器内没有后台进程的概念,必须前台运行。
容器就是为了主程序存在的,主进程如果退出了,容器就毫无意义
例如
CMD systemctl start nginx
-- 这样的写法是错误的,容器会立即退出
-- 因为systemctl start nginx是希望以守护进程形式启动nginx,且CMD命令会转化为
CMD ["sh","-c","systemctl start nginx"]
-- 这样的命令主进程是sh解释器,执行完毕后会立刻结束,因此容器就退出了
-- 因此正确的方法是:
CMD ["nginx","-g","daemon off;"]
ENTRYPOINT
容器启动后执行的命令
ENTRYPOINT 和 RUN指令一样,分两种形式
- exec
- shell
作用和CMD一样,都是在指定容器启动程序以及参数。
当指定了ENTRYPOINT 之后,CMD指令的语义就有了变化,变成了把CMD的内容当作参数传递给ENTRYPOINT指令。
例如
-- 准备一个Dockerfile
FROM centos:7.8.2003
RUN rpm --rebuilddb && yum install epel-release -y
RUN rpm --rebuilddb && yum install curl -y
CMD ["curl","-s","http://ipinfo.io/ip"]
-- CMD等同于
docker run my_centos curl -s http://ipinfo.io/ip
-- 构建镜像
docker build .
-- 修改镜像名
docker tag 3dd centos_curl
-- 查看镜像
docker images
-- 生成容器
docker run centos_curl
-- 如果再想传入一个参数
docker run centos_curl pwd
-- 就会输出当前路径,该形式会覆盖镜像中的CMD,就好比把docker镜像当作一个环境执行后面的命令。
-- 如果想要正确的传入一个参数
-- 办法1:给容器传入新的完整命令
docker run my_centos curl -s http://ipinfo.io/ip -I
-- 办法2(正确方式),修改Dockerfile,使用ENTRYPOINT
FROM centos:7.8.2003
RUN rpm --rebuilddb && yum install epel-release -y
RUN rpm --rebuilddb && yum install curl -y
ENTRYPOINT ["curl","-s","http://ipinfo.io/ip"]
-- 重新构建镜像
ENV
环境变量,无论再镜像构建时,和容器运行时,都可以使用。
-- Dockerfile脚本中
ENV NAME="LMK"
ENV MYSQL_VERSION=5.7
-- 后面所有的操作可以通过$NAME获取变量的值,维护dockerfile的时候更友好。
ARG
也是设置环境变量,只是用于构建镜像设置的变量,容器运行的时候就不能使用了。
DockerFile实践
通过Dockerfile,构建nginx镜像,且运行容器后,生成的页面,内容为hello world
-- 创建一个Dockerfile,名称不能改,只能叫Dockerfile
FROM nginx
RUN echo '<meta charset= utf8>hello world'
/usr/share/nginx/html/index.html
-- 构建Dockerfile
-- 首先进入Dockerfile所在路径
docker build .
-- 修改镜像名
docker tag bs3 my_nginx
--运行该镜像
docker run -d -p 80:80 my-nginx
构建一个镜像网站
- nginx,修改首页内容,html网站就跑起来了,是一个web server,提供web服务,提供网关,限流等。
- web framework,web框架,一般由开发,通过某个开发语言,基于某个web框架,自己开发一个web站点,类似python的django框架
大概操作:
- 接下来就使用python语言,基于flask web框架,开发一个站点,写一个后端的网站代码
- 开发dockerfile,部署该代码,生成镜像
- 其他人基于该镜像,docker run就能再电脑上跑这个网站。
具体操作
在宿主机下,准备一个目录,准备好dockerfile,你的代码,写一个flask的python代码flask.py
#coding:utf8
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "hello docker"
if __name__=="__main__":
app.rum(hoet='0.0.0.0', port=8080)
2. 编写dockerfile
touch Dockerfile(这里代码和Docker处于同一个目录下为例)
FROM centos:7.8.2003
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo;
RUN curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo;
RUN yum makecache fast;
RUN yum install python3-devel python3-pip -y
RUN pip3 install -i https://pypi.douban.com/simple flask
COPY flask.py /opt
WORKDIR /opt
EXPOSE 8080
CMD ["python3", "flask.py"]
3. 构建镜像
docker build –no-cache -t ‘lmk212/my_flask_web’
–no-cache:不使用旧的缓存,重新生成缓存
4. 运行镜像,生成容器
docker run -d –name my_flask_web1 -p 90:8080 lmk212/my_flask_web
5. 访问宿主机,查看网站是否运行
http://172.0.0.1:90/
6. 修改网站的内容,程序是跑在容器中的
- 修改宿主机的代码,以及dockerfile,重新构建
- 进入到以及运行的容器内,修改代码,重启容器即可
使用docker exec -it 3dx bash 可以进入容器
Docker compose
docker compose是一个容器编排工具(自动化部署、管理)
可以在单台linux服务器上运行多个docker容器
docker-compose使用yaml文件来配置所有要运行的docker容器,该yaml文件的默认名称为docker-compose.yml
安装流程
# 安装
curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# 授予执行权限
sudo chmod +x /usr/local/bin/docker-compose
# 验证安装
docker-compose --version
使用案例
创建yml文件
首先在一个文件夹中创建docker-compose.yml文件,写入相关的内容。
version: '3.8'
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest # Zookeeper 通常向下兼容,保持最新问题不大
hostname: zookeeper
container_name: zookeeper
ports:
- "2181:2181"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
networks:
- kafka-net
kafka:
image: confluentinc/cp-kafka:7.5.0 # <--- 将此处替换为明确支持 Zookeeper 的旧版本
# 或者尝试 confluentinc/cp-kafka:7.4.0
hostname: kafka
container_name: kafka
ports:
- "9092:9092"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://0.0.0.0:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
# KAFKA_PROCESS_ROLES: broker # <--- 移除此行,旧版本通常不需要
networks:
- kafka-net
networks:
kafka-net:
driver: bridge
networks:
kafka-net:
driver: bridge
创建启动容器
在对应文件夹中,运行docker-compose
# 创建容器
docker-compose up -d
# -d: 后台运行所有服务。
# 启动、停止、重启容器
docker-compose start
docker-compose stop
docker-compose restart
查看容器
docker-compose ps -a
停止和清理
docker-compose down
