用的h2数据库,方便固化迁移,写太多就很卡,至少要分三个部分,此为第一部分。

docker加jenkins能省去很大程度上后端做测试的时间,在深入了解docker的时候发现docker不止一种FS,docker的镜像很类似jvm的类模板,docker的网络也十分有趣,大型分布式架构通过docker和k8s容器编排就能自动化部署,自动化压测,自动化监控等等,而这些各有特点的应用在以前我从不知道他们一直有着统一的归属叫云原生。
image.png

1、云原生的前世今生

2、docker的基本操作

首先了解下docker的架构,其实和github差不多:
image20210804154855978.png
Client是指我们操作的命令行界面,DOCKER_HOST是docker主机的内容,包括了后台进程daemon和容器Containers及镜像Images,还有个Registry相当于docker仓库,可以提供各种容器化组件包括但不限于nginx和redis等,个人也可上传自己制作的镜像到自己空间。

我们Client的命令都会交到对应的daemon进程然后由守护进程去调用docker的CRI(Container Runtime Interface)进行相应的处理。

还需要了解一下Docker是使用Go语音编写的,从镜像imgaes实例化容器后,同镜像的容器只会存在差异化配置文件,并不会占用多余内存,还有Docker复用了Linux的安全隔离机制namespace划分隔离级别,这种机制更契合Linux操作系统。
docker相比VMware**

docker更轻量化,不需要划分虚拟资源直接运行在系统内核之上,不需要独立的操作系统,资源利用率高,沙箱安全虽然比不上虚拟机的绝对隔离。

docker的安全机制

docker虽然没有虚拟机的绝对隔离,但是也采用了相当完善的安全策略,容器之间基于Linux的namespace进行资源隔离,通过cgroup等进行资源限制。

不同的namespace隔离级别和粒度不同,比如UTS namespace隔离主机和域名,PID namespace会隔离到进程。而cgroups则是对资源的管控,使得容器在配额的资源下运行,对于配额的资源来说也有优先级别的划分,管控的范围不仅限于监控还包括任务调度等。

namespace:

namespace系统调用参数隔离内容
UTSCLONE_NEWUTS主机和域名
IPCCLONE_NEWIPC信号量、消息队列和共享内存
PIDCLONE_NEWPID进程编号
NetworkCLONE_NEWNET网络设备、网络栈、端口等
MountCLONE_NEWNS挂载点(文件系统)
UserCLONE_NEWUSER用户和用户组

cgroups:

子系统功能
cpu使用调度程序控制任务对CPU的使用。
cpuacct(CPU Accounting)自动生成cgroup中任务对CPU资源使用情况的报告。
cpuset为cgroup中的任务分配独立的CPU(多处理器系统时)和内存。
devices开启或关闭cgroup中任务对设备的访问
freezer挂起或恢复cgroup中的任务
memory设定cgroup中任务对内存使用量的限定,并生成这些任务对内存资源使用 情况的报告
perf_event(Linux CPU性能探测器)使cgroup中的任务可以进行统一的性能测试
net_cls(Docker未使用)通过等级识别符标记网络数据包,从而允许Linux流量监控程序(Traic Controller)识别从具体cgroup中生成的数据包

cgroup提供的主要功能总结如下:

资源限制:限制任务使用的资源总额,并在超过这个 配额 时发出提示

优先级分配:分配CPU时间片数量及磁盘IO带宽大小、控制任务运行的优先级

资源统计:统计系统资源使用量,如CPU使用时长、内存用量等

任务控制:对任务执行挂起、恢复等操作

docker安装

都是抄的文档的:

还是做个简单记录:

1、先移除linux上可能存在的旧版本:

sudo yum remove docker*

2、配置docker的yum源

sudo yum install -y yum-utils
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/dockerce/linux/centos/docker-ce.repo
#此处改成了ali的yum源

3、安装最新的docker engine

sudo yum install docker-ce docker-ce-cli containerd.io

这里可以选择安装指定版本的docker engine

#查看可用的docker版本列表
yum list docker-ce --showduplicates | sort -r

#安装指定版本,用以上命令列举的某个版本号替换<VERSION_STRING>
sudo yum install docker-ce-<VERSION_STRING>.x86_64 docker-ce-cli-<VERSION_STRING>.x86_64 containerd.io

离线安装自行查找。

4、启动服务

systemctl start docker
systemctl enable docker

5、镜像加速

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://82m9ar63.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

阿里云镜像会增速国内网络,上面的daemon就是开头讲的守护进程,自然daemon.json是docker的核心配置文件。

6、可视化界面

和github一样,docker也有很多可视化界面,自己选取即可,IDEA选择docker插件一样可以远程管理docker容器。

可以选择安装Portainer:

官方文档:https://documentation.portainer.io/

简单来讲就是受众大,功能强大,受众永远是第一位的。

安装也十分简单:

# 服务端部署
docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v
/var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data
portainer/portainer-ce
# 访问 9000 端口即可
#agent端部署
docker run -d -p 9001:9001 --name portainer_agent --restart=always -v
/var/run/docker.sock:/var/run/docker.sock -v
/var/lib/docker/volumes:/var/lib/docker/volumes portainer/agent

至于 -p -d 之类的是什么意思,后面会详细说明。

docker 常用命令

操作逻辑和关系都在这张图中:
image20210805153321551.png
根据这张图从上到下捋一遍,首先容器可以commit为镜像,这跟qq上分享应用一样即使没有app安装包依然能打包成app分享。image镜像自然通过create的创建出容器,创建出容器后可以start启动也可以从镜像使用docker run一步启动运行,对于运行中的容器可以使用pause和unpause暂停或开启也可以直接stop停止或者kill杀掉容器进程。通过cp命令可以将容器内的文件复制到容器之外。diff可以查看容器文件结构到目前为止的更改。容器还可以通过export命令导出为tar,tar还可以通过import命令创建一个镜像,同样image可以save成tar,也可从tar load成镜像。可以根据Dockerfile build构建成image。image可以pull到registry,也可以push下来,registry需要login。

详细命令肯定还是官网文档全:

https://docs.docker.com/engine/reference/commandline/docker/

我这先列个表解释大概意思然后标注重点,对相应操作可能产生的问题也做详细解读。

所有的命令都可以加上 --help做辅助参考可选参数

