基本概念
Docker架构
- K8S: CRI (Container Runtime Interface)
- Client: 客户端, 操作docker服务器的客户端(命令行或者界面)
- Docker_Host: Docker主机;安装Docker服务的主机
- Docker_Daemon:后台进程;运行在Docker服务器的后台进程
- Containers :容器;在Docker服务器中的容器(一个容器一般是一个应用实例,容器间互相隔离)
- Images:镜像、映像、程序包; Image是只读模板,其中包含创建Docker容器的说明。容器是由Image运 行而来, Image固定不变。
- Registries :仓库;存储Docker Image的地方。
- 官方远程仓库地址: https://hub.docker.com/search
Docker隔离原理
-
linux 的namespace 6项隔离 (资源隔离)
| namespace | 系统调用参数 | 隔离内容 |
| — | — | — |
| UTS | CLONE_NEWUTS | 主机和域名 |
| IPC | CLONE_NEWIPC | 信号量、消息队列和共享内存 |
| PID | CLONE_NEWPID | 进程编号 |
| Network | CLONE_NEWNET | 网络设备、网络栈、端口等 |
| Mount | CLONE_NEWNS | 挂载点(文件系统) |
| User | CLONE_NEWUSER | 用户和用户组 | -
cgroups资源限制 (资源限制) cgroup提供的主要功能如下:
- 资源限制:限制任务使用的资源总额,并在超过这个配额时发出提示
- 优先级分配:分配CPU时间片数量及磁盘IO带宽大小、控制任务运行的优先级
- 资源统计:统计系统资源使用量,如CPU使用时长、内存用量等
- 任务控制:对任务执行挂起、恢复等操作
Docker安装
- 详细参照文档: https://docs.docker.com/engine/install/centos/
- 移除旧版本
yum remove docker*
- 设置docker yum源
yum install -y yum-utils
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
#此处可以百度 docker yum aliyun 切换为ali的yum源
- 安装最新docker engine
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
#例如:
yum install docker-ce-3:20.10.5-3.el7.x86_64 docker-ce-cli-3:20.10.5-
3.el7.x86_64 containerd.io
#注意加上 .x86_64 大版本号
- 服务启动
- systemctl start docker
- systemctl enable docker
{
"registry-mirrors": [
"https://hub.rat.dev",
"https://docker.anyhub.us.kg",
"https://dockerhub.icu",
"https://docker.awsl9527.cn"
]
}
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://huecker.io",
"https://dockerhub.timeweb.cloud",
"https://noohub.ru"
]
- 镜像加速
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.anyhub.us.kg",
"https://dockerhub.icu",
"https://docker.awsl9527.cn"
]
}
EOF
systemctl daemon-reload
systemctl restart docker
#以后docker下载直接从阿里云拉取相关镜像
docker命令
- docker命令手册: https://docs.docker.com/engine/reference/commandline/docker/
- 容器到镜像 , 从镜像到容器
- docker images
- docker run 镜像名称
- docker image 对于镜像的管理操作
- docker pull redis ==> docker pull redis:latest
- docker rmi -f $(docker images -aq) 删除全部镜像
- docker rm 删除容器
- docker rm -f $(docker ps -aq) 删除全部容器
- docker rmi -f 强制删除镜像
- docker image prune 移除游离镜像 dangling:游离镜像(没有镜像名字的)
- docker tag 原镜像 :标签 新镜像名 :标签 给镜像重命名
- docker create|run [OPTIONS] IMAGE [COMMAND] [ARG . . . ] 创建容器
- docker create [设置项] 镜像名 [启动命令] [启动参数 . . . ]
- docker create -d --name myredis -p 6379:6379 redis
- -p 主机端口: 容器端口 -p 可以多次指定,绑定多个端口
- -p 指定端口映射 -P 自动端口映射
- –name 容器的名称
- -d 后台启动
- – restart=awlays docker重启,容器也启动
- –rm 用完就删除这个容器
- docker ps -a 查看所有运行和退出的容器
- docker rm -f $(docker ps -aq) 删除所有容器
- docker stop ,pause ,unpause , kill ,start 停止, 暂停, 不暂停, 杀掉, 开启 容器
- docker logs 容器名称 查看容器的日志
- docker logs -f myredis
- -f 一直追踪日志的数据
- -t 只看最后几行
- docker attach 容器名称 进入容器, 绑定的是控制台,如果退出后会导致容器也退出
- docker exec -it 容器名称 /bin/bash 进入容器的控制台
- –privileged 以root用户进入
- docker inspect 容器|镜像 查看容器或者镜像的详情
- docker cp 容器名:路径 当前目录下的文件 容器的复制到外边 反过来,外边的复制到容器中
- docker diff 容器名 容器发生过什么变化
- docker commit 把容器的改变提交变为新的镜像
- docker commit -a cf -m “first commit” mynginx1 mynginx:v1
- -a 作者信息
- -m 备注信息
- 后边 :容器名 镜像名:版本号
- 推送到dockerhub:
- docker push dafei12/mynginx:tagname
- docker镜像的完整名字: docker.io/dafei12/mynginx:v1
- docker.io/名称空间/镜像名称:版本号
- 登录远程docker hub仓库
- docker login
- 修改镜像名称
- docker tag mynginx:v1 dafei12/mynginx:v1
- docker push dafei12/mynginx:v1 推送镜像
- 导出容器
- docker export 导出容器
- docker export -o nginx.tar mynginx1
- -o 文件名 容器名
- docker import
- docker import nginx.tar mynginx:v2
- 文件名 镜像名:版本
- 导入tar的内容创建一个镜像,再导入进来的镜像直接启动不了容器。 docker ps --no-trunc看下之前的完整启动命令再用他
- docker run -d --name mynginx6 -p 81:80 mynginx:v2 /docker-entrypoint.sh nginx -g ‘daemon off;’
- 导出镜像
- save: 把一个或者多个镜像保存为tar文件
- docker save -o busybox.tar busybox:latest 把busybox镜像保存成tar文件
- docker load -i busybox.tar 把压缩包里面的内容直接导成镜像
- 镜像长久运行: 镜像启动的时候有阻塞的进程
- docker run -it busybox 交互模式进入当前镜像启动的容器
- 做镜像
- 准备dockerfile
- 编写dockerfile
- 构建镜像
- docker build -t busy -f Dockerfile .
- -t 镜像名 -f 文件名
- –build-arg a=b --build-arg c=d 指定构建时的参数
- -e a=b 就可以修改
- docker history redis 查看镜像有多少层
- docker ps -s 看到容器真正用到的文件大小
- docker volume ls docker命令查看卷的内容
- docker volume inspect 卷的id
- docker volume prune : 移除无用卷
- 可视化界面启动
- 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
- docker events 查看docker中发生的事件
- docker system prune docker 系统清理
- nptables -nL 展示dns网关转发链路
- netstat -nl|grep 端口 查看主机监听的端口
- docker network ls 查看docker的网络
- docker network create 创建自定义网络
- docker network create --subnet=192.168.0.0/16 --gateway=192.168.0.1 mynet
- –subnet 指定子网域, --gateway 指定网关
网络和存储原理
存储原理
- 容器: 某个软件完整的运行环境, 包含了一个小型的linux系统, 如果宿主机里面同时4个nginx, 一个nginx运行时完整环境有20MB; 4个nginx 合起来占用多少的磁盘空间 80?
- docker在底层使用自己的存储驱动。来组件文件内容storage drivers。之前docker 基于AUFS (联合文件系 统)
- nginx底层的文件存储目录
- LowerDir :底层目录; dif (只是存储不同);包含小型linux和装好的软件
- 倒着看 : 小linux系统 + Dockerfile的每一个命令可能都引起了系统的修改,所以和git 一样,只记录变化 (小linux系统 + 变化)
- 进入到镜像启动的容器, 容器使用的底层文件还在镜像中
- 镜像分层,镜像构建时会分为多层镜像
- UpperDir: 上层目录 容器的修改,保存在这个位置
- MergedDir: 合并目录. 容器最终的完整工作目录全内容都在合并层, 数据卷在容器层产生, 所有的增删改都在容器层;
- WorkDir: 容器的工作目录
- 镜像的层不会变,只有容器的层会发生变化
- 数据卷在容器层(上层)
- LowerDir :底层目录; dif (只是存储不同);包含小型linux和装好的软件
- 镜像写时复制
- 写时复制是一种共享和复制文件的策略,可最大程度地提高效率。
- 如果文件或目录位于映像的较低层中,而另一层(包括可写层)需要对其进行读取访问,则它仅使用现有文件。
- 另一层第一次需要修改文件时(在构建映像或运行容器时),将文件复制到该层并进行修改。这样可以将I/0 和每个后续层的大小最小化。
- 加的那一层在upperDir
- 镜像层
- Docker镜像由一系列层组成。 每层代表图像的Dockerfile中的一条指令。 除最后一层外的每一层都是只 读的
- 每一个指令都可能会引起镜像改变,这些改变类似git的方式逐层叠加
- docker的默认底层驱动: overlay2(写时复制), 之前时联合文件系统
- 联合文件系统是通过挂载实现的
- overlay2是通过共用实现的
挂载
- 每一个容器里面的内容,支持三种挂载方式:
- docker自动在外部创建文件夹自动挂载容器内部指定的文件夹内容 , Dockerfile VOLUME指令的作用
- 自己在外部创建文件夹,手动挂载
- 可以把数据挂载到内存中
–mount 挂载到linux宿主机,手动挂载(手动挂载)
-v 可以自动挂载,挂载的地方: linux主机或者docker自动管理的这一部分区域
- volume
- -v 宿主机绝对路径:Docker容器内部绝对路径, 叫挂载;这个有空挂载问题
- 宿主机的目录原始是空的,挂载到容器,会将容器里的文件也搞成空的
- -v 不以/开头的路径:Docker容器内部绝对路径:叫绑定(docker会自动管理,docker不会把他当前目 录,而把它当前卷)
- cd /var/lib/docker/volumes/ docker自动卷挂载的默认目录
- -v 宿主机绝对路径:Docker容器内部绝对路径, 叫挂载;这个有空挂载问题
- -v 以绝对路径开头: 可能发生空挂载
- 解决空挂载: docker cp, 先把内容复制出来
- docker cp nginxdemo:/etc/nginx /root/nginxconf #注意/的使用
- -v 不以绝对路径方式
- 先在docker底层创建一个你指定名字的卷(具名卷)
- 把这个卷和容器内部目录绑定
- 容器启动以后, 目录里面的内容就在卷里面存着
- -v 没有冒号,直接写映射文件路径
- 生成卷的名字为uuid
- 挂载的容器的文件夹后边加 :ro 指定了挂载文件的读写权限
- 卷挂载最终指令: docker run -d -P -v nginxconf:/etc/nginx/ -v nginxpage:/usr/share/nginx/html nginx
一个公网多个私网机器
- 1个公网绑定私网的网关,配置路由转发,不同的端口路由到不同私网机器的22端口
Dockerfile编写
基础指令
- Dockerfile的组成
- 镜像基础信息(FROM) 维护者信息(LABEL) 镜像操作指令 启动时执行指令
- LABEL : 给镜像打标签, k=v k=v
- RUN指令 : 镜像操作指令, 构建时期运行的指令,根据dockerfile创建镜像的时期
- RUN shel 形式, /bin/sh -c 的方式运行,避免破坏shell字符串)
- RUN echo $Param 可以取到环境变量,获取不到arg
- RUN [ “executable”, "param1 ", “param2”] exec 形式
- 这个方式取不到环境变量和构建参数 $Param
- 每次RUN的基础环境都是根目录,RUN指令没有上下文关系
- 可以通过 两个指令通过 & 符号连接
- RUN shel 形式, /bin/sh -c 的方式运行,避免破坏shell字符串)
- ENTRYPOINT: 容器启动时运行的内容
- 运行的指令
- CMD可以修改,ENTRYPOINT 不可以修改
- CMD 镜像启动要运行很长命令,准备一个sh文件,让镜像启动运行sh文件
- 运行时期,根据创建的镜像启动的容器,容器默认启动的命令
- 可以获取到环境变量,但是获取不到arg参数
- 指令的参数
- CMD指定的指令,可以在容器启动时后边被修改
- 多个CMD只有最后一个生效
- [“echo”,“${param}”] 不是bash -c的方式, 取不出环境变量性
- echo $param = [“/bin/sh”,“-c”,“多长的命令都写在这里 echo ${param}”] 这个可以取出环境变量
CMD ["ping","baidu.com"]
CMD ["ping",${url}]
CMD ping ${url}
CMD ["bin/sh","-c","ping ${url}"]
//CMD怎么写都没用,以ENTRYPOINT为准
ENTRYPOINT ping www.baicu.com
//官方推荐的写法,这两个加在一起是一个完整的命令,在容器启动时执行
//写指令
ENTRYPOINT ["ping", "-c"]
//写参数,这个可以动态指定
CMD ["5","www.baidu.com"]
- ENV指定环境变量
- 会被固化在镜像的config中
- 在构建期和运行期间都可以生效
- ENV a=b
- 在构建期不能修改,但是运行时可以修改
- ENV可以引用arg的参数
- dockerfile文件中指定 a =1 ,b = a,在docker run运行时通过-e重新制定a=2, 但是b的值还是1
- ARG 可以指定构建参数
- 在定义以后,后边的环节可以用到, 但是不包括运行时的环境,只能在构建时期使用
- 可以在构建时进行变化 docker build 命令 --build-arg 进行修改,会把dockerfile中定义的覆盖掉
- arg 不能并行写
- 作用: 可以在构建镜像时,动态指定版本号,指定from镜像的版本号
- ADD:将上下文指定的内容添加到镜像中,如果时压缩文件会自动解压,远程文件会自动下载
- ADD 宿主机的位置 容器中的位置
- COPY:将上下文指定的内容添加到镜像中
- 不会自动解压和下载
- COPY --chown=用户的ID:用户组的ID 指定文件可以操作的权限用户
- USER: 指定后边的指令用这个用户运行, 用USER 1000:1000 用户的ID:用户组的ID
- WROKDIR: 指定工作目录,指定镜像构建时的工作目录,为后边所有的命令指定了基础命令
- 多个workdir可以嵌套, a/b/c
- 后续进入容器后,初始的目录也是workdir指定的目录
- VOLUME 挂载 (匿名卷挂载)
- 写法: VOLUME [“/hello”,“/app”], 两个内容,挂载两个目录
- hello目录和app目录都会挂载出去
- 挂载: 容器内的指定文件夹,如果不存在就创建
- 指定了 VOLUME ,即使启动容器没有指定 -v 参数,我们也会自动进行匿名卷挂载
- 指定了挂载目录后,dockerfile以后对目录的操作是不生效的
- VOLUME挂载出去的东西,容器改变也不会最终commit的时候生效
- 使用 VOLUME和-v挂载出去的目录(外面变,容器里面变),但是 docker commit 提交当前容器的所有变化为镜像的时候,就会丢弃
- 挂载只有一点就是方便在外面修改,或者把外面的东西直接拿过来
- 作用JAVA 日志都要挂外面 /app/log VOLUME [“/log”]
- EXPOSE 这个只是一个声明,docker 使用-P指令随机分配端口时,会给这两个端口分配宿主机端口
- EXPOSE 8080
- EXPOSE 999
多阶段构建
- 目的: 打包时需要maven的环境,但是程序运行时只需要java的基础环境,让最终生成的镜像只有java的基础环境而没有mave的环境
//多阶段构建
//as 打个标记
FROM alpine AS mave
打包编译的命令
FROM jre
//将上边镜像生成的文件复制到当前镜像
//--from 指定上个镜像的标记
COPY --from=mave xxx文件 xxx文件
ENTRYPOINT [ "executable" ]
FROM maven:3.6.1-jdk-8-alpine AS buildapp
WORKDIR /app
# 只复制pom文件和src文件
# 要指定maven仓库的地址,可以在pom.xml中进行指定
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
# 指定时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
LABEL maintainer="534096094@qq.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" ]
- dockerfile 瘦身
- 选择最小的基础镜像
- 合并RUN环节的所有指令,少生成一些层
- 使用 .dockerignore 文件,排除上下文中不需要构建的文件
- 使用多阶段构建
Docker网络
- 网络流量接入
- 先到宿主机网卡,通过主机端口映射转发到docker0网卡,通过端口区分转发到宿主机对应的容器网卡,然后再到容器的内部网卡
- 容器间ping id 是互通的,但是通过名字ping不通的
- 每个容器启动起来,docker都会为其自动分配一个ip,这个ip挂载到docker0这个桥接网关
- 桥接网络
- ens33: 虚拟机桥接网络
- docker0: 桥接网络
- 容器内的网卡: 网卡27
- 宿主机的网络
- 容器内的网卡27,宿主机的网卡26,一对(网卡的挂载)
- 主机网络
- 容器启动时指定 -network=host,和宿主机共享网络,这个模式下容器间的端口不能一样
- 自定义网络
- 容器之间互相连接,但是容器1要在配置中写死容器2的地址,但是容器2的地址在容器2启动后才能知道,而且容器2每次启动后的ip地址都可能变化,所以要在容器1中通过容器2的容器名称访问到容器2
- docker run --name=my1 --link=my2 -it alpine ==> docker容器启动时通过–ling=容器名指定要接入的另一个容器,但是另一容器重启后ip变化了,通过容器名访问时访问不了的,因为这个会将另一容器名称和ip写入到当前容器的host映射中
- docker network create --subnet=192.168.0.0/16 --gateway=192.168.0.1 mynet 创建自定义网络
- docker run 的时候通过–netwrok=mynet 指定容器运行时的网络,然后容器之间可以通过容器的名称访问到了
- docker network connect 自定义网络名称 容器名称 将容器名称加入到自定义网络
- 点分10进制
- /10 表示前边的多少位二进制是一样的,后边可以变化
docker-compose
- 一个复杂的应用,需要启动多个容器,docker-compose将启动多个容器的动作自动化,利用yaml文件进行编排
- yaml文件中指定需要启动的容器
- 启动命令: docker-compose up -d
- 启动流程
- 创建自定义网络(可以直接通过容器名ping到另一容器)
- 生成两个镜像,构建或者下载
- 根据镜像启动容器
docker-swarm
- docker 集群
- docker swarm init 初始化swarm
- 在别的机器执行
- docker swarm join --token SWMTKN-1-4kdbja4t7p4a5kdm0r7h89o6llem79bmhmqviidjmzagaj5pbr-b53quc3onxa9islrpn6z4q45z 172.16.228.5:2377 加子节点
- docker swarm join-token manager 加主节点
- compose中的 deploy.replicas: 6 指定容器有多少个副本就可以生效了
- docker node ls 查看集群所有节点的状态