容器化技术之Docker-从入地到上天
作者:行百里er
博客:https://chendapeng.cn (opens new window)
提示
这里是 行百里er 的博客:行百里者半九十,凡事善始善终,吾将上下而求索!
# 0x00 开局一张图
# 0x01 容器化技术
# 1.1 历史演化
这里简单BB一下容器化技术的发展过程,觉得太长不看的可直接下滑看实战部分。
# 1.1.1 物理机时代
物理机时代,当我们的程序开发完成后,需要部署到服务器上,如果项目体量不大,部署在单台机器上也还可以,但是如果部署集群架构的项目,就很难了。
物理机时代的限制主要在以下几点:
- 部署非常慢 每个主机都要安装操作系统、相关的应用程序所需要的环境,各种配置
- 成本很高 服务器的价格是很贵滴,我们项目上的两台HP服务器,据说将近10万块
- 资源浪费 有时候就为了水平扩展一下应用就需要加一台服务器,太浪费了
- 难于扩展和迁移 比如代码仓库迁移、数据库迁移等都需要考虑一大堆配置
- 受制于硬件
# 1.1.2 虚拟化时代
虚拟化时代具有以下特点:
- 多部署
- 资源池
- 资源隔离
- 很容易扩展
- VM需要安装操作系统
每一台虚拟机都必须安装操作系统,才能在虚拟机上做其他的事情。
虚拟机的监视器 ( hypervisor ) 是类似于用户的应程序运行在主机OS之上,如 VMware 的 workstation,这种虚拟化产品提供了的硬件,像我们在机器上面安装一个linux的虚拟机就是:
图中的虚拟机都能够独立运行,都安装了操作系统,但是他们都依赖于我自己的物理机器,我这台物理机断电了,他们都得挂。
# 1.1.3 容器化时代
虚拟化是物力资源层面的隔离,那么容器可以看做是是APP层面的隔离。
容器化技术的应用场景:
- 标准化的迁移方式 开发环境打包给运维,运维展开后就能得到相同的环境
- 统一的参数配置 运行程序相关的参数,在打包的时候就能进行设置
- 自动化部署 进行镜像还原的过程是自动化部署的,不需要人工参与。我们项目上就是使用通过Gitlab的CICD功能感知代码的提交,使用Docker进行构建镜像。全程自动化。
- 应用集群监控 提供了应用监控功能,实时了解集群的运行状况
- 开发与运维之间的沟通桥梁 因为标准化的环境部署方式,可以减少不必要的环境不一致导致的问题世界清净了不少,程序员专心搞开发!
# 0x02 Docker入门
# 2.1 Docker介绍
Docker 是一个开源的应用容器引擎,基于 Go 语言开发并遵从 Apache2.0 协议开源。
Docker 可以让开发者打包应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。
- 开源的应用容器引擎,基于 Go 语言开发
- 容器是完全使用沙箱机制,容器开销极低
- 容器化技术的代名词
- 一定的虚拟化职能
- 标准化的应用打包
# 2.2 CentOS 7下安装Docker
由于服务器90%以上都是Linux系统的,所以我们在CentOS虚拟机上安装Docker,这些安装命令可以当做手册来参考。
- 安装基础包
yum install -y yum-utils device-mapper-persistent-data lvm2
device-mapper-persistent和lvm2 安装数据存储驱动包,进行数据存储用的
yum-utils 安装工具包,简化安装
- 设置安装源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum-utils 提供的yum-config-manager简化工具,用于修改yum的安装源
--add-repo 设置安装源
- 将软件包信息提前在本地缓存一份,用来提高搜索安装软件的速度
yum makecache fast
- 安装Docker社区版
yum -y install docker-ce
- 启动Docker
service docker start
以上是docker的安装步骤,汇总如下:
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache fast
yum -y install docker-ce
service docker start
2
3
4
5
6
7
8
9
- 验证Docker安装
docker -version
# 2.3 拉取docker镜像
使用 docker pull 拉取hello-world
镜像:
docker pull hello-world
使用 docker images 查看镜像:
# 2.4 根据镜像启动容器
docker run 镜像id<:tag> 命令
docker run hello-world
# 2.5 配置镜像加速
国内网从docker中央仓库下载镜像的速度我是不能忍的,我们可以通过镜像加速器来进行加速。
使用阿里云的容器镜像加速服务就很方便。
登录阿里云,搜索“容器镜像服务”,找到镜像加速器:
# 2.6 Docker安装目录
Docker安装目录位于var/lib/docker
,进去看一下:
cd /var/lib/docker && ll
这里面有image、containers、volumes等目录,是关于镜像、容器等相关的目录,下面来看一下Docker的这些重要的概念。
# 2.7 基本概念
这个图中包含了docker容器、镜像、仓库等重要概念。
Docker客户端通过docker run命令启动容器,Docker Server通过docker daemon守护进程查看本地有没有镜像,如果没有则到docker仓库中通过docker pull拉取到本地,然后执行docker run创建容器。
docker daemon守护进程,管理镜像和容器。
# 2.7.1 镜像
镜像是文件,是只读的,提供了运行程序完整的软硬件资源,是应用程序的“集装箱”。
# 2.7.2 容器
是镜像的实例,由Docker负责创建,容器之间彼此隔离。
# 2.7.3 仓库
仓库(Repository)是集中存放镜像的地方。
# 2.7.4 数据卷
数据卷是在宿主机中可以在容器之间进行共享和重用的一系列的文件和文件夹。
# 2.7.5 网络
Docker网桥是宿主机虚拟出来的,并不是真实存在的网络设备,外部网络是无法寻址到的。这也意味着外部网络无法通过直接Container-IP访问到容器。
# 0x03 Docker快速部署Tomcat
- docker pull tomcat:8.5-jdk8-openjdk
:8.5-jdk8-openjdk
表示镜像的tag,如果不加:8.5-jdk8-openjdk
默认拉取最新的。
- docker run tomcat:8.5-jdk8-openjdk
这样tomcat容器就启动了,此时我们通过宿主机的IP地址是访问不了的,需要对容器端口和宿主机端口进行映射。
停止容器 docker stop 3854be1d5f93(容器ID),删除容器 docker rm 3854be1d5f93,然后一如下方式启动:
docker run -p 8090:8080 -d tomcat:8.5-jdk8-openjdk
再访问就可以了。
快速部署命令:
# 拉取镜像
docker pull 镜像名<:tags>
# 查看镜像列表
docker images
# 运行容器
docker run 镜像名<:tags>
# 查看正在运行的容器
docker ps
# 删除容器
docker rm <-f> 容器id
# 删除镜像
docker rmi <-f> 镜像名<:tags>
# 运行容器
docker run -p 8000:8080 -d 镜像名<:tags>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 0x04 Docker容器内部结构
一个Docker容器创建好了以后,我们可以进入到容器内部,执行相关的命令查看其内部结构。
以前文创建的Tomcat容器为例:
查看容器id
docker ps -a
[root@basic ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dd1a6b408cdf hello-world "/hello" 14 hours ago Exited (0) 14 hours ago priceless_kapitsa
3854be1d5f93 tomcat:8.5-jdk8-openjdk "catalina.sh run" 25 hours ago Up 24 hours 0.0.0.0:8090->8080/tcp awesome_solomon
2
3
4
得到tomcat容器的id为 3854be1d5f93
进入容器内部
docker exec [-it] 容器id 命令
- exec 在对应容器中执行命令
- -it 采用交互式方式执行命令
因此,进入tomcat容器内部可这样做:
docker exec -it 3854be1d5f93 /bin/bash
交互式进入Tomcat容器内部,并开启一个bash终端,
[root@basic ~]# docker exec -it 3854be1d5f93 /bin/bash
root@3854be1d5f93:/usr/local/tomcat#
2
自动定位到容器内部的/usr/local/tomcat
目录,我们可以在容器内执行一些命令。
执行脚本命令
Tomcat容器内置了一个小型的Linux OS,可以执行cat /proc/version
看一下其Linux版本:
root@3854be1d5f93:/usr/local/tomcat# cat /proc/version
Linux version 3.10.0-957.el7.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1 SMP Thu Nov 8 23:39:32 UTC 2018
2
Tomcat运行依赖于Java环境,再来看一下容器内部Java的版本:
root@3854be1d5f93:/usr/local/tomcat# java -version
openjdk version "1.8.0_275"
OpenJDK Runtime Environment (build 1.8.0_275-b01)
OpenJDK 64-Bit Server VM (build 25.275-b01, mixed mode)
2
3
4
# 0x05 容器生命周期
Docker容器的生命周期中主要有以下几种状态:
- stopped
- running
- paused
- deleted
容器的状态在我们实际工作中还是挺有用的,容器出问题时,我们首先看一下它的状态有利于我们定位问题。
容器生命周期的状态有对应的docker命令。
- docker create 创建一个新的容器但不启动它
- docker run 创建一个新的容器并运行
- docker start/stop/restart 启动、停止、重启容器
- docker kill 杀掉运行中的容器。docker stop是优雅的退出,退出前发送一些信号,docker内部应用做一些退出前的准备工作后再退出;docker kill是应用程序直接退出。
- docker rm 删除容器
- docker pause/unpause 暂停或恢复容器中的所有进程
# 0x06 Dockerfile
Dockerfile是构建镜像所用到的最重要的文件。Dockerfile是镜像的描述文件:
- Dockerfile是一个包含用于组合镜像的命令的文本文档
- Docker通过读取Dockerfile中的指令按步骤自动生成镜像
构建镜像标准命令:
docker build -t 构建者/镜像名<:tags> Dockerfile目录
比如我们构建一个简单的基于Tomcat的web镜像,就一个页面,我们需要准备一个html页面和一个Dockerfile镜像描述文件。
创建一个myweb目录,里面有个test.html文件:
<h1>Hello,Dockerfile!<h1>
Dockerfile文件:
FROM xblzer/tomcat:8.5
MAINTAINER xblzer
WORKDIR /usr/local/tomcat/webapps
ADD myweb ./myweb
2
3
4
TIP:此处myweb和Dockerfile要在同一级目录。
另外,这个
xblzer/tomcat:8.5
基础镜像是我之前构建好的。也可以使用官方的,不过有可能官方的访问不了页面资源(404),这是因为镜像内部的webapps目录是空的,但它里面有webaaps.dist目录,需要把webaaps.dist里面的内容拷贝到webapps下才行。
Dockerfile中的基本命令:
FROM
- FROM 镜像名<:tags> 基于基准镜像构建
- FROM scrash 不依赖于任何基准镜像
LABEL & MAINTAINER
- 镜像的说明信息
- 例:
MAINTAINER xblzer
LABEL version=1.0
LABEL description="行百里er"
2
3
WORKDIR
- 设置工作目录,指容器内部的工作目录,需要了解FROM的基础镜像内部的结构
- 尽量使用绝对路径
比如我们刚才构建的web镜像,指定工作目录是/usr/local/tomcat/webapps
。
ADD & COPY
- ADD和COPY都是复制文件的命令
- ADD还有远程复制文件的功能,类似于wget,使用较少
ENV
- 设置环境常量
- 例如:
ENV JAVA_HOME=/usr/local/java
RUN ${JAVA_HOME}/bin/java -jar test.jar
2
根据Dockerfile构建镜像
在Dockerfile所在目录执行:
docker build -t xblzer/myweb:1.0 .
注意,最后面的“ . ”。
[root@basic mydockerfile]# docker build -t xblzer/myweb:1.0 .
Sending build context to Docker daemon 3.584kB
Step 1/4 : FROM xblzer/tomcat:8.5
---> ad4eef1cdffc
Step 2/4 : MAINTAINER xblzer
---> Running in 00ff37cb7a66
Removing intermediate container 00ff37cb7a66
---> 6675a0a2b8be
Step 3/4 : WORKDIR /usr/local/tomcat/webapps
---> Running in c753825a9dc3
Removing intermediate container c753825a9dc3
---> cd5999c1d8ff
Step 4/4 : ADD myweb ./myweb
---> 9262ba119d14
Successfully built 9262ba119d14
Successfully tagged xblzer/myweb:1.0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
构建完了,用 docker images 看一下:
[root@basic mydockerfile]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
xblzer/myweb 1.0 9262ba119d14 2 minutes ago 537MB
xblzer/tomcat 8.5 ad4eef1cdffc 17 minutes ago 537MB
tomcat 8.5-jdk8-openjdk 5a5e790eb3eb 4 days ago 533MB
hello-world latest bf756fb1ae65 10 months ago 13.3kB
2
3
4
5
6
我们构建的镜像xblzer/test-web:1.0
出现在了镜像列表中。
有了镜像,按照传统Docker的运行方式,自然是docker run
启动一个容器:
docker run -d -p 8000:8080 xblzer/myweb:1.0
然后访问:http://192.168.2.110:8000/myweb/test.html
这说明根据我们构建的镜像运行的容器没有问题!
# 0x07 镜像分层
再回头来看一下我之前构建myweb的步骤:
Dockerfile文件:
FROM xblzer/tomcat:8.5
MAINTAINER xblzer
WORKDIR /usr/local/tomcat/webapps
ADD myweb ./myweb
2
3
4
构建时的步骤:
Step 1/4 : FROM xblzer/tomcat:8.5
---> ad4eef1cdffc
Step 2/4 : MAINTAINER xblzer
---> Running in 3bfecae6049d
Removing intermediate container 3bfecae6049d
---> 927a6dcf9639
Step 3/4 : WORKDIR /usr/local/tomcat/webapps
---> Running in e98ccfd0488c
Removing intermediate container e98ccfd0488c
---> a1dcd9b4885e
Step 4/4 : ADD myweb ./myweb
---> 16c0cb847216
Successfully built 16c0cb847216
Successfully tagged xblzer/myweb:1.0
2
3
4
5
6
7
8
9
10
11
12
13
14
可以看到,每一步都创建了一个临时镜像,这个临时镜像有点类似于游戏存档,下次如果有用到就直接用这个临时容器了。
这一个一个的临时镜像就是镜像的分层。
我们来验证一下。
创建一个Dockerfile:
FROM centos
RUN ["echo", "aaa"]
RUN ["echo", "bbb"]
RUN ["echo", "ccc"]
RUN ["echo", "ddd"]
2
3
4
5
构建:
docker build -t xblzer/test-layer:1.0 .
再将Dockerfile改一下,构建一个1.1版本的镜像:
FROM centos
RUN ["echo", "aaa"]
RUN ["echo", "not bbb!!"]
RUN ["echo", "not ccc!!!"]
RUN ["echo", "ddd"]
2
3
4
5
构建:
docker build -t xblzer/test-layer:1.1 .
可以看到,第1、2步,使用了之前的临时镜像。
我们使用 docker history 看一下两个镜像的历史:
版本1.1的前两个和镜像1.0版本的前两步是相同的。
# 0x08 Docker容器间的通信
Docker在应用部署方面给我们提供了很大的便利,很多情况下,一个应用部署在一个Docker容器中。比如应用程序和数据库都可以用Docker部署。
那么在这种情况下,应用程序的Docker容器如何访问数据库的Docker容器呢?这就涉及到容器间的通信问题。
用docker容器的虚拟ip当然是可以的,查看docker容器ip地址可以使用如下命令:
docker inspect 容器id
但是,线上真是环境一般是不会这么用的,因为容器有可能会被误操作而导致容器内部IP地址改变,从而引发连接不通的情况。
Docker的解决方案是给容器起个名字(docker run --name),用容器的名称进行容器间的通信。
Docker容器间的通信方式:
- Link 单向访问
- Bridge 网桥双向访问
下面我们创建两个容器,来实验容器间的通信。
# 8.1 创建容器
创建一个名称为web的容器
docker run -d --name web tomcat:8.5-jdk8-openjdk
再创建一个名称为db的容器
docker run -d -it --name db centos /bin/bash
然后分别使用docker inspect 容器id
命令查看两个容器的虚拟ip。
db容器:172.17.0.4
web容器:172.17.0.3
使用虚拟IP进入web容器ping DB容器:
同样,db容器内也能ping通web容器的虚拟IP:
# 8.2 容器间Link单向通信
前文说了,我们一般不使用虚拟IP地址来进行通信,使用容器的name,这就需要在创建容器的时候指定和哪个容器进行 link 。
使用 --link 创建web容器,使其连接db容器:
docker run -d --name web --link db tomcat:8.5-jdk8-openjdk
然后进入到该容器内部,直接 ping db 就可以连接。
# 8.3 Bridge网桥双向通信
使用 --link 可以实现容器间的单向通信,
比如我没有让db容器link到web容器,在db容器内部ping一下web看看:
有时候我们希望两个容器能够互联互通,怎么办呢?
让两个容器互相link,这种方法可以,但是容器很多的情况下会很麻烦。
Docker提供了Bridge网桥的方式,让一组绑定到网桥上的Docker能够互联互通。
# 8.3.1 创建bridge网桥并绑定容器
1. 创建网桥
docker network create -d bridge my-bridge
2. 查看网桥
docker network ls
3. 容器与网桥绑定
将web和db容器都绑定到创建的 my-bridge 网桥:
# 先后执行
docker network connect my-bridge web
docker network connect my-bridge db
2
3
这个时候再进入db容器就能ping通web容器了:
在加一个容器,也绑定到 my-bridge 网桥上
docker run -d -it --name myapp centos /bin/bash
docker network connect my-bridge myapp
2
进入到myapp容器,分别连web和db容器:
均能互联互通。
# 8.3.2 网桥实现原理
每当创建了一个网桥,它都会在宿主机上安装一个虚拟网卡,承担网关的作用,由虚拟网卡构成的网关形成了内部的一个通路,只要有容器绑定到这个虚拟网卡上,就都能互联互通。
但是,虚拟网卡终究是虚拟的,IP地址也是虚拟的,如果要和外部通信的话,还必须要和宿主机的物理网卡进行地址转换。
容器内部发送的数据包都会经过虚拟网卡做地址转换,将其转成物理网卡的数据包向外网进行通信;
同样,从外网回来的数据先进入物理网卡,之后再通过地址转换进入到虚拟网卡,再由虚拟网卡进行数据的分发。
# 0x09 Docker容器间共享数据
# 9.1 为什么要进行数据共享
我现在所在的项目做持续部署的时候,每次提交代码后都用Docker构建镜像,启动容器。
试想一下,每次都启动,那么日志啊、图片啊、文件啊什么的就都没了,这是不行的,因此需要指定一个宿主机上的实际存在的目录和Docker容器内部相应的路径进行对应。
还有一种场景就是多个容器间都需要访问一些公共的静态页面,这是也可以把公共的页面放到一个固定的地方,让容器进行目录挂载。
# 9.2 通过设置 -v 挂载宿主机目录
命令格式
docker run --name 容器名 -v 宿主机路径:容器内挂载路径 镜像名
还是以tomcat容器为例。
将tomcat容器内部的/usr/local/tomcat/webapps
目录映射到宿主机/usr/webapps
目录,这样访问tomcat的页面时就会访问宿主机的/usr/webapps
下的页面。
docker run -d -p 8001:8080 --name app1 -v /usr/webapps:/usr/local/tomcat/webapps xblzer/tomcat:8.5
在宿主机/usr/webapps
下创建app-web
目录,并新建test.html
:
[root@basic webapps]# mkdir app-web
[root@basic webapps]# cd app-web/
[root@basic app-web]# vim test.html
2
3
html内容:
<h1>111</h1>
此时访问:http://192.168.2.110:8001/app-web/test.html
:
这个时候我们来修改一下/usr/webapps/app-web/test.html
:
<h1>111</h1>
<h1>222 added</h1>
2
不用重启docker容器,再次访问:
牛逼不?不用重启Docker容器就能访问更新过的文件!
# 9.3 通过 --volumes-from 共享容器内挂载点
还有一种挂载目录的方法就是通过创建共享容器的方式来实现。
创建共享容器
docker create --name commonpage -v /usr/webapps:/usr/local/tomcat/webapps xblzer/tomcat:8.5 /bin/true
/bin/true 是占位符,无实际意义。
共享容器挂载点
docker run -d -p 8002:8080 --volumes-from commonpage --name app2 xblzer/tomcat:8.5
共享容器的目的是定义好挂载点,然后其他容器通过 --volumes-from 共享容器名
来实现和共享容器同样的挂载目录。
这样做的好处是,如果容器非常多,而且挂载目录有变化时,不用每个容器都去通过-v
修改挂载点,只需要修改共享容器的挂载目录即可。
再来创建一个app3容器:
docker run -d -p 8003:8080 --volumes-from commonpage --name app3 xblzer/tomcat:8.5
此时访问http://192.168.2.110:8002/app-web/test.html
和http://192.168.2.110:8003/app-web/test.html
均能访问到test.html页面。
# 0x10 Docker Compose
Docker Compose是Docker官方提供的容器编排工具,所谓容器编排就是按顺序将一个个应用(微服务)在网络级别进行组织,以使其能够按照计划运行。
- Docker Compose 是单机多容器部署工具,只能在一台主机上工作
- 通过yml文件定义多容器如何部署
- Linux下需要安装Docker Compose
安装方法:
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
2
3
4
5
然而,众所周知,Kubernetes才是当下最流行的容器编排平台,不管是生产环境的采用率,还是云原生生态都很强大。
so,这里不说太多了,给自己挖个坑,敬请期待吧~
首发公众号 行百里er ,欢迎老铁们关注阅读指正。