容器数据卷

什么是容器数据卷

Docker的理念回顾

将应用和环境打包成一个镜像,可以快速部署在不同的平台。

问题:如果数据都在容器中,那么如果我们删除容器,数据就会丢失!

需求:数据可持续化,数据可以存储在宿主机。

引入:容器之间可以有一个数据共享的技术,Docker容器中产生的数据,同步到宿主机,这就是容器数据卷技术。

总结:将容器内的目录挂载在宿主机上,实现容器数据的持久化和同步操作,容器间的数据共享。

容器数据卷

使用数据卷

挂载数据卷方式1:运行容器时直接使用命令 -v来挂载

docker run -it -v 主机目录:容器目录

# 测试
[root@xizou /]# cd /home/
[root@xizou home]# ls
[root@xizou home]# docker run -it -v /home/ceshi:/home centos /bin/bash

# Ctrl +p +q退出容器
[root@xizou /]# cd /home/
[root@xizou home]# ls
ceshi

# 可以通过 docker inspect 容器id 查看容器挂载信息

双向绑定挂载数据卷

测试文件的同步

数据卷挂载测试

就算容器停止,宿主机上修改文件,修改后的文件也能同步到容器内。

实战:安装MySQL

思考:MySQL的数据持久化的问题

# 获取镜像
[root@xizou /]# docker pull mysql:5.7

# 运行容器,需要做数据挂载,安装MySql上需要配置密码,参考Docker Hub的官方指令 docker run --name some-mysql -v /my/custom:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

# docker run 命令
-d 后台运行
-p 端口映射
-v 卷挂载
-e 环境配置
--name 容器名字

[root@xizou /]# docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
4dc2e63a55e463168c39249dd4c4cdec2a1d43a1c4fa6f9045da2dd4099fa3a9

# 启动成功之后,就可以使用 Navicat Premium 或任意数据库连接软件远程连接到容器内的MySQL
# Navicat Premium - 连接到服务器的3310端口 -- 服务器的3310端口与容器内的3306端口映射,因此就能连接到服务器启动的容器内部的MySQL
# 在Navicat Premium创建一个数据库test,可以发现服务器/home/mysql/data目录下会多一个test目录,并且容器内/var/lib/mysql目录下也会多一个test目录

将容器删除,发现挂载到服务器的数据卷依旧没有丢失,这体现了容器数据持久化功能。

具名挂载和匿名挂载

# 匿名挂载
-v 容器内路径
[root@xizou /]# docker run -d -P --name nginx01 -v /etc/nginx nginx
e5b3b9b6b45b916a91ab149604bea917995cdea321ba3158be51503010624c90

# 查看容器卷的情况
[root@xizou /]# docker volume ls
DRIVER VOLUME NAME
local 18ae53d1715fed2dd0250ae1dc8ab0a5a06bd6efcf801f7c46e9ed9bd7ffdd3d

# 这种就是匿名挂载,在 -v 只写了容器内的路径,没有写容器外的路径

# 具名挂载
[root@xizou /]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
dbcd1852923d857b825f9afff4429e79c29617e45402ae54e12b8861638c1c7e
[root@xizou /]# docker volume ls
DRIVER VOLUME NAME
local 18ae53d1715fed2dd0250ae1dc8ab0a5a06bd6efcf801f7c46e9ed9bd7ffdd3d
local juming-nginx

# 通过 -v 卷名:容器内路径
# 查看该卷
[root@xizou /]# docker volume inspect juming-nginx
[
{
"CreatedAt": "2022-10-30T16:53:03+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/juming-nginx/_data",
"Name": "juming-nginx",
"Options": null,
"Scope": "local"
}
]

[root@xizou /]# cd /var/lib/docker/volumes/juming-nginx/_data/
[root@xizou _data]# ls
conf.d mime.types nginx.conf uwsgi_params
fastcgi_params modules scgi_params

所有的Docker容器内的卷,没有指定宿主机目录的情况下都是放在宿主机的 /var/lib/docker/volumes/xxx/_data下。

通过具名挂载可以方便的找到我们的一个卷,大多数情况下使用具名挂载

# 如何区分具名挂载和匿名挂载
-v 容器内路径 # 匿名挂载
-v 卷名:容器内路径 # 具名挂载
-v /宿主机路径:容器内路径 # 指定路径挂载

拓展

# 通过 -v 容器内路径,ro、rw改变读写权限
ro readonly # 只读
rw readwrite # 可读可写
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx

# ro说明该路径的文件只能通过宿主机来改变,容器内部无法操作

初始Dockerfile

Dockerfile是用来构建Docker镜像的构建文件。

挂载数据卷方式2:Dockerfile文件内定义

如,创建一个dockerfile1文件,内容为:

# 创建一个dockerfile文件,名字可以随意,建议为Dockerfile
# 文件中的内容 指令(大写) 参数
FROM centos

VOLUME ["volume01", "volume02"]

CMD echo "----end----"
CMD /bin/bash

生成镜像

[root@xizou docker-test-volume]# docker build -f dockerfile1 -t xizou/centos:1.0 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos
---> 5d0da3dc9764
Step 2/4 : VOLUME ["volume01", "volume02"]
---> Running in 6cc1bc751006
Removing intermediate container 6cc1bc751006
---> f3475e56ea65
Step 3/4 : CMD echo "----end----"
---> Running in 3eeb578f3d3d
Removing intermediate container 3eeb578f3d3d
---> 3db916284cfc
Step 4/4 : CMD /bin/bash
---> Running in d0b660ad310f
Removing intermediate container d0b660ad310f
---> ee034a44e231
Successfully built ee034a44e231
Successfully tagged xizou/centos:1.0

[root@xizou docker-test-volume]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
xizou/centos 1.0 ee034a44e231 27 seconds ago 231MB
tomcat02 1.0 600af0d3c7c9 20 hours ago 684MB
elasticsearch 8.4.3 ce2b9dc7fe85 3 weeks ago 1.26GB
nginx latest 605c77e624dd 10 months ago 141MB

# 运行容器
[root@xizou docker-test-volume]# docker run -it ee034a44e231 /bin/bash
[root@6d21590582da /]# ls -l
total 56
lrwxrwxrwx 1 root root 7 Nov 3 2020 bin -> usr/bin
drwxr-xr-x 5 root root 360 Oct 30 09:23 dev
drwxr-xr-x 1 root root 4096 Oct 30 09:23 etc
drwxr-xr-x 2 root root 4096 Nov 3 2020 home
lrwxrwxrwx 1 root root 7 Nov 3 2020 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Nov 3 2020 lib64 -> usr/lib64
drwx------ 2 root root 4096 Sep 15 2021 lost+found
drwxr-xr-x 2 root root 4096 Nov 3 2020 media
drwxr-xr-x 2 root root 4096 Nov 3 2020 mnt
drwxr-xr-x 2 root root 4096 Nov 3 2020 opt
dr-xr-xr-x 130 root root 0 Oct 30 09:23 proc
dr-xr-x--- 2 root root 4096 Sep 15 2021 root
drwxr-xr-x 11 root root 4096 Sep 15 2021 run
lrwxrwxrwx 1 root root 8 Nov 3 2020 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Nov 3 2020 srv
dr-xr-xr-x 13 root root 0 Oct 30 09:23 sys
drwxrwxrwt 7 root root 4096 Sep 15 2021 tmp
drwxr-xr-x 12 root root 4096 Sep 15 2021 usr
drwxr-xr-x 20 root root 4096 Sep 15 2021 var
drwxr-xr-x 2 root root 4096 Oct 30 09:23 volume01
drwxr-xr-x 2 root root 4096 Oct 30 09:23 volume02