命令作用
attach绑定到运行中容器的 标准输入, 输出,以及错误流(这样似乎也能进入容器内容,但 是一定小心,他们操作的就是控制台,控制台的退出命令会生效,比如 redis,nginx...)
build从一个 Dockerfile 文件构建镜像
commit把容器的改变 提交创建一个新的镜像
cp容器和本地文件系统间 复制 文件/文件夹
create创建新容器,但并不启动(注意与docker run 的区分)需要手动启动。start\stop
diff检查容器里文件系统结构的更改【A:添加文件或目录 D:文件或者目录删除 C:文 件或者目录更改】
events获取服务器的实时事件
exec在运行时的容器内运行命令
export导出容器的文件系统为一个tar文件。commit是直接提交成镜像,export是导出成文 件方便传输
history显示镜像的历史
images列出所有镜像
import导入tar的内容创建一个镜像,再导入进来的镜像直接启动不了容器。 /docker-entrypoint.sh nginx -g 'daemon o;' docker ps --no-trunc 看下之前的完整启动命令再用他
info显示系统信息
inspect获取docker对象的底层信息
kill杀死一个或者多个容器
load从 tar 文件加载镜像
login登录Docker registry
logout退出Docker registry
logs获取容器日志;容器以前在前台控制台能输出的所有内容,都可以看到
pause暂停一个或者多个容器
port列出容器的端口映射
ps列出所有容器
pull从registry下载一个image 或者repository
push给registry推送一个image或者repository
rename重命名一个容器
restart重启一个或者多个容器
rm移除一个或者多个容器
rmi移除一个或者多个镜像
run创建并启动容器
save把一个或者多个镜像保存为tar文件
search去docker hub寻找镜像
start启动一个或者多个容器
stats显示容器资源的实时使用状态
stop停止一个或者多个容器
tag给源镜像创建一个新的标签,变成新的镜像
top显示正在运行容器的进程
unpausepause的反操作
update更新一个或者多个docker容器配置
versionShow the Docker version information
container管理容器
image管理镜像
network管理网络
volume管理卷

docker 命令解读

我们所有的镜像初始来源都是docker仓库:

https://registry.hub.docker.com/

我们配置的阿里加速不过是阿里做了一个缓存,阿里拉取的仍然是docker-registry里的镜像。

开始之前要有个基本的认知,就是docker镜像和我们平时所说的软件不一样,docker内其实打包了整个linux操作系统,只不过这个linux是裁剪过的alpine版本,只有不到6mb,docker是基于linux分层搭建起来的,可能电脑上对应的软件很小但是docker镜像就很大也可能大大相反。

所以要构建一个docker镜像,首先就要选择基底依赖比如linux或者jdk,而做一个轻量高级的docker镜像要优先选用裁剪过的版本,比如alpine或者slim版本。

docker commit

当运行后的容器经过长期修改完善后,通过commit打成最终镜像保存到我们的docker hub中。

docker commit -a flitsneak -m "final version"  myredis myredis:v8

docker create

docker create [OPTIONS] IMAGE [COMMAND] [ARG...] 

docker create [设置项] 镜像名 [启动] [启动参数...]

比如

docker create --name myredis -p 6379(主机的端口):6379(容器的端口) redis

其中 -p port1:port2 port1是必须唯一的对于主机的端口

然后可以docker start运行该容器

也可直接docker run

docker run

docker run --name myredis2 -p 6379:6379 -p 6666:6379 redis 

默认前台启动,加-d 让他后台悄悄启动, 这里演示的是虚拟机的多端口绑定容器的一个端口是可以的。

所以 docker run -d == docker create + docker start

docker run 是最长用的命令,docker run --help 会看到许多OPTIONS

-d: 后台运行容器,并返回容器ID

-i: 以交互模式运行容器,通常与 -t 同时使用

-P: 随机端口映射,容器内部端口随机映射到主机的端口

-p:指定端口映射,格式为:主机(宿主)端口:容器端口

-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用

--name="nginx-lb":为容器指定一个名称

--dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致

--dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致

-h "mars": 指定容器的hostname

-e username="ritchie": 设置环境变量

--env-file=[]: 从指定文件读入环境变量;

--cpuset="0-2" or --cpuset="0,1,2": 绑定容器到指定CPU运行

-m :设置容器使用内存最大值

--net="bridge": 指定容器的网络连接类型,支持bridge/host/none/container: 四种类型

--link=[]: 添加链接到另一个容器

--expose=[]: 开放一个端口或一组端口

--restart , 指定重启策略,可以写--restart=awlays 总是故障重启

--volume , -v: 绑定一个卷。一般格式 主机文件或文件夹:虚拟机文件或文件夹

其中 volume数据卷在文件系统中详解

net 会在docker网络中展开

**docker stop && docker kill **

docker kill 就是我们常用的 kill -9 pid一个意思相当于直接拔掉电脑电源。

docker stop则是优雅停机,会等待运行中的程序处理完所有的事情。

docker rm && docker prune

docker rmi -f ${docker images -aq}

删除全部镜像

docker image prune

删除游离镜像,即没有镜像名字的镜像

docker logs

查看日志 docker logs -f 容器名 可以追踪日志。

docker tag

就是改镜像名 docker tag 原镜像:标签 新镜像名:标签

docker attach && docker exec

这俩命令都能进入容器进行操作,但是docker attach绑定的是控制台,ctrl+c也会导致容器停止,所以进入容器操作用docker exec。

docker exec -it -u 0:0 --privileged myredis /bin/bash

-i是交互模式 t是分配新的终端,u是用户这里是0用户0组(root),以特权方式进入容器控制台,/bin/bash是进控制台命令,注意并不是所有的容器进入后台的命令都是bash,还有很多是sh或别的

docker inspect

inspect是查看容器的详细信息的,这个有很多东西关联到docker 的文件结构后面会深入解释。

docker container inspect 容器名 = docker inspect 容器名

docker inspect 可以跟镜像 网络 数据卷等等。

docker export && docker import

docker export导出的jar文件方便传输,然后import导入jar后变成镜像,但是并不能直接启动容器,需要知道之前的启动命令 (docker ps --no-trunc)会看到完整的COMMAND(类似/docker-entrypoint.sh nginx -g 'daemon off;'),复制下来然后再用下面命令启动。

docker run -d -P mynginx:v6 /docker-entrypoint.sh nginx -g 'daemon off;'

或者docker image inspect 看之前的镜像,把 之前镜像的 Entrypoint数组里的所有和 Cmd数组里面的连接起来就 能得到启动命令 同似上面的 /docker-entrypoint.sh nginx -g 'daemon off;'

这部分要深入理解的话需要看完下面docker文件系统部分。

docker cp

docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|- :把容器里面的复制出来

docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH:把外部的复制进去

SRC_PATH 指定为一个文件

​ DEST_PATH 不存在:文件名为 DEST_PATH ,内容为SRC的内容

​ DEST_PATH 不存在并且以 / 结尾:报错

​ DEST_PATH 存在并且是文件:目标文件内容被替换为SRC_PATH的文件内容。

​ DEST_PATH 存在并且是目录:文件复制到目录内,文件名为SRC_PATH指定的名字 SRC_PATH 指定为一个目录

​ DEST_PATH 不存在: DEST_PATH 创建文件夹,复制源文件夹内的所有内容 DEST_PATH 存在是文件:报错

​ DEST_PATH 存在是目录

​ SRC_PATH 不以 /. 结束:源文件夹复制到目标里面

​ SRC_PATH 以 /. 结束:源文件夹里面的内容复制到目标里面 自动创建文件夹不会做递归。把父文件夹做好

docker cp index.html mynginx4:/usr/share/nginx/html

docker cp mynginx4:/etc/nginx/nginx.conf nginx.conf

docker diff

就是看你安装后改变了什么,ABCD的含义上面也说明过了
image20210805211701768.png

然后docker commit提交成新镜像

新镜像可推送到我们docker hub 仓库

docker push

推送镜像

​ 注册docker hub并登录

​ 创建一个仓库,选为public

​ docker push flitsneak/mynginx:tagname

​ docker hub一个完整镜像的全路径是 docker.io/library/redis:alpine3.13 我们的 docker.io/flitsneak/mynginx:tagname

​ docker images的时候镜像缩略了全名 默认官方镜像没有docker.io/library/ docker.io/ rediscommander / redis-commander:latest

​ docker.io/flitsneak/mynginx:v4 我的镜像的全称

​ 登录远程docker仓库

​ 当前会话登录以后 docker login 。所有的东西都会push到这个人的仓库

​ docker push flitsneak/mynginx:tagname

​ 上面命令的完整版 docker push docker.io/flitsneak/mynginx:v4

​ 怎么知道是否登录了 cat ~/.docker/config.json 有没有 auth的值,没有就是没有登录

也可以使用阿里云提供的镜像仓库,阿里云有完整的指令引导。

docker save && docker load

docker save -o busybox.tar busybox:latest

将busybox镜像保存成tar

docker load -i busybox.tar

把压缩包里面的内容直接导成镜像

docker 命令总结

做一个镜像可以有如下几步:

1、基于已经存在的容器commit提取成镜像

2、根据tar包import导入成镜像

3、Dockerfile来build成镜像

编写Dockerfile是重点,也是CI/CD的基础,后面会详解。

容器的状态有Created(新建)、Up(运行中)、Pause(暂停)、Exited(退出)。

docker 镜像启动一定要有个阻塞的进程,一直有事做,否则不能长久运行。

常用的docker部署方案

日常开发中会有些常用的docker部署,这里记录一下,用的时候直接复制就好。

事先要注意一下挂载的问题,在docker文件系统会详解挂载问题,我们下面的案例都是绝对路径的挂载,为避免空挂载需要创建对应的文件夹及文件,这样挂载的时候会将系统挂载文件同步到docker对应的挂载目录之中。

-v 是volume的意思,下面文件系统会详解挂载和绑定的区别,细心也会发现有的是绝对路径有的是相对路径,至于为什么要-v挂载,一是方便修改docker配置,二是如果遇到异常情况比如docker宕机数据丢失我们可以有备份还原。

1、Nginx

docker run --name nginx-app \
-v /app/nginx/html:/usr/share/nginx/html:ro \
-v /app/nginx/conf:/etc/nginx
-d nginx

要注意 /app/nginx/conf下的内容必须存在不然就是上面所说的空挂载,访问forbidden。

2、MySQL

# 5.7版本
docker run -p 3306:3306 --name mysql57-app \
-v /app/mysql/log:/var/log/mysql \
-v /app/mysql/data:/var/lib/mysql \
-v /app/mysql/conf:/etc/mysql/conf.d \
-e MYSQL_ROOT_PASSWORD=123456 \
-d mysql:5.7
#8.x版本,引入了 secure-file-priv 机制,磁盘挂载将没有权限读写data数据,所以需要将权限透传,或者chmod -R 777 /app/mysql/data
# --privileged 特权容器,容器内使用真正的root用户
docker run -p 3306:3306 --name mysql8-app \
-v /app/mysql/conf:/etc/mysql/conf.d \
-v /app/mysql/log:/var/log/mysql \
-v /app/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--privileged \
-d mysql

3、Redis

# 提前准备好redis.conf文件,创建好相应的文件夹。如:
port 6379
appendonly yes
#更多配置参照 https://raw.githubusercontent.com/redis/redis/6.0/redis.conf
docker run -p 6379:6379 --name redis \
-v /app/redis/redis.conf:/etc/redis/redis.conf \
-v /app/redis/data:/data \
-d redis:6.2.1-alpine3.13 \
redis-server /etc/redis/redis.conf --appendonly yes

4、ES

#准备文件和文件夹,并chmod -R 777 xxx
#配置文件内容,参照https://www.elastic.co/guide/en/elasticsearch/reference/7.5/node.name.html 搜索相关配置
docker run --name=elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms300m -Xmx300m" \
-v /app/es/data:/usr/share/elasticsearch/data \
-v /app/es/plugins:/usr/shrae/elasticsearch/plugins \
-v esconfig:/usr/share/elasticsearch/config \
-d elasticsearch:7.12.0

5、Tomcat

docker run --name tomcat-app -p 8080:8080 \
-v tomcatconf:/usr/local/tomcat/conf \
-v tomcatwebapp:/usr/local/tomcat/webapps \
-d tomcat:jdk8-openjdk-slim-buster

6、重启策略

no,默认策略,在容器退出时不重启容器

on-failure,在容器非正常退出时(退出状态非0),才会重启容器

on-failure:3,在容器非正常退出时重启容器,最多重启3次

always,在容器退出时总是重启容器

unless-stopped,在容器退出时总是重启容器,但是不考虑在Docker守护进程启动时就已经停止了的容器

3、docker的文件和网络系统

开头有讲过,docker有自己适用的文件系统,docker在底层使用自己的存储驱动,来组建文件内容,docker文件系统是基于AUFS(联合文件系统)下面的OverlayFS是其中一种。

直接从镜像和容器来说,下图就是docker默认的OverlayFS。
image20210808031112527.png
如果我们根据nginx镜像启动一个容器会占用30mb空间,那么启动四个呢?并不是120mb,只会多了一部分。

这就是上图含义的一部分。

使用

docker image inspect nginx

查看nginx镜像的详细信息可以看到数据部分分成了三层,如上图一致的“lowerdir”,"upperdir","merged"还有“WorkDir”
image20210808032121520.png
LowerDir**

从LowerDir开始,我们进入到该目录:

/var/lib/docker/overlay2/67b3802c6bdb5bcdbcccbbe7aed20faa7227d584ab37668a03ff6952e
631f7f2/diff:用户文件;
/var/lib/docker/overlay2/f56920fac9c356227079df41c8f4b056118c210bf4c50bd9bb077bdb4
c7524b4/diff: nginx的启动命令放在这里
/var/lib/docker/overlay2/0e569a134838b8c2040339c4fdb1f3868a7118dd7f4907b40468f5fe6
0f055e5/diff: nginx的配置文件在这里
/var/lib/docker/overlay2/2b51c82933078e19d78b74c248dec38164b90d80c1b42f0fdb1424953
207166e/diff: 小linux系统

可以看出这就是底层目录,包含了小型linux和其他基础软件。

事实上,我们进入的容器内的文件系统就是镜像的,真实查看文件大小可以用docker ps -s s是size的意思。

UpperDir

上层目录,上层是容器层,容器层能感知容器的变化,读取文件时如果目标文件不在容器层内则从镜像层读取文件,如果在容器层内直接从容器层读取,如果目标文件同时存在于容器层和镜像层则overlay读取容器层的文件。

修改文件时在容器层的文件直接修改,不在容器层的文件会从镜像层copy到容器层再进行修改。

删除文件时如果仅是容器层文件直接删除,如果是镜像层文件,overlay存储驱动会在容器层创建标记文件,用来隐藏镜像层中的目标文件。

上面所说的就是Docker的 Copy On Write写时复制的具体体现。

MergedDir

合并目录,容器最终的完整工作目录全部都在合并层,数据卷在容器层产生,所有的增删查改都在容器层。

WorkDir

工作目录(临时层),容器修改后的文件保存在宿主机的WorkDir,容器删除后容器目录不再存在。

总结

总结来说,Docker镜像由一系列层组成,其实每层都代表Dockerfile中的一条指令,除最后一层外的每一层都是只读的。

FROM ubuntu:15.04
COPY . /app
RUN make /app
CMD python /app/app.py
# 每一个指令都可能会引起镜像改变,这些改变类似git的方式逐层叠加。

该Dockerfile包含四个命令,每个命令创建一个层。

FROM语句从ubuntu:15.04映像创建一个图层开始。

COPY命令从Docker客户端的当前目录添加一些文件。

RUN命令使用make命令构建您的应用程序。

最后,最后一层指定要在容器中运行的命令。

每一层只是与上一层不同的一组。 这些层彼此堆叠。

创建新容器时,可以在基础层之上添加一个新的可写层。 该层通常称为“容器层”。 对运行中 的容器所做的所有更改(例如写入新文件,修改现有文件和删除文件)都将写入此容器层。

容器和镜像之间的主要区别是可写顶层。 在容器中添加新数据或修改现有数据的所有写操作都存储在此可写层中。 删除容器后,可写层也会被删除。 基础图像保持不变。 因为每个容器都有其自己的可写容 器层,并且所有更改都存储在该容器层中,所以多个容器可以共享对同一基础映像的访问, 但具有自己的数据状态。
image20210808154641054.png
锁头的意思是ReadOnly。

了解了docker存储原理,如何合理预估docker的磁盘容量?

docker ps -s

这里的size是用于每个容器的可写层的数据量,而virtual size是容器使用的用于只读图像数据的数据量加上容器的可写图层大小。 多个容器可以共享部分或全部只读图像数据。 从同一图像开始的两个容器共享100%的只读数据,而具有不同图像的两个容器(具有相同的层)共享这些公共 层。 因此,不能只对虚拟大小进行总计。这高估了总磁盘使用量,可能是一笔不小的数目。

容器挂载
image20210808155306633.png
如图所示有三种挂载方式:

1、bind mount 手动挂载,自己在外部创建文件夹手动挂载到docker目录。

2、volume 绑定,docker自动在外部创建文件夹自动挂载容器内部指定的文件夹内容。

3、tmpfs mount 挂载到内存中

后来mount命令就被volume取代了,如果是-v绝对路径就是mount相对路径是volume。

加深理解,详细说明这三个概念:
image20210808160005987.png
Bind mounts(绑定挂载) `:可以在任何地方 存储在主机系统上。 它们甚至可能是重要的系统文件或 目录。 Docker主机或Docker容器上的非Docker进程可以随时对其进行修改。

Volumes(卷):存储在主机文件系统的一部分中,该文件系统由Docker管理(在Linux上是“ / var / lib / docker / volumes /”)。 非Docker进程不应修改文件系统的这一部分。 卷是在Docker中持久存 储数据的最佳方法。

tmpfs mounts(临时挂载):仅存储在主机系统的内存中,并且永远不会写入主机系统的文件系统

挂载&&绑定

-v 宿主机绝对路径:Docker容器内部绝对路径:叫挂载;这个有空挂载问题,宿主机目录如果没有内容会对容器内目录进行覆盖,并不是常规意义的覆盖。

-v 不以/开头的路径:Docker容器内部绝对路径:叫绑定(docker会自动管理,docker不会把他当成目录,而把它当成卷),这是docker内部会先创建一个对于名称的目录,然后将容器内需要挂载的目录复制到其中,再以卷的方式挂载到宿主机。

以上两种一般生产环境用卷的方式,平时学习测试可以用-v 绝对路径的方式即-mount即手动挂载的方式。

如果将绑定安装或非空卷安装到存在某些文件或目录的容器中的目录中,则这些文件或目录会被安装遮盖,就像您将文件保存到Linux主机上的/ mnt中一样,然后 将USB驱动器安装到/ mnt中。 在卸载USB驱动器之前,/ mnt的内容将被USB驱动器的内容遮盖。 被遮盖的文件不会被删除或更改,但是在安装绑定安装或卷时将无法访问。

总结:外部目录覆盖内部容器目录内容,但不是修改。所以谨慎,外部空文件夹挂载方式也会导 致容器内部是空文件夹

警惕bind mount 方式,文件挂载没有在外部准备好内容而导致的容器启动失败问题

匿名卷&&具名卷

匿名卷:

docker run -dP -v /etc/nginx nginx
#docker将创建出匿名卷,并保存容器/etc/nginx下面的内容
# -v 宿主机:容器里的目录

具名卷:

docker run -dP -v nginx:/etc/nginx nginx
#docker将创建出名为nginx的卷,并保存容器/etc/nginx下面的内容

很容易看出区别,匿名卷会生成类似uuid作为目录名,具名卷就会使用你给的名字。

上面这两种都算volume绑定的方式。如果将空卷装入存在文件或目录的容器中的目录中,则容器中的内容(复制)到该卷中。 如果启动一个容器并指定一个尚不存在的卷,则会创建一个空卷。(再次说明,卷是宿主机上仅有docker进程管理的空间)

案例分析

nginx测试html挂载几种不同情况:

  1. 不挂载 效果:访问默认欢迎页

  2. -v /root/html:/usr/share/nginx/html 效果:访问forbidden

  3. -v html:/usr/share/nginx/html:ro 效果:访问默认欢迎页

  4. -v /usr/share/nginx/html 效果:匿名卷

    解析:

    -v 不以绝对路径

    1. 先在docker底层创建一个你指定名字的卷(具名卷) html
    2. 把这个卷和容器内部目录绑定
    3. 容器启动以后,目录里面的内容就在卷里面存着

    -v nginxhtml:/usr/share/nginx/html 也可以以下操作

    1. docker create volume nginxhtml 如果给卷里面就行修改,容器内部的也就改了

    2. docker volume inspect nginxhtml

    3. docker run -d -P -v nginxhtml:/usr/share/nginx/html -- name=nginx777 nginx

      # 可以看到
      "Mounts": [
      {
      "Type": "volume", //这是个卷
      "Name": "html", //名字是html
      "Source": "/var/lib/docker/volumes/html/_data", //宿主
      机的目录。容器里面的哪两个文件都在
      "Destination": "/usr/share/nginx/html", //容器内部
      "Driver": "local",
      "Mode": "z",
      "RW": true, //读写模式
      "Propagation": ""
      }
      ]
      

    应对挂载空挂载问题有两种角度解决,一是提前手动创建好目录文件,二是先将docker目录cp到宿主机再挂载。

    # 一行命令启动nginx,并且配置文件和html页面。需要知道卷的位置才能改
    docker run -d -P -v nginxconf:/etc/nginx/ -v nginxpage:/usr/share/nginx/html nginx
    # 想要实现 docker run -d -P -v /root/nginxconf:/etc/nginx/ -v
    /root/nginxhtml:/usr/share/nginx/html --name=nginx999 nginx
    ### 1、提前准备好东西 目录nginxconf,目录里面的配置we年都放里面,,再调用命令
    ### 2、docker cp nginxdemo:/etc/nginx /root/nginxconf #注意/的使用
    ### 3、docker run -d -P -v /root/nginxconf:/etc/nginx/ -v
    /root/nginxhtml:/usr/share/nginx/html --name=nginx999 nginx
    

    卷相关命令

    docker volume create xxx:创建卷名
    docker volume inspect xxx:查询卷详情
    docker volume ls: 列出所有卷
    docker volume prune: 移除无用卷
    

docker网络

端口映射

docker create -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name hello-mysql
mysql:5.7

这个相对基础,都明白啥意思。

容器互联

--link name:alias,name连接容器的名称,alias连接的别名。

当我们不想暴露mysql容器给外部时,采用此方式让内部web使用mysql。

docker run -d -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
docker run -d --link mysql01:mysql --name tomcat tomcat:7
docker exec -it tomcat bash
cat /etc/hosts
ping mysql

docker 自定义网络

Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据 Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。 因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
image20210808165534976.png
Docker容器网络就很好的利用了Linux虚拟网络技术,在本地主机和容器内分别创建一个虚拟接口,并让 他们彼此联通(这样一对接口叫veth pair); Docker中的网络接口默认都是虚拟的接口。虚拟接口的优势就是转发效率极高(因为Linux是在内核中进 行数据的复制来实现虚拟接口之间的数据转发,无需通过外部的网络设备交换),对于本地系统和容器 系统来说,虚拟接口跟一个正常的以太网卡相比并没有区别,只是他的速度快很多。

原理:

  1. 每一个安装了Docker的linux主机都有一个docker0的虚拟网卡。桥接网卡
  2. 每启动一个容器linux主机多了一个虚拟网卡。
  3. docker run -d -P --name tomcat --net bridge tomcat:8

网络模式

网络模式配置说明
bridge模式--net=bridge默认值,在Docker网桥docker0上为容器创建新的网络 栈
none模式--net=none不配置网络,用户可以稍后进入容器,自行配置
container模 式-- net=container:name/id容器和另外一个容器共享Network namespace。 kubernetes中的pod就是多个容器共享一个Network namespace
host模式--net=host容器和宿主机共享Network namespace;
用户自定义--net=mynet用户自己使用network相关命令定义网络, 创建容器的时候可以指定为自己定义的网络

实战自建网络

#1、docker0网络的特点。,
默认、域名访问不通、--link 域名通了,但是删了又不行
#2、可以让容器创建的时候使用自定义网络,用自定义
1、自定义创建的默认default "bridge"
2、自定义创建一个网络网络
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway
192.168.0.1 mynet
所有东西实时维护好,直接域名ping通
docker network connect [OPTIONS] NETWORK CONTAINER
#3、跨网络连接别人就用。把tomcat加入到mynet网络
docker network connect mynet tomcat
效果:
1、自定义网络,默认都可以用主机名访问通
2、跨网络连接别人就用 docker network connect mynet tomcat
#4、命令
1、容器启动,指定容器ip。 docker run --ip 192.168.0.3 --net 自定义网络
2、创建子网。docker network create --subnet 指定子网范围 --driver bridge 所有东西实时
维护好,直接域名ping通
3、docker compose 中的网络默认就是自定义网络方式。
docker run -d -P --network 自定义网络名(提前创建)

其实还是计算机网络那点东西。

4、dockerfile

Dockerfile是很重要的知识,我们构建web应用为docker镜像必须要写Dockerfile。

上面已经见过dockerf的格式了,就是一行行命令组成,注释是#,一般来说Dockerfile分为四部分组成:

基础镜像,维护者,镜像操作指令,启动时执行命令

指令说明
FROM指定基础镜像
MAINTAINER指定维护者信息,已经过时,可以使用LABEL maintainer=xxx 来替代
RUN运行命令 v
CMD指定启动容器时默认的命令 v
ENTRYPOINT指定镜像的默认入口.运行命令 v
EXPOSE声明镜像内服务监听的端口 v
ENV指定环境变量,可以在docker run的时候使用-e改变 v;会被固化到image的 config里面
ADD复制指定的src路径下的内容到容器中的dest路径下,src可以为url会自动下载, 可以为tar文件,会自动解压
COPY复制本地主机的src路径下的内容到镜像中的dest路径下,但不会自动解压等
LABEL指定生成镜像的元数据标签信息
VOLUME创建数据卷挂载点
USER指定运行容器时的用户名或UID
WORKDIR配置工作目录,为后续的RUN、CMD、ENTRYPOINT指令配置工作目录
ARG指定镜像内使用的参数(如版本号信息等),可以在build的时候,使用--buildargs改变 v
OBBUILD配置当创建的镜像作为其他镜像的基础镜像是,所指定的创建操作指令
STOPSIGNAL容器退出的信号值
HEALTHCHECK健康检查
SHELL指定使用shell时的默认shell类型

FROM

FROM 指定基础镜像,最好挑一些apline,slim之类的基础小镜像.

scratch镜像是一个空镜像,常用于多阶段构建

如何确定我需要什么要的基础镜像?

  1. Java应用当然是java基础镜像(SpringBoot应用)或者Tomcat基础镜像(War应用)
  2. JS模块化应用一般用nodejs基础镜像
  3. 其他各种语言用自己的服务器或者基础环境镜像,如python、golang、java、php等

LABEL

标注镜像的一些说明信息。

LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"

ARG&&ENV

ARG简单来说就是构建时传递的参数,运行时无效,也就是说CMD和ENTRYPOINT时失效。

  1. ARG指令定义了一个变量,用户可以在构建时使用--build-arg = 传递,来改变Dockerfile定义的对于arg,docker build命令会将其传递给构建器。
docker build --no-cache --build-arg param="12233" --build-arg msg="aaa" -t demo:test -f Dockerfile .
#. 是上下文环境,指的是dockerfile所在的位置
  1. --build-arg 指定参数会覆盖Dockerfile 中指定的同名参数
  2. 如果用户指定了 未在Dockerfile中定义的构建参数 ,则构建会输出警告。
  3. ARG只在构建期有效,运行期无效 ,可以写在任何位置定义并在以后使用,比如ARG写在开头指定版本号,后面再用--build-arg 传入。
  4. 不建议使用构建时变量来传递诸如github密钥,用户凭据等机密。因为构建时变量值使用docker history是可见的。
  5. ARG变量定义从Dockerfile中定义的行开始生效。
  6. 使用ENV指令定义的环境变量始终会覆盖同名的ARG指令。

ENV在构建期和运行期都生效。

  1. 在构建阶段中所有后续指令的环境中使用,并且在许多情况下也可以内联替换。

  2. 引号和反斜杠可用于在值中包含空格。

  3. ENV 可以使用key value的写法,但是这种不建议使用了,后续版本可能会删除。

构建期不能改ENV的值,运行期可以修改

ENV MY_MSG hello
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
#多行写法如下
ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
MY_CAT=fluffy
  1. docker run --env(-e) 可以修改这些值
  2. 容器运行时ENV值可以生效
  3. ENV在image阶段就会被解析并持久化(docker inspect image查看),参照下面示例。
FROM alpine
ENV arg=1111111
ENV runcmd=$arg
RUN echo $runcmd
CMD echo $runcmd
#ENV的固化问题: 改变arg,会不会改变 echo的值,会改变哪些值,如何修改这些值?

综合测试:

FROM alpine
ARG arg1=22222
ENV arg2=1111111
ENV runcmd=$arg1
RUN echo $arg1 $arg2 $runcmd
CMD echo $arg1 $arg2 $runcmd

ENV能引用ARG的值,如果CMD改了arg2,那么arg2改了,runcmd并没有改。原因是docker build的时候,env环境的信息会固化,直接在镜像配置里面就已经写死,docker image inspect xxx可以查看到。

RUN

run是构建时期我们会运行的指令,也就是根据Dockerfile创建一个镜像的整个过程时期。这和CMD命令有本质区别。

  1. RUN指令在当前镜像层顶部的新层执行任何命令,并提交结果,生成新的镜像层。
  2. 生成的提交映像将用于Dockerfile中的下一步。 分层运行RUN指令并生成提交符合Docker的核心概念,就像源代码控制一样。
  3. exec形式可以避免破坏shell字符串,并使用不包含指定shell可执行文件的基本映像运行RUN命令。 可以使用SHELL命令更改shell形式的默认shell。 在shell形式中,您可以使用\(反斜杠)将一条 RUN指令继续到下一行。

RUN ( shell 形式, /bin/sh -c 的方式运行,避免破坏shell字符串)

RUN ["executable", "param1", "param2"] ( exec 形式)

举例:

RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
#上面等于下面这种写法
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
RUN ["/bin/bash", "-c", "echo hello"]

区别除了形式还在于exec形式不能输出变量信息,也就是不会进行变量替换。

举例:

# 测试案例
FROM alpine
LABEL maintainer=flitsneak xx=aa
ENV msg='hello flitsneak'
RUN echo $msg
RUN ["echo","$msg"]
RUN /bin/sh -c 'echo $msg'
RUN ["/bin/sh","-c","echo $msg"]
CMD sleep 10000
#总结; 由于[]不是shell形式,所以不能输出变量信息,而是输出$msg。其他任何/bin/sh -c 的形式都可以输出变量信息

CMD&&ENTRYPOINT

一般CMD是给ENTRYPOINT提供参数。

CMD指令可以被修改,而ENTRYPOINT无法修改。

多个CMD会覆盖,只有最后一次生效,ENTRYPOINT也一样。

CMD命令是运行时期会运行的命令,也就是容器启动默认运行的命令。

CMD的主要目的是为执行中的容器提供默认值。 这些默认值可以包含可执行文件,也可以省略可 执行文件,在这种情况下,您还必须指定ENTRYPOINT指令。

如果使用CMD为ENTRYPOINT指令提供默认参数,则CMD和ENTRYPOINT指令均应使用JSON数组格 式指定。

cmd和entrypoint都可以做容器启动的入口。

CMD有三种写法:

  1. CMD ["executable","param1","param2"](exec方式,首选)
  2. CMD ["param1","param2"](为ENTRYPOINT提供默认参数)
  3. CMD command param1 param2(shell 形式)

["echo","$"]不是 bash -c的方式取不出环境变量

echo $param = ["/bin/sh","-c","命令写在这里 echo $"]

ENTRYPOINT的两种写法:

  1. ENTRYPOINT ["executable","param1","param2"](exec方式,首选)
  2. ENTRYPOINT command param1 param2(shell形式)
# 一个示例
FROM alpine
LABEL maintainer=flitsneak
CMD ["1111"]
CMD ["2222"]
ENTRYPOINT ["echo"]
#构建出如上镜像后测试
docker run xxxx:效果 echo 1111

组合效果:

无ENTRYPOINTENTRYPOINT exec_entry p1_entryENTRYPOINT [“exec_entry”, “p1_entry”
无CMD错误,不允许的写法;容器没有启动命令/bin/sh -c exec_entry p1_entryexec_entry p1_entry
CMD["exec_cmd","p1_cmd]exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry exec_cmd p1_cmd
CMD["p1_cmd","p2_cmd"]p1_cmd p2_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry p1_cmd p2_cmd
CMD exec_cmd p1_cmd/bin/sh -c exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry /bin/sh -c exec_cmd p1_cmd
这条竖线总是以ENTRYPOINT为准这条竖线,ENTRYPOINT和CMD共同作用
  1. ENTRYPOINT ping baidu.com + CMD 怎么写都没用,容器启动都是以ENTRYPOINT完整命令为准。

  2. CMD ["baidu.com"]
    ENTRYPOINT ["ping"]
    

    docker run -it --rm demo:test flitsneak.wang

    这时CMD的命令就改为flitsneak.wang

  3. ENTRYPOINT才是唯一入口

FROM alpine
ENV url=baidu.com
CMD ["ping","baidu.com"]#正常
CMD ["ping",${url}]#无法取出变量
CMD ["bin/sh","-c","ping ${url}"]#官方推荐

ADD&&COPY

ADD和COPY用法差不多,不过ADD支持远程下载文件和解压。

FROM alpine
ADD https://download.redis.io/release/redis-6.2.1.tar.gz /dest
RUN cd /dest/
RUN ls -l

这里有个问题,两个RUN没有上下文关系,并不是进入到了dest/文件夹ls,仍然是查看的根目录。所以改为

RUN cd /dest && ls -l

如果是ADD *.tar.gz /app/会自动解压

src 路径必须在构建的上下文中; 不能使用 ../something /something 这种方式,因为docker 构建的第一步是将上下文目录(和子目录)发送到docker守护程序。

docker build --no-cache -t demo:test -f Dockerfile /root/dockerfiles/

. 是Dockerfile目录,也可指定src目录。

如果 src 是URL,并且 dest 不以斜杠结尾,则从URL下载文件并将其复制到 dest 。 如果 dest 以斜杠结尾,将自动推断出url的名字(保留最后一部分),保存到 dest。

如果 src 是目录,则将复制目录的整个内容,包括文件系统元数据。

COPY

COPY命令和ADD功能一样,只是不能自动解压。COPY还可以改变用户权限

FROM alpine
#开用户,有问题??改数字1000
RUN adduser redis##不是linux的真实用户而是容器的
#给当前容器开一个用户,以后的命令可以用这个用户运行,可能没有权限
USER redis:redis#USER 1000:1000
#以容器的用户和组,授予这个用户全部权限
COPY --chown=redis:redis *.tar.gz /redis/##1000:1000

COPY的两种写法:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
  1. --chown功能仅在用于构建Linux容器的Dockerfiles上受支持,而在Windows容器上不起作用
  2. COPY指令从 src 复制新文件或目录,并将它们添加到容器的文件系统中,路径为 dest 。
  3. 可以指定多个 src 资源,但是文件和目录的路径将被解释为相对于构建上下文的源。
  4. 每个 src 都可以包含通配符,并且匹配将使用Go的filepath.Match规则进行。
COPY hom* /mydir/ #当前上下文,以home开始的所有资源
COPY hom?.txt /mydir/ # ?匹配单个字符
COPY test.txt relativeDir/ # 目标路径如果设置为相对路径,则相对与 WORKDIR 开始
# 把 “test.txt” 添加到 <WORKDIR>/relativeDir/
COPY test.txt /absoluteDir/ #也可以使用绝对路径,复制到容器指定位置
#所有复制的新文件都是uid(0)/gid(0)的用户,可以使用--chown改变
COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/

WORKDIR

WORKDIR指令为Dockerfile中跟随它的所有 RUN,CMD,ENTRYPOINT,COPY,ADD 指令设置工作目录。 如果WORKDIR不存在,即使以后的Dockerfile指令中未使用它也将被创建。

WORKDIR指令可在Dockerfile中多次使用。 如果提供了相对路径,则它将相对于上一个WORKDIR指令的路径。 例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
#结果 /a/b/c

也可以用到环境变量

ENV DIRPATH=/path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
#结果 /path/$DIRNAME

WORKDIR不仅可以指定基础目录,还可以为进入容器指定一个默认目录。

比如nginx镜像可以做成:

FROM nginx
WORKDIR /usr/share/nginx/html

这样进入nginx容器后默认所处的位置就是/usr/share/nginx/html

VOLUME

之前解释的很详细了,这里讲下Dockerfile的细节:

写法:

VOLUME ["/var/log/"] #可以是JSON数组
VOLUME /var/log #可以直接写
VOLUME /var/log /var/db #可以空格分割多个

要注意,使用VOLUME声明的卷,那么以后对于卷内容的修改会被丢弃,所以一定在volume声明之前修改内容。

FROM alpine
RUN mkdir /hello && mkdir /app
RUN echo 123 > /hello/a.txt
RUN echo 234 > /app/b.txt#匿名卷会同步到linux中
#VOLUME挂载出去的东西,容器改变也不会最终commit时生效
VOLUME ["/hello","/app"]
RUN echo hhh > /hello/a.txt#之后不再生效

指定了VOLUME,即使启动容器没有指定-v参数,也会自动进行匿名挂载。

docker diff xxx

看不到-v 挂载出去的目录改变。总结就是-v或者VOLUME挂载出去的修改docker commit时不生效。挂载只是为了同步修改。

USER

用法

USER <user>[:<group>]
USER <UID>[:<GID>]

上面展示过,好像非数字不生效。

EXPOSE

EXPOSE指令通知Docker容器在运行时在指定的网络端口上进行侦听。 可以指定端口是侦听TCP还 是UDP,如果未指定协议,则默认值为TCP。 EXPOSE指令实际上不会发布端口。 它充当构建映像的人员和运行容器的人员之间的一种文档,即有关打算发布哪些端口的信息。 要在运行容器时实际发布端口,请在docker run上使用-p标志发布 并映射一个或多个端口,或使用-P标志发布所有公开的端口并将其映射到高阶端口。

EXPOSE <port> [<port>/<protocol>...]
EXPOSE [80,443]
EXPOSE 80/tcp
EXPOSE 80/udp

multi-stage builds(多阶段构建)

一个镜像分为多个阶段构建,比如我们的springboot程序,maven构建,jre打包等最终构建成docker镜像。

每一层都依赖上一次的结果。

参考文档:

https://docs.docker.com/develop/develop-images/multistage-build/

完整案例:

FROM maven:3.6.1-jdk-8-alpine
#指定工作目录/app,然后复制同级目录的pom和src打包
WORKDIR /app
COPY pom.xml .
COPY src .

RUN mvn clean package -Dmaven.test.skip=true
#选择查看目录位置是否正确
RUN pwd && ls -l
#打包完成后将target下的jar包cp到根目录命名为app.jar
RUN cp /app/target/*.jar /app.jar
RUN ls -l

#第二阶段
FROM openjdk:8-jre-alpine

LABEL maintainer="flitsneak@gmail.com"

# 从上一个阶段复制内容
COPY --from=builder /app/target/*.jar /app.jar

# 修改时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone && touch /app.jar

ENV JAVA_OPTS=""
ENV PARAMS=""
# 运行jar包
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]
<!--为了加速下载需要在pom文件中复制如下 -->
<repositories>
<repository>
<id>aliyun</id>
<name>Nexus Snapshot Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
</releases>
<!--snapshots默认是关闭的,需要开启 -->
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyun</id>
<name>Nexus Snapshot Repository</name>
<url>https://maven.aliyun.com/repository/public</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
######小细节
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
或者
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
可以让镜像时间同步。
## 容器同步系统时间 CST(China Shanghai Timezone)
-v /etc/localtime:/etc/localtime:ro
#已经不同步的如何同步?
docker cp /etc/localtime 容器id:/etc/

docker build --build-arg url="git address" -t demo:test . :自动拉代码并构建镜像

FROM maven:3.6.1-jdk-8-alpine AS buildapp
#第二阶段,把克隆到的项目源码拿过来
# COPY --from=gitclone * /app/
WORKDIR /app
COPY pom.xml .
COPY src .
RUN mvn clean package -Dmaven.test.skip=true
# /app 下面有 target
RUN pwd && ls -l
RUN cp /app/target/*.jar /app.jar
RUN ls -l
### 以上第一阶段结束,我们得到了一个 app.jar
## 只要一个JRE
# FROM openjdk:8-jre-alpine
FROM openjdk:8u282-slim
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
LABEL maintainer="flitsneak@gmail.com"
# 把上一个阶段的东西复制过来
COPY --from=buildapp /app.jar /app.jar
# docker run -e JAVA_OPTS="-Xmx512m -Xms33 -" -e PARAMS="--spring.profiles=dev -
-server.port=8080" -jar /app/app.jar
# 启动java的命令
ENV JAVA_OPTS=""
ENV PARAMS=""
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]

Images瘦身

  1. 选择最小的基础镜像
  2. 合并RUN环节的所有指令,少生成一些层
  3. RUN期间可能安装其他程序会生成临时缓存,要自行删除。如:
# 开发期间,逐层验证正确的
RUN xxx
RUN xxx
RUN aaa \
aaa \
vvv \
#生产环境
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion \
&& rm -rf /var/lib/apt/lists/*
  1. 使用 .dockerignore 文件,排除上下文中无需参与构建的资源
  2. 使用多阶段构建
  3. 合理使用构建缓存加速构建。[--no-cache]

springboot 生产写法

FROM openjdk:8-jre-alpine
LABEL maintainer="flitsneak@gmail.com"
COPY target/*.jar /app.jar
RUN apk add -U tzdata; \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime; \
echo 'Asia/Shanghai' >/etc/timezone; \
touch /app.jar;#更新最后修改时间
ENV JAVA_OPTS=""
ENV PARAMS=""
EXPOSE 8080
ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom $JAVA_OPTS -jar /app.jar $PARAMS" ]
# 运行命令 docker run -e JAVA_OPTS="-Xmx512m -Xms33 -" -e PARAMS="--
spring.profiles=dev --server.port=8080" -jar /app/app.jar

docker-compose

安装文档:

https://docs.docker.com/compose/install/

我们正常的应用来说不只是一个jar包,很可能是多个组件构成的比如redis和nginx以及mysql都是不可缺失的。

常规来讲,我们需要启动至少四个docker然后调试各个docker之间的网络通信。

而docker-compose就可以通过一个yml文件配置所有要启动的内容。

通过docker-compose up/down一键启动和关闭,并且docker之间的网络是通的,也就是自定义网络。

实际上是不会使用docker-compose的最终还是k8s。

安装

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.0/dockercompose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

dockerhub示例

官网给的是个app.py程序,略过了。。

首先编写compose文件[compose.yaml]

compose文件名 docker-compose.yml, docker-compose.yaml, compose.yml, compose.yaml

version: "3.9" #指定版本号;查看文档https://docs.docker.com/compose/compose-file/
services: #所有需要启动的服务
  web: #第一个服务的名字
    build: #docker build -t xxx -f Dockerfile .
      dockerfile: Dockerfile
      context: .
    image: 'hello:py'
    ports: #指定启动容器暴露的端口
      - "5000:5000"
  redis: #第二个服务的名字
    image: "redis:alpine"
  mysqlserver: #第三个服务

docker-compose up 就可以启动了加-d后台启动

下面给个较为复杂的情况:

version: "3.7"
services:
  app:
    image: node:12-alpine
    command: sh -c "yarn install && yarn run dev"
    ports:
      - 3000:3000
    working_dir: /app
    volumes:
      - ./:/app
    environment:
      MYSQL_HOST: mysql
      MYSQL_USER: root
      MYSQL_PASSWORD: secret
      MYSQL_DB: todos
    networks:
      - hello
      - world
    deploy: #安装docker swarm
      replicas: 6 #指定副本:处于不同的服务器(负载均衡+高可用)
  mysql: #可以代表一个容器,ping 服务名 mysql 可以访问
    image: mysql:5.7 #负载均衡下,数据一致怎么做???主从同步,读写分离
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos
    networks: #这个服务加入那个自定义网络
      - hello
    deploy: #安装docker swarm
      replicas: 6 #指定副本:处于不同的服务器(负载均衡+高可用)
  redis:
    image: redis
    networks:
      - world
volumes:
  todo-mysql-data:
#网络声明,对上面单独配置的网络都需要在此声明,详细配置需参考文档
networks:
  hello:
  world:

docker swarm

docker-compose + docker swarm = 集群模式。

docker swarm init(创建一个master节点)

控制台打印 docker swarm join --token SWMTKN-1-1i0biktih9tfn7mrj6asn27em4vydg8pp00u930nrycpgct1ww7ecs32nl5f5y 8qx6e5lp4f064 10.120.82.4:2377

其他和本机(master)能互通的机器 把上面的命令运行,加入集群。

docker join docker join manager 会给token,整体架构和k8s一模一样。

docker swarm join --token SWMTKN-1-1i0biktih9tfn7mrj6asn27em4vydg8pp00u930nrycpgct1ww7ecs32nl5f5y8qx6e5lp4f064 10.120.82.4:2377

k8s怎么解决: helm把整个应用的部署打成应用包,helm install mysql(主从)

总结: docker-compose 和 docker swarm没啥用了解即可。

5、Jenkins

6、简单的CI/CD落实

7、k8s

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议