# 可以看见生成挂载的两个数据卷目录 volume01和volume02

验证:这个卷和外部一定有一个同步的目录

在容器的volume01内创建一个container.txt文件

[root@09423fb0f1d5 /]# cd volume01/
[root@09423fb0f1d5 volume01]# ls
[root@09423fb0f1d5 volume01]# touch container.txt
[root@09423fb0f1d5 volume01]# ls
container.txt

查看下卷挂载的路径 docker inspect 容器id,如可以看到信息:

卷挂载路径

[root@xizou /]# cd /var/lib/docker/volumes/6fc2791f3e981e53c16a71d17d36c8279fda6210956603e69c9f75ccc9c0669c/_data
[root@xizou _data]# ls
container.txt

容器间数据共享实现多个MySQL数据同步。

数据卷容器

启动第1个容器docker01

父容器

启动第2个容器docker02,数据卷继承自docker01,docker01成为数据卷容器。

docker run -it --name docker02 --volumes-from docker01 xizou/centos:1.0

删掉容器docker01,docker02依旧可以访问数据卷。数据卷之间是拷贝的概念。

多个MySQL实现数据共享

[root@xizou /]# docker run -d -p 3310:3306 -v /etc/mysql/conf.d -v /var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7

[root@xizou /]# docker run -d -p 3311:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql02 --volumes-from mysql01 mysql:5.7

结论

容器之间配置信息的传递,数据卷容器的生命周期一直持续到没有容器使用为止。

但是一旦持久化到了本地,本地的数据是不会自动删除的。

Dockerfile

Dockerfile介绍

Dockerfile是用来构建Docker镜像的文件,是一个命令参数脚本文件。

构建步骤

1、编写Dockerfile文件

2、docker build构建为一个镜像

3、docker run 运行镜像

4、docker push 发布镜像(Docker Hub、阿里云镜像仓库)

示例:CentOS7的Dockerfile文件,点击tag可跳转到Github,看到其Dockerfile文件。

Docker Hub CentOS7界面

CentOS7的Dockerfile文件

很多官方镜像都是基础包,很多功能没有,通常需要自己搭建自己的镜像。

Dockerfile构建过程

基础知识:

1、每个保留关键字(指令)都必须是大写字母

2、执行从上到下顺序执行

3、#表示注释

4、每一个指令都会创建和提交一个镜像层

Docker镜像制作

Dockerfile是面向开发的,发布项目制作镜像就需要写Dockerfile文件,Docker镜像逐渐成为企业交付的标准,必须掌握。

Dockerfile:构建文件,定义了一切的步骤,源代码,用来构建镜像。

Docker镜像:发布和运行的产品,可以通过commit指令或Dockerfile来制作。

Docker容器:镜像运行起来提供服务的服务器。

Dockerfile命令

FROM		 # 基础镜像,一切从这里开始构建
MAINTAINER # 镜像是谁写的,姓名+邮箱
RUN # 镜像构建时需要运行的指令
ADD # 步骤,添加内容
WORKDIR # 镜像的工作目录
VOLUME # 挂载的目录
EXPOSE # 暴露端口配置
CMD # 指定这个容器启动的时候要运行的命令,只有最后一个CMD命令会生效,可被替换
ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令
ONBUILD # 当构建一个被继承的Dockerfile时就会运行ONBUILD的指令,触发指令
COPY # 类似ADD,将文件拷贝到镜像中
ENV # 构建的时候设置环境变量

Dockerfile命令比喻

Dockerfile实战

Docker Hub 中大部分镜像是从基础镜像 scratch 开始的,然后配置需要的软件来进行构建的。

创建自己的CentOS镜像

CentOS镜像默认的根目录为/,且没有ifconfig

# 1. 编写Dockerfile镜像
[root@xizou dockerfile]# cat mydockerfile-centos
FROM centos:7
MAINTAINER xizou<xiongbinzou@163.com>

ENV MYPATH /usr/local
WORKDIR $MYPATH

RUN yum -y install vim
RUN yum -y install net-tools

EXPOSE 80

CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash

# 2. 通过这个文件构建镜像
# 命令是 docker build -f dockerfile文件路径 -t 镜像名[:tag] .
[root@xizou dockerfile]# docker build -f mydockerfile-centos -t centos:0.1 .

# 构建成功
.....
Successfully built 0fc60a3f44de
Successfully tagged mycentos:0.1

[root@xizou dockerfile]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mycentos 0.1 0fc60a3f44de 2 minutes ago 626MB

# 3. 测试运行,发现默认目录变更且ifconfig和vim指令都能够支持
[root@xizou ~]# docker run -it mycentos:0.1
[root@c7cad6e52fd3 local]# pwd
/usr/local
[root@c7cad6e52fd3 local]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.3 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:03 txqueuelen 0 (Ethernet)
RX packets 8 bytes 656 (656.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

[root@c7cad6e52fd3 local]# vim --help
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Dec 15 2020 16:44:08)

usage: vim [arguments] [file ..] edit specified file(s)
or: vim [arguments] - read text from stdin
or: vim [arguments] -t tag edit file where tag is defined
or: vim [arguments] -q [errorfile] edit file with first error

# 4. 查看指定image构建历史(先回到宿主机)
[root@xizou dockerfile]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mycentos 0.1 0fc60a3f44de 2 hours ago 626MB
xizou/centos 1.0 ee034a44e231 5 hours ago 231MB
tomcat02 1.0 600af0d3c7c9 24 hours ago 684MB

[root@xizou dockerfile]# docker history 0fc60a3f44de
IMAGE CREATED CREATED BY SIZE COMMENT
0fc60a3f44de 2 hours ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/bin… 0B
87d94a5cd7db 2 hours ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B
701f4da62eda 2 hours ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B
a28bd4c7b6d4 2 hours ago /bin/sh -c #(nop) EXPOSE 80 0B
15d49edff70d 2 hours ago /bin/sh -c yum -y install net-tools 183MB
565a95d30b44 2 hours ago /bin/sh -c yum -y install vim 238MB
1dda317404d4 2 hours ago /bin/sh -c #(nop) WORKDIR /usr/local 0B
ca7984844f10 2 hours ago /bin/sh -c #(nop) ENV MYPATH=/usr/local 0B
6e566e3aace3 2 hours ago /bin/sh -c #(nop) MAINTAINER xizou<xiongbin… 0B
eeb6ee3f44bd 13 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 13 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 13 months ago /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4… 204MB

拿到一个镜像后,可以通过docker history 镜像id查看镜像的构建历史,分析该镜像Dockerfile文件的组成。

CMD和ENREYPOINT的区别

CMD			 # 指定这个容器启动的时候要运行的命令,只有最后一个CMD命令会生效,可被替换
ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令

测试 CMD 命令

# 1.进入文件
[root@xizou /]# cd /home/dockerfile/
# 2.创建dockerfile
[root@xizou dockerfile]# cat dockerfile-cmd-test
FROM centos:7
CMD ["ls","-a"]
# 3.构建镜像
[root@xizou dockerfile]# docker build -f dockerfile-cmd-test -t cmdtest .
Sending build context to Docker daemon 3.072kB
Step 1/2 : FROM centos:7
---> eeb6ee3f44bd
Step 2/2 : CMD ["ls","-a"]
---> Running in 850c034e822a
Removing intermediate container 850c034e822a
---> 0b3e53a3ddc6
Successfully built 0b3e53a3ddc6
Successfully tagged cmdtest:latest
# 4.运行,发现 ls -a 命令生效
[root@xizou dockerfile]# docker run 0b3e53a3ddc6
.
..
.dockerenv
anaconda-post.log
bin
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

# 想追加一个命令-l,ls -al
[root@xizou dockerfile]# docker run 0b3e53a3ddc6 -l
docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "-l": executable file not found in $PATH: unknown.

# cmd的清理下 -l 替换了CMD ["ls","-a"]命令,-l 不是命令,所以报错

测试 ENTRYPOINT 命令

# 1.进入文件
[root@xizou /]# cd /home/dockerfile/
# 2.创建dockerfile
[root@xizou dockerfile]# cat dockerfile-entrypoint-test
FROM centos:7
ENTRYPOINT ["ls","-a"]
# 3.构建镜像
[root@xizou dockerfile]# docker build -f dockerfile-entrypoint-test -t entrypointtest .
Sending build context to Docker daemon 4.096kB
Step 1/2 : FROM centos:7
---> eeb6ee3f44bd
Step 2/2 : ENTRYPOINT ["ls","-a"]
---> Running in 6ebfea05232f
Removing intermediate container 6ebfea05232f
---> 16b996a429de
Successfully built 16b996a429de
Successfully tagged entrypointtest:latest
# 4.运行,发现 ls -a 命令生效
[root@xizou dockerfile]# docker run 16b996a429de
.
..
.dockerenv
anaconda-post.log
bin
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

# 想追加一个命令-l,ls -al,entrypoint没有替换命令,命令是拼接在后面
[root@xizou dockerfile]# docker run 16b996a429de -l
total 64
drwxr-xr-x 1 root root 4096 Oct 30 14:21 .
drwxr-xr-x 1 root root 4096 Oct 30 14:21 ..
-rwxr-xr-x 1 root root 0 Oct 30 14:21 .dockerenv
-rw-r--r-- 1 root root 12114 Nov 13 2020 anaconda-post.log
lrwxrwxrwx 1 root root 7 Nov 13 2020 bin -> usr/bin
drwxr-xr-x 5 root root 340 Oct 30 14:21 dev
drwxr-xr-x 1 root root 4096 Oct 30 14:21 etc
drwxr-xr-x 2 root root 4096 Apr 11 2018 home
lrwxrwxrwx 1 root root 7 Nov 13 2020 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Nov 13 2020 lib64 -> usr/lib64
drwxr-xr-x 2 root root 4096 Apr 11 2018 media
drwxr-xr-x 2 root root 4096 Apr 11 2018 mnt
drwxr-xr-x 2 root root 4096 Apr 11 2018 opt
dr-xr-xr-x 99 root root 0 Oct 30 14:21 proc
dr-xr-x--- 2 root root 4096 Nov 13 2020 root
drwxr-xr-x 11 root root 4096 Nov 13 2020 run
lrwxrwxrwx 1 root root 8 Nov 13 2020 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Apr 11 2018 srv
dr-xr-xr-x 13 root root 0 Oct 30 09:23 sys
drwxrwxrwt 7 root root 4096 Nov 13 2020 tmp
drwxr-xr-x 13 root root 4096 Nov 13 2020 usr
drwxr-xr-x 18 root root 4096 Nov 13 2020 var

创建自己的Tomcat镜像

1、准备镜像文件 tomcat压缩包和jdk压缩包

本教程使用的是 apache-tomcat-9.0.68.tar.gzjdk-8u161-linux-x64.tar.gz。需要自己本地下载,然后上传到服务器。

2、编写Dockerfile文件

FROM centos:7
MAINTAINER xizou<xiongbinzou@163.com>

COPY readme.txt /usr/local/readme.txt

ADD jdk-8u161-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.68.tar.gz /usr/local/

RUN yum -y install vim

ENV MYPATH /usr/local
WORKDIR $MYPATH

ENV JAVA_HOME /usr/local/jdk1.8.0_161
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.68
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.68
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD /usr/local/apache-tomcat-9.0.68/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.68/bin/logs/catalina.out

3、构建镜像

[root@xizou tomcat]# docker build -t diytomcat .
......
Successfully built 1a0b96225372
Successfully tagged diytomcat:latest
[root@xizou tomcat]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
diytomcat latest 1a0b96225372 About a minute ago 843MB

4、运行容器

# 使用自定义的tomcat镜像构建容器
[root@xizou tomcat]# docker run -d -p 9090:8080 --name xizoutomcat -v /home/xizou/build/tomcat/test:/usr/local/apache-tomcat-9.0.68/webapps/test -v /home/xizou/build/tomcat/logs/:/usr/local/apache-tomcat-9.0.68/logs diytomcat
3a3378620c68d7619622f4914c801bec41686d4f8caabdcdfe97ca867657b40a

# 查看当前目录
[root@xizou tomcat]# ls
apache-tomcat-9.0.68.tar.gz logs
Dockerfile readme.txt
jdk-8u161-linux-x64.tar.gz test

# 进入容器
[root@xizou tomcat]# docker exec -it 3a3378620c68 /bin/bash

# 查看目录
[root@3a3378620c68 local]# ls
aegis etc jdk1.8.0_161 libexec share
apache-tomcat-9.0.68 games lib readme.txt src
bin include lib64 sbin
[root@3a3378620c68 local]# cd apache-tomcat-9.0.68/
[root@3a3378620c68 apache-tomcat-9.0.68]# ls
BUILDING.txt NOTICE RUNNING.txt lib webapps
CONTRIBUTING.md README.md bin logs work
LICENSE RELEASE-NOTES conf temp

5、访问测试

[root@xizou /]# curl localhost:9090
# 可以正常访问

6、发布项目(由于做了卷挂载,直接在本地可以发布)

进入test目录,创建WEB-INF目录和index.jsp,进入WEB-INF目录创建web.xml

index.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>小邹同学(xiongbinzou.github.io)</title>
</head>
<body>
Hello World!<br/>
<%
System.out.println("---Welcome to my blog---");
%>
</body>
</html>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<display-name>Archetype Created Web Application</display-name>
</web-app>

打开浏览器,输入 你的服务器ip地址+端口/test/ ,就能看到Hello World!

访问成功

发布自己的镜像

发布到DockerHub网站

1、在DockerHub网站注册自己的账号

2、确定账号可以登陆

3、在我们的服务器上提交自己的镜像到DockerHub

[root@xizou tomcat]# docker login --help

Usage: docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
-p, --password string Password
--password-stdin Take the password from stdin
-u, --username string Username

[root@xizou tomcat]# docker login -u xizou1995
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

4、登陆完毕后

Login Succeeded

# push自己的镜像到服务器上!
[root@xizou tomcat]# docker push diytomcat
Using default tag: latest
The push refers to repository [docker.io/library/diytomcat]
5132fa4d71f7: Preparing
61502ae68a5a: Preparing
09ef56b9dc63: Preparing
6d92c54bcf47: Preparing
174f56854903: Preparing
denied: requested access to the resource is denied # 被拒绝

# 修改镜像名为 Dockerhub上你的用户名/镜像名:tag
[root@xizou tomcat]# docker tag hello-world xizou1995/hello-world:1.0
[root@xizou tomcat]# docker push xizou1995/hello-world:1.0
The push refers to repository [docker.io/xizou1995/hello-world]
e07ee1baac5f: Mounted from library/hello-world
1.0: digest: sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 size: 525

5、在DockerHub网站你的页面上可以看到

Docker镜像库

发布到阿里云镜像服务上

1、登陆阿里云

2、找到容器镜像服务

3、找到镜像仓库,如果没有需要自己创建个人实例,在个人实例里能找到镜像仓库

4、在个人实例里创建命名空间

创建命名空间

5、创建镜像仓库,选择本地仓库

创建镜像仓库

6、点击仓库名称浏览信息

阿里云镜像操作指南

小结

Dockerfile操作流程

Docker网络原理

理解Docker0

先清空所有的容器和镜像

docker rm -f $(docker ps -aq)
docker rmi -f $(docker images -aq)

测试

ip addr

三个网络

问题:Docker是如何处理容器网络访问的?比如容器内的tomcat访问容器内的mysql。

[root@xizou /]# docker run -d -P --name tomcat01 tomcat

# 查看容器的内部网络地址 ip addr
[root@xizou /]# docker exec -it tomcat ip addr
# 如果执行失败,进入容器内部安装工具 apt update && apt -y install iproute2,然后再退出容器
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

# 容器启动时会得到一个 eth0@if5 ip地址,docker分配的

[root@xizou /]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.058 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.044 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.046 ms
64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.055 ms

# linux可以ping通docker容器内部

原理

1、每启动一个docker容器,docker就会给docker容器分配一个ip,只要安装了docker,就会有一个网卡docker0桥接技术,使用的是evth-pair技术

[root@xizou /]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:16:3e:06:ad:10 brd ff:ff:ff:ff:ff:ff
inet 172.20.179.171/20 brd 172.20.191.255 scope global dynamic eth0
valid_lft 315327838sec preferred_lft 315327838sec
inet6 fe80::216:3eff:fe06:ad10/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:46:b8:0a:f8 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:46ff:feb8:af8/64 scope link
valid_lft forever preferred_lft forever
5: veth33d487b@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 36:f0:95:72:d4:2b brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::34f0:95ff:fe72:d42b/64 scope link
valid_lft forever preferred_lft forever

ip地址多了一个5: veth33d487b@if4,是为容器内网卡对应的网卡。

发现容器带来的网卡都是成对出现的,一个在容器内部,一个在宿主机,evth-pair技术充当一个桥梁,让宿主机可以与容器内部通信。

按照相同的方式创建tomcat02容器,然后让tomcat01容器ping tomcat02容器,可以发现能够ping通。

tomcat01与tomcat02容器网络通过宿主机内的docker0连接。

tomcat01与tomcat02网络通信示意

所有的容器在不指定网络的情况下,都是docker0路由的,docker会给容器分配一个默认的可用IP。

结论

Docker通过宿主机的Docker0进行桥接,Docker中所有的网络接口都是虚拟的,转发效率高。只要容器删除,虚拟网卡也会消失。

Docker桥接示意

编写一个微服务,database url=ip,项目不启动,数据库ip换掉了,可以通过名字来访问容器,实现高可用。

[root@xizou /]# docker exec -it tomcat02 ping tomcat01
ping: tomcat01: Name or service not known

# 如何解决?
[root@xizou /]# docker run -d -P --name tomcat03 --link tomcat02 tomcat
6a603a98a0de2fbe2504b01bfb2b40cab602feffcc9d379c5887f2364e6ff5df
# 此时tomcat3可以ping通tomcat02
[root@xizou /]# docker exec -it tomcat03 ping tomcat02
# 如果提示ping命令不存在,请执行
docker exec -it tomcat03 apt-get update
docker exec -it tomcat03 apt install iputils-ping

# tomcat02能够ping同tomcat03吗? 不能
[root@xizou /]# docker exec -it tomcat02 ping tomcat03
ping: tomcat03: Name or service not known

[root@xizou /]# docker network ls
NETWORK ID NAME DRIVER SCOPE
8e9b37aaa9ce bridge bridge local
0673ec2aaa1c host host local
9ba9f05fc241 none null local

[root@xizou /]# docker network inspect 8e9b37aaa9ce
[
{
"Name": "bridge",
"Id": "8e9b37aaa9ce5bb9d88f68daa4f3eea16d1c8cbfe62a127a7e019ef931687391",
"Created": "2022-10-31T01:31:03.542724354+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"3b8af7fbc37fd2905b411a9e0a3b4bbc57c2685a555aea6482ac7948858a01aa": {
"Name": "tomcat01",
"EndpointID": "db565d8188ff9fd69be16c86d193199eed1934b9ae416cdf903a12755b20fbe4",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"6a603a98a0de2fbe2504b01bfb2b40cab602feffcc9d379c5887f2364e6ff5df": {
"Name": "tomcat03",
"EndpointID": "40fb946ba46cba96573646ed6374272b1c27c490c1a1c7f14b084ddb9296f1c3",
"MacAddress": "02:42:ac:11:00:04",
"IPv4Address": "172.17.0.4/16",
"IPv6Address": ""
},
"92a91f9faa29e067d006ace0b4a1c32245b4521ab71098db1503c78f023e45c6": {
"Name": "tomcat02",
"EndpointID": "a0a96ea13aaef24383be2abd638422cfb59cfda4e670ec2a392418d7f96d4c5e",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]

docker network inspect 8e9b37aaa9ce

[root@xizou /]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6a603a98a0de tomcat "catalina.sh run" 6 minutes ago Up 6 minutes 0.0.0.0:49155->8080/tcp, :::49155->8080/tcp tomcat03
92a91f9faa29 tomcat "catalina.sh run" 41 minutes ago Up 41 minutes 0.0.0.0:49154->8080/tcp, :::49154->8080/tcp tomcat02
3b8af7fbc37f tomcat "catalina.sh run" About an hour ago Up About an hour 0.0.0.0:49153->8080/tcp, :::49153->8080/tcp tomcat01
[root@xizou /]# docker inspect tomcat03
# 在HostConfig的Links字段里能看到link到tomcat02,如:
"Links": ["/tomcat02:/tomcat03/tomcat02"],

# 也可以查看/etc/hosts文件
[root@xizou /]# docker exec -it tomcat03 cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 tomcat02 92a91f9faa29
172.17.0.4 6a603a98a0de

本质:–link 就是在容器tomcat03的hosts配置在中增加了一个tomcat02的映射

不建议使用–link实现网络映射

自定义网络

查看所有的docker网络

[root@xizou /]# docker network ls
NETWORK ID NAME DRIVER SCOPE
8e9b37aaa9ce bridge bridge local
0673ec2aaa1c host host local
9ba9f05fc241 none null local

[root@xizou /]# docker network --help

Usage: docker network COMMAND

Manage networks

Commands:
connect Connect a container to a network
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
prune Remove all unused networks
rm Remove one or more networks

Run 'docker network COMMAND --help' for more information on a command.

网络模式

bridge:桥接 docker(默认)

none:不配置网络

host:和宿主机共享网络

container:容器网络连通(不常用)

# 直接启动命令 --net bridge,这个就是docker0
docker run -d -P --name tomcat01 tomcat
等价于
docker run -d -P --name tomcat01 --net bridge tomcat

# docker0的特点:默认,域名不能访问。--link可以打通

# 创建一个自定义网络
# --driver bridge
# --subnet 192.168.0.0/16
# --gateway 192.168.0.1
[root@xizou /]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
8870e01ec415be0a02e181760b6bec5040edb660762903ec3c6dac14ffd368dd

[root@xizou /]# docker network ls
NETWORK ID NAME DRIVER SCOPE
8e9b37aaa9ce bridge bridge local
0673ec2aaa1c host host local
8870e01ec415 mynet bridge local
9ba9f05fc241 none null local

[root@xizou /]# docker network inspect mynet
[
{
"Name": "mynet",
"Id": "8870e01ec415be0a02e181760b6bec5040edb660762903ec3c6dac14ffd368dd",
"Created": "2022-10-31T11:31:39.986065834+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.0.0/16",
"Gateway": "192.168.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]

# 使用自定义网络
[root@xizou /]# docker run -d -P --name tomcat01 --net mynet tomcat
5a311d6ef0e2fdd71afc8595de172cd54f7b51cb500f7040aebf562efea4896f
[root@xizou /]# docker run -d -P --name tomcat02 --net mynet tomcat
5459203e0fb8dcf80a79d24ad6dbf07765b0d8103f879c03aef3e20ec806a4a6
[root@xizou /]# docker network inspect mynet
[
{
"Name": "mynet",
"Id": "8870e01ec415be0a02e181760b6bec5040edb660762903ec3c6dac14ffd368dd",
"Created": "2022-10-31T11:31:39.986065834+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.0.0/16",
"Gateway": "192.168.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"5459203e0fb8dcf80a79d24ad6dbf07765b0d8103f879c03aef3e20ec806a4a6": {
"Name": "tomcat02",
"EndpointID": "ee8fe045e89d3c7c20ee1ea48f1f6fbb6877967fa67e8ccaa69bab2b9ed29694",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
},
"5a311d6ef0e2fdd71afc8595de172cd54f7b51cb500f7040aebf562efea4896f": {
"Name": "tomcat01",
"EndpointID": "30d69fc865bc8020da370fd3a0e810e2f7a4e51f7ecc80a88c8bc1bfe66ce541",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]

# 再次测试ping连接,使用ip和使用容器名都可以ping成功
[root@xizou ~]# docker exec -it tomcat01 ping tomcat02
PING tomcat02 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from tomcat02.mynet (192.168.0.3): icmp_seq=2 ttl=64 time=0.062 ms
64 bytes from tomcat02.mynet (192.168.0.3): icmp_seq=3 ttl=64 time=0.042 ms

自定义网络的docker都已经维护好了对应的关系,推荐平时使用这样的网络

好处:不同的集群使用不同的网络,保证集群是安全和健康的。

网络连通

网络连通

[root@xizou ~]# docker network connect --help

Usage: docker network connect [OPTIONS] NETWORK CONTAINER

Connect a container to a network

Options:
--alias strings Add network-scoped alias
for the container
--driver-opt strings driver options for the network
--ip string IPv4 address (e.g.,
172.30.100.104)
--ip6 string IPv6 address (e.g.,
2001:db8::33)
--link list Add link to another container
--link-local-ip strings Add a link-local address
for the container

让使用Docker0作为桥接的容器和使用自定义网络mynet作为桥接的容器连通。

假设:

使用Docker0作为桥接的容器有:tomcat01、tomcat02

使用自定义网络mynet作为桥接的容器有:tomcat-net-01、tomcat-net-02

[root@xizou /]# docker network connect mynet tomcat01
[root@xizou /]# docker network inspect mynet
可以发现Containes字段里多了tomcat01
[root@xizou /]# docker exec -it tomcat01 ping tomcat-net-01

实战:Redis集群

Redis集群

# 创建网卡
docker network create redis --subnet 172.38.0.0/16

# 通过脚本创建6个redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

# 启动docker
docker run -p 6371:6379 -p 16371:16739 --name redis-1 -v /mydate/redis/node-1/data:/data -v /mydate/redis/node-1/conf/redis.conf:/etc/redis/redis.conf -d --net redis --ip 172.38.0.11 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf

docker run -p 6372:6379 -p 16372:16739 --name redis-2 -v /mydate/redis/node-2/data:/data -v /mydate/redis/node-2/conf/redis.conf:/etc/redis/redis.conf -d --net redis --ip 172.38.0.12 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf

docker run -p 6373:6379 -p 16373:16739 --name redis-3 -v /mydate/redis/node-3/data:/data -v /mydate/redis/node-3/conf/redis.conf:/etc/redis/redis.conf -d --net redis --ip 172.38.0.13 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf

docker run -p 6374:6379 -p 16374:16739 --name redis-4 -v /mydate/redis/node-4/data:/data -v /mydate/redis/node-4/conf/redis.conf:/etc/redis/redis.conf -d --net redis --ip 172.38.0.14 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf

docker run -p 6375:6379 -p 16375:16739 --name redis-5 -v /mydate/redis/node-5/data:/data -v /mydate/redis/node-5/conf/redis.conf:/etc/redis/redis.conf -d --net redis --ip 172.38.0.15 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf

docker run -p 6376:6379 -p 16376:16739 --name redis-6 -v /mydate/redis/node-6data:/data -v /mydate/redis/node-6/conf/redis.conf:/etc/redis/redis.conf -d --net redis --ip 172.38.0.16 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf

# 进入任一个容器内
docker exec -it redis-1 /bin/sh

# 创建集群
/data # redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1
/data # redis-cli -c
127.0.0.1:6379> cluster info
观察发现已经创建了6个redis服务,其中3个主机,3个从机,主机的数据会同步到对应的从机,主机挂掉后,从机会替代挂掉的主机工作

实战:SpringBoot微服务打包成Docker镜像

步骤:

1、构建SpringBoot项目

2、打包应用

3、编写Dockerfile

FROM java:8

MAINTAINER xizou<xiongbinzou@163.com>

COPY *.jar /app.jar

CMD ["--server.port=8080"]

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "/app.jar"]

4、构建镜像

docker build -t helloworld .
docker images
docker run -d -P --name helloworld-web helloword
curl localhost:32779

5、发布运行

扩展

什么是容器编排

问题:将应用程序打包到了Docker容器中后,如何在生产环境中运行它,如果应用程序有依赖于其他容器,如数据库、消息服务或其他后端服务容器,该怎么办?如果用户数量增加并且你需要扩展你的应用程序怎么办?如果要缩小规模减少负载该怎么做?

思考:需要有一个具有一组资源和功能的底层平台,这个平台能够统筹容器之间的连接,并且根据负载可自动扩展或缩减容器。

方案:这种自动部署和管理容器的整个过程称为容器编排,Docker有自己的容器编排工具,叫做Docker Swarm,但是缺少复杂应用程序所需的一些高级功能,MESOS也是一种容器编排技术,虽然支持许多高级功能,但是很难设置和上手。Kubernets是一种容器编排技术,设置和入门有点困难,提供了许多自定义部署的选项和支持复杂架构的部署,目前所有的公有云服务供应商都支持了Kubernetes。

容器编排的优势:

  • 应用程序高可用。硬件故障不会导致应用程序停机,因为容器编排技术使我们在不同节点上运行了应用程序的多个实例。
  • 用户流量在容器间负载均衡
  • 需求增加时,可轻松、快速部署更多应用程序实例

容器编排

Kubernetes是一种容器编排技术,用于编排数百个应用程序的部署和管理。

Kubernetes架构

在设置Kubernetes集群前,先介绍一些Kubernetes的术语。

节点Nodes:节点是安装了Kubernetes的物理或虚拟机器,节点是工作机器,是Kubernetes将容器启动的地方。过去也叫Minions。

节点Node

集群Cluster:集群是一组组合在一起的节点,即使一个节点发生故障,依然可以从其他节点访问你的应用程序,此外节点也有助于分担负载,

集群Cluster

Master:Master是另一个安装了Kubernetes的节点,被配置为Master,负责监视集群中的节点并负责工作节点上容器的编排,存储着集群成员的信息。当一个节点发生故障时,Master负责将节点的工作负载转移到另一个工作节点。

Master

Kubernetes:在系统上安装Kubernetes时,实际上是在安装以下组件

  • API Server:充当Kubernetes的前端,包括用户、管理设备、命令行界面、从API服务器到Kubernets集群。
  • etcd:是Kubernetes使用可靠键值来存储用于管理集群的所有数据,当集群中有多个节点和多个Master时,etcd会以分布式的方式在集群的所有节点上存储着所有这些信息。etcd负责在集群内实现锁,以确保Master之间不存在冲突。
  • Scheduler:负责跨多个节点分发工作或容器,它查找新创建的容器并将它们分配给节点。
  • Controller:是编排背后的大脑,它负责在节点、容器或端点出现故障时进行统通知和响应,负责决定在这种情况下启动新容器。
  • Container Runtime:是运行容器的底层软件,如Docker。
  • Kubelet:是在集群中每个节点上运行的代理,负责保证容器按预期在节点上运行。

Pod:Kubernetes不直接在工作节点上部署容器,容器封装进而成为Pod的Kubernetes对象,Pod是应用程序的单个实例,Pod是你可以在Kubernetes创建的最小对象。

POD

Kubernetes容器编排

Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。

Pod(就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个)容器; 这些容器共享存储、网络、以及怎样运行这些容器的声明。 Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。

POD定义文件:

appVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
type: front-end
spec:
containers:
- name: nigix-container
image: nginx

# kubectl create -f pod-definition.yml
# kubectl get pods

Replica Set:ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。 因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。ReplicaSet 是通过一组字段来定义的,包括一个用来识别可获得的 Pod 的集合的选择算符、一个用来标明应该维护的副本个数的数值、一个用来指定应该创建新 Pod 以满足副本个数条件时要使用的 Pod 模板等等。 每个 ReplicaSet 都通过根据需要创建和删除 Pod 以使得副本个数达到期望值, 进而实现其存在价值。当 ReplicaSet 需要创建新的 Pod 时,会使用所提供的 Pod 模板。

ReplicaSet 定义文件:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp-replicaset
labels:
app: myapp
type: front-end
spec:
template:
metadata:
name: myapp-pod
labels:
app: myapp
type: front-end
spec:
containers:
- name: nginx-container
image: nginx
replicas: 3
selector:
matchLabels:
type: front-end

# kubectl create -f replicaset-definition.yml
# kubectl get replicaset
# kubectl get pods

Deployment:Deployment 是一个更高级的概念,它管理 ReplicaSet,并向 Pod 提供声明式的更新以及许多其他有用的功能。 因此,我们建议使用 Deployment 而不是直接使用 ReplicaSet, 除非你需要自定义更新业务流程或根本不需要更新。

你负责描述 Deployment 中的目标状态,而 Deployment控制器(Controller)以受控速率更改实际状态, 使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment, 并通过新的 Deployment 更新其资源。

应用:

1.当你有一个需要部署在生产环境中的Web服务器,你需要运行的不是一个而是多个Web服务器实例

2.每当应用程序需要更新版本时,你需要无缝升级Docker实例,但是当你升级实例时,不希望一次升级所有实例,因为会影响正在访问你应用程序的用户,因此你希望一个接一个升级他们,这种升级称为滚动更新。

3.当你执行的升级之一导致了意外错误,并且你被要求撤销最近的更改,你希望能够回滚最近执行的更改。

4.当对环境进行多项更改,例如升级底层Web服务器版本以及扩展你的环境并修改资源等,你不想在命令运行后立即应用每个更改,而是希望暂停应用进行更改,然后恢复,以便一起推出所有更改。

Deployment定义文件:

apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
labels:
app: myapp
type: front-end
spec:
template:
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: nginx-container
image: nginx

replicas: 3
selector:
matchLabels:
type: front-end

# kubectl create -f deployment-definition.yml
# kubectl get deployments
# kubectl get replicaset
# kubectl get pods
# kubectl get all

POD、Replica Set、Deployment关系

POD、Replica Set、Deployment关系

工作节点拥有Container Runtime、Kubelet

Master节点拥有API Server、etcd、Controller、Scheduler

Master VS Worker Nodes

Kubernetes网络设置

IP地址指定给POD:

IP地址指定给POD

Kubernetes网络需求:

  • Pod 能够与所有其他节点上的 Pod 通信, 且不需要网络地址转译(NAT)
  • 节点上的代理(比如:系统守护进程、kubelet)可以和节点上的所有 Pod 通信
Kubernetes网络模型解决方案之一

Kubernetes服务类型

  • NodePort Server
NodePort Server NodePort Server-多个Pods NodePort Server-多个Nodes
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: NodePort
ports:
- targetPort: 80
port: 80
nodePort: 30008
selector:
app: myapp
type: front-end

# kubectl create -f service-definition.yml
# kubectl get services

外部执行指令http://192.168.1.2:30008即可以访问节点里POD应用

  • ClusterIP Server
ClusterIP Server

为每种应用服务创建一个单独的Service,这个Service可定义提供相同应当服务节点的网络地址、端口配置。

apiVersion: v1
kind: Service
metadata:
name: back-end
spec:
type: ClusterIP
ports:
- targetPort: 80
port: 80
selector:
app: myapp
type: back-end

# kubectl create -f service-definition.yml
# kubectl get services
  • LoadBalancer Server

LoadBalancer Server

apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: LoadBalancer
ports:
- targetPort: 80
port: 80
nodePort: 30008

# kubectl create -f service-definition.yml
# kubectl search sevices

kubectl工具

kubectl工具在Kubernetes集群上部署和管理应用程序,获取集群信息,获取集群中其他节点的状态并管理许多其他事情。

kubectl run hello-minikube  # 在集群上部署应用程序
kubectl cluster-info # 查看集群信息
kubectl get nodes # 用于列出集群的所有节点部分

可以通过Minicube项目体验kubernetes的部署与应用。

实战:Kubernetes部署投票应用

投票应用

解释:

  • voting-app: 前端页面,用户通过该页面进行投票,python

  • redis:数据库,用户的投票数据暂存在内存的redis里,redis

  • worker:从redis里读取用户投票数据,统计计算后存入持久性数据库,.Net

  • postgress:数据库,持久化存储用户的投票数据跟统计结果,postgress

  • result-app: 前端页面,显示投票统计数据,Javascript

部署的目标

1、运行容器

2、容器间的连通

3、外部访问

解决方案1(不使用Deployment,不建议):

投票应用解决方案1

voting-app-pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: voting-app-pod
labels:
name: voting-app-pod
app: demo-voting-app
spec:
containers:
- name: voting-app
image: kodekcloud/examplevotingapp_vote:v1
ports:
- containerPort: 80

result-app-pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: result-app-pod
labels:
name: result-app-pod
app: demo-voting-app
spec:
containers:
- name: result-app
image: kodekcloud/examplevotingapp_result:v1
ports:
- containerPort: 80

redis-pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: redis-pod
labels:
name: redis-pod
app: demo-voting-app
spec:
containers:
- name: redis
image: redis
ports:
- containerPort: 6379

postgres-pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: postgres-pod
labels:
name: postgres-pod
app: demo-voting-app
spec:
containers:
- name: postgres
image: postgres
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: "postgres"
- name: POSTGRES_PASSWORD
value: "postgres"

worker-app-pod.yaml

apiVersion: v1
kind: Pod
metadata:
name: worker-app-pod
labels:
name: worker-app-pod
app: demo-voting-app
spec:
containers:
- name: worker-app
image: kodekcloud/examplevotingapp_worker:v1

redis-service.yaml

apiVersion: v1
kind: Service
metadata:
name: redis
labels:
name: redis-service
app: demo-voting-app
spec:
ports:
- port: 6379
targetPort: 6379
selector:
name: redis-pod
app: demo-voting-app

postgres-service.yaml

apiVersion: v1
kind: Service
metadata:
name: db
labels:
name: postgres-service
app: demo-voting-app
spec:
ports:
- port: 5432
targetPort: 5432
selector:
name: postgres-pod
app: demo-voting-app

voting-app-service.yaml

apiVersion: v1
kind: Service
metadata:
name: voting-service
labels:
name: voting-service
app: demo-voting-app
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30004
selector:
name: voting-app-pod
app: demo-voting-app

result-app-service.yaml

apiVersion: v1
kind: Service
metadata:
name: result-service
labels:
name: result-service
app: demo-voting-app
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30005
selector:
name: result-app-pod
app: demo-voting-app

将上面所有文件放入voting-app文件夹下

cd voting-app
kubectl get pods,svc
kuberctl create -f voting-app-pod.yaml
kuberctl create -f voting-app-service.yaml
# 查看投票服务的url
minikube service voting-service --url
kuberctl create -f redis-pod.yaml
kuberctl create -f redis-service.yaml
kubectl get pods,svc
kuberctl create -f postgres-pod.yaml
kuberctl create -f postgres-service.yaml
kubectl get pods,svc
kuberctl create -f worker-app-pod.yaml
kubectl get pods,svc
kuberctl create -f result-app-pod.yaml
kuberctl create -f result-app-service.yaml
kubectl get pods,svc
# 查看投票结果服务的url
minikube service voting-service --url

解决方案2(使用Deployment)

投票应用解决方案2

voting-app-deployment.yaml

apiVersion: v1
kind: Deployment
metadata:
name: voting-app-deployment
labels:
name: voting-app-deployment
app: demo-voting-app
spec:
replicas: 1
template:
metadata:
name: voting-app-pod
labels:
name: voting-app-pod
app: demo-voting-app
spec:
containers:
- name: voting-app
image: kodekcloud/examplevotingapp_vote:v1
ports:
- containerPort: 80
selector:
matchLabels:
name: voting-app-pod
app: demo-voting-app

redis-deployment.yaml

apiVersion: v1
kind: Deployment
metadata:
name: redis-deployment
labels:
name: redis-deployment
app: demo-voting-app
spec:
replicas: 1
template:
metadata:
name: postgres-pod
labels:
name: postgres-pod
app: demo-voting-app
spec:
containers:
- name: postgres
image: postgres
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: "postgres"
- name: POSTGRES_PASSWORD
value: "postgres"
selector:
matchLabels:
name: redis-pod
app: demo-voting-app

postgress-deployment.yaml

apiVersion: v1
kind: Deployment
metadata:
name: postgres-deployment
labels:
name: postgres-deployment
app: demo-voting-app
spec:
replicas: 1
template:
metadata:
name: redis-pod
labels:
name: redis-pod
app: demo-voting-app
spec:
containers:
- name: redis
image: redis
ports:
- containerPort: 6379
selector:
matchLabels:
name: postgres-pod
app: demo-voting-app

worker-deployment.yaml

apiVersion: v1
kind: Deployment
metadata:
name: worker-app-deployment
labels:
name: worker-app-deployment
app: demo-voting-app
spec:
replicas: 1
template:
metadata:
name: worker-app-pod
labels:
name: worker-app-pod
app: demo-voting-app
spec:
containers:
- name: worker-app
image: kodekcloud/examplevotingapp_worker:v1
selector:
matchLabels:
name: worker-app-pod
app: demo-voting-app

result-app-deployment.yaml

apiVersion: v1
kind: Deployment
metadata:
name: result-app-deployment
labels:
name: result-app-deployment
app: demo-voting-app
spec:
replicas: 1
template:
metadata:
name: voting-app-pod
labels:
name: voting-app-pod
app: demo-voting-app
spec:
containers:
- name: voting-app
image: kodekcloud/examplevotingapp_result:v1
ports:
- containerPort: 80
selector:
matchLabels:
name: result-app-pod
app: demo-voting-app

确保所有文件放入voting-app文件夹下,包括方案1的文件

cd voting-app
kubectl get pods,svc
kuberctl create -f voting-app-deployment.yaml
kuberctl create -f voting-app-service.yaml
# 查看投票服务的url
minikube service voting-service --url
kuberctl create -f redis-deployment.yaml
kuberctl create -f redis-service.yaml
kubectl get deployments
kuberctl create -f postgres-deployment.yaml
kuberctl create -f postgres-service.yaml
kubectl get deployments
kuberctl create -f worker-app-deployment.yaml
kubectl get deployments
kuberctl create -f result-app-deployment.yaml
kuberctl create -f result-app-service.yaml
kubectl get deployments,svc
# 查看投票结果服务的url
minikube service voting-service --url
# 修改投票和结果服务的集群为3
kubectl scale deployment voting-app-deployment --replicas=3
kubectl scale deployment result-app-deployment --replicas=3

为什么Podman比Docker更安全

Docker必须由root用户启动一个守护进程才能使用systemctl start docker,其次,普通用户需要加入容器组,才能够运行和启动Docker,如此会存在一些安全问题,如普通用户可以通过容器进行提权。

# 查看docker版本
[root@xizou /]# docker --version
Docker version 20.10.21, build baeda1f

# root用户启动docker服务进程
[root@xizou /]# systemctl start docker

# 查看docker服务状态
[root@xizou /]# systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
Active: active (running) since Wed 2022-11-02 12:24:26 CST; 47s ago
Docs: https://docs.docker.com
Main PID: 31905 (dockerd)
Tasks: 7
Memory: 27.9M
CGroup: /system.slice/docker.service
└─31905 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Nov 02 12:24:26 xizou dockerd[31905]: time="2022-11-02T12:24:26.199751948+08:00" level=info m...ing"
Nov 02 12:24:26 xizou dockerd[31905]: time="2022-11-02T12:24:26.431287299+08:00" level=info m...ing"
Nov 02 12:24:26 xizou dockerd[31905]: time="2022-11-02T12:24:26.475634500+08:00" level=info m...ing"
Nov 02 12:24:26 xizou dockerd[31905]: time="2022-11-02T12:24:26.676136067+08:00" level=info m...ess"
Nov 02 12:24:26 xizou dockerd[31905]: time="2022-11-02T12:24:26.778914793+08:00" level=info m...ing"
Nov 02 12:24:26 xizou dockerd[31905]: time="2022-11-02T12:24:26.891111549+08:00" level=info m...ne."
Nov 02 12:24:26 xizou dockerd[31905]: time="2022-11-02T12:24:26.907790255+08:00" level=info m...0.21
Nov 02 12:24:26 xizou dockerd[31905]: time="2022-11-02T12:24:26.907847502+08:00" level=info m...ion"
Nov 02 12:24:26 xizou systemd[1]: Started Docker Application Container Engine.
Nov 02 12:24:26 xizou dockerd[31905]: time="2022-11-02T12:24:26.924507836+08:00" level=info m...ock"
Hint: Some lines were ellipsized, use -l to show in full.

# 查看当前所有镜像
[root@xizou /]# docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE

# 下载镜像
[root@xizou /]# docker pull centos
Using default tag: latest
latest: Pulling from library/centos
a1d0c7532777: Pull complete
Digest: sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177
Status: Downloaded newer image for centos:latest
docker.io/library/centos:latest
[root@xizou /]# docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 5d0da3dc9764 13 months ago 231MB

# 创建并切换用户
[root@xizou /]# adduser test -G docker
[root@xizou /]# su - test

# 普通用户能看到root用户下载的镜像,如果使用podman则不会显示
[test@xizou ~]$ docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 5d0da3dc9764 13 months ago 231MB

# 普通用户运行centos应用的容器,挂载根目录到容器内的/host目录,并把容器内的/host目录作为容器的根目录
[test@xizou ~]$ docker run -it --privileged -v /:/host centos chroot /host
sh-4.2#
# 容器内能够执行命令获取原本普通用户没有权限的内容
sh-4.2# head -1 /etc/shadow
root:$6$8Ufh0Fb6$tpjmUtcq0r36xLc.q9tDb.5/ECL5cS6iOmglyETI23.C0BbGO0uHLtS7c97O6YvKV.CBQvufaP7mHJ25P.J6X/:19293:0:99999:7:::
sh-4.2# exit
exit
[test@xizou ~]$ head -1 /etc/shadow
head: cannot open ‘/etc/shadow’ for reading: Permission denied
[test@xizou ~]$ exit
logout
[root@xizou /]# head -1 /etc/shadow
root:$6$8Ufh0Fb6$tpjmUtcq0r36xLc.q9tDb.5/ECL5cS6iOmglyETI23.C0BbGO0uHLtS7c97O6YvKV.CBQvufaP7mHJ25P.J6X/:19293:0:99999:7:::

上面例子举证了Docker造成的安全隐患,通过挂载数据卷的方式,普通用户能够在容器内获得root权限,能够随意更改文件。另外,容器内创建的用户,退出容器后居然存在于宿主机中。容器的隔离性被破坏。

# 解决方案:
方案1. 使用podman替代docker
alias docker=podman
方案2. 限制只有root用户才能使用docker
将docker.sock文件的属组改成root组
[root@xizou /]# cd /var/run/
[root@xizou run]# chown root:root docker.sock

后续学习方向

  • IDEA整合Docker
  • Docker Compose、yaml
  • Docker Swarm、Kubernetes
  • CI/CD Jenkins

参考文章

本文是笔者通过下列视频教程和文档进行Docker进阶学习的记录,有部分修改和补充,转载请注明出处,并附带下面链接。

1.【B站up主-遇见狂神说】

2.【kubernetes官网】

3.【Udemy学院-Kubernetes初学者教程视频】