Docker 学习手册
《Docker — 从入门到实践》电子版本 https://www.kancloud.cn/docker_practice/docker_practice
下载镜像,根据镜像创建容器,运行容器
快速上手
docker run -it centos '/bin/bash' # 自动下载镜像、创建窗口并在前台运行
docker run -d contos '/bin/bash' -c '需要运行的命令' # 自动下载镜像、创建窗口并在后台运行
docker exec -it dockerId '/bin/bash' # 挂到容器中
docker logs dockerid # 获得窗口的输出内容
docker rm dockerId # 删除容器
docker rmi imageId # 删除镜像
安装
高版本 Linux
# https://store.docker.com/editions/community/docker-ce-server-centos
# docket ce 提示只支持 centos 7.3
curl -fsSL https://get.docker.com/ | sh
低版本 Linux
# 内核 >= 2.6.32 (centos 6.* ) 使用以下方法安装
# 参考
# https://wiki.centos.org/zh/Cloud/Docker
# https://segmentfault.com/a/1190000000735011
1. # 检测版本
uname -r # 2.6.32-431.el6.x86_64
cat /etc/redhat-release # CentOS release 6.5 (Final)
2. # 安装 EPEL https://dl.fedoraproject.org/pub/epel/6/x86_64/
yum install epel-release-6-8.noarch.rpm
3. # 安装 docker
yum install docker-io
4. # 启动服务
service docker start
5. # 加入启动项
chkconfig docker on
6. # ** 未测试通过** 为 docker 设置国内源
sudo echo "DOCKER_OPTS=\"\$DOCKER_OPTS --registry-mirror=http://hub-mirror.c.163.com\"" >> /etc/default/docker
service docker start
镜像操作
使用
# 看版本
docker version
# 查看磁盘占用 docker system -h
docker system df
# 镜像名由 name:tag 组成。 name为仓库名,比如 ubuntu, tag 为区分同一仓库中的不同镜像,一般为版本号
# 下载镜像
docker pull NAME[:TAG] # 如果没有指定 TAG,则 TAG 为 latest, latest 表示最新镜像
# 下载指定仓库的镜像
docker pull hub.c.163.com/public/ubuntu[:TAG]
# 列出所有已安装镜像
docker images
# 为已安装镜像设置标签
# 比如: docker tag f6a575b7c805 myubuntu:latest
# docker images 时会发现多一条,但与源指向同一个镜像
docker tag NAME[:TAG] TAGNAME
# 查看镜像详细信息
docker inspect myubuntu:latest
# 查看镜像历史, 镜像由一层一层(layer)组成, 使用 history 可以查看这一层一层创建信息
docker history myubuntu[:TAG]
# 搜索镜像
docker search --automated -s 3 mysql # --automated 自动创建镜像 -x 一定等级的镜像
# 删除镜像
## 使用 tag 名进行删除,如果 Tag 指向同一个镜像,则只删除Tag, 如果只有一个Tag 指向镜像,则删除Tag时删除镜像文件
docker rmi imagetag # docker rmi ubuntu:14.04
## 使用镜像ID删除, 删除 imageid 对应的镜像及引用该镜像的 Tag, 如果镜像已经创建了容器了,则使用 docker rmi -f imageid 进行删除, 但建议删除容器再删除镜像
docker rmi imageid # docker rmi eddas23
## 删除所有镜像
docker rmi $( docker images -aq )
## 移除不用的数据
docker image prune # 移除一些发布新版本后残留的旧版本镜像
docker system prune # 移除更多不用数据
创建镜像
# 方法1, 根据容器创建一个镜像,
# 创建容器后,进行相应的配置,然后根据容器创建新的镜像。以后可以使用新的镜像创建容器,这样就不用重复进行配置了。
# 不建议使用,因为仅在上一层处理,镜像会变臃肿。而且可能无法查看操作的具体内容。
$ docker commit -m '备注' -a '作者' -c [dockerfile 指令] 容器编号 [repository[:TAG]]
$ docker commit \
--author "Tao Wang <twang2218@gmail.com>" \ # 作者
--message "修改了默认网页" \ # 备注
webserver \ # '窗口名、编号'
nginx:v2 # 镜像名
# 方法2, 直接从一个操作系统模板文件导入镜像
docker import **** # 可能不会用到
导出、载入
# 将镜像导出为 tar 文件
docker save -o ubuntu_14.04.tar ubuntu:14.04
# 从 tar 文件导入本地镜像库
docker load --import ubuntu_14.04.tar
docker load <ubuntu_14.04.tar
上传镜像
docker push name[:tag] # 默认上传到官方仓库
容器
基本操作
# 根据镜像创建一个容器
docker create -it --name='mydemo' centos:latest
# -d 后台运行
# -i 保持标准输入
# -t 分配一个伪终端
# --name 容器名
# --add-host=[] hosts 文件中加入主机映射, --dns: dns服务器 --hostname:主机名 --env:运行环境 --ip:指定ip
# 启动容器
docker start dockerid
# 重启容器
docker restart dockerid
# 重命名容器
docker rename 5b9 demobase
# 创建并运行, 等于 docker create => docker start
docker run
# 创建并运行容器后,使用用户可以与容器进行交互。然后使用 exit 或 ctrl+d 退出, 退出后窗口直接关闭
docker run -it centos '/bin/bash'
# 创建并运行后台容器, 容器命令运行完成后应该也会直接退出。会返回一个 dockerid
docker run -d contos '/bin/bash' -c '需要运行的命令'
# 运行后自动删除容器
docker run --rm -P dockerid # 使用 --rm 则容器终止后自动删除,--rm 与 -d 不能同时使用
# 获得窗口的输出内容
docker logs dockerid
# 附加到后台容器, ctrl+q、ctrl+p、ctrl+c 退出, 当多人attach一个容器时,如果一个人阻塞操作,所有人阻塞
docker attach dockerid
# 附加到后台容器, 看起来如果容器正在运行一个阻塞命令, attach 进去后,也会阻塞状态,而exec相当于别开一个线程
docker exec -it dockerid '/bin/bash' # 推荐使用该命令
# docker exec dockerid 'command' 本质是在容器中运行命令
# 第三方工具附加到后台窗口
nsenter
# 如果容器没有运行程序,会启动后直接退出,这样就没有机会 start 然后挂载进去了。一个可能的解决该当如下
# 目前没有找到直接运行一个已经停止工作的容器的命令,但是可以通过将容器创建镜像,然后再创建一个新容器来进行操作
docker commit dockerid imagename
docker run -it imagename --name demo bash
docker run -ti --entrypoint /bin/bash NEWIMAGENAME # 有些人运行上面的命令有问题,使用此命令可以解决
# 中止容器
docker stop dockerid # 发送 SIGTERM 信号,等待一段时间(-t=10)后再中止
docker kill dockerid # 发送 SIGKILL 信号强制中止
# 查看容器列表 #
docker ps # 列出当前运行的容器
docker ps -a # 列出所有容器
docker ps -qa # 列出所有容器编号
# 删除容器
docker rm dockerid
# 删除所有的容器
docker rm $( docker ps -aq )
# 导入导出容器,进行迁移时使用
docker export -o myback.tar dockerid
docker export dockerid > myback.tar
docker import myback.tar - 仓库[:tag]
# 复制里面的文件
docker cp <containerId>:/file/path/within/container /host/path/target
根据容器创建镜像
docker commit dockerid imagename
仓库
仓库存放镜像, 分为公开与私有, 仓库存储在注册服务器(Registry)上。
# https://hub.daocloud.io/
# docker hub 为最大的公共镜像仓库
# 时速云为国内比较大的镜像仓库
# 创建本地仓库
docker run -d -p 5000:5000 registry # 默认在 /tmp/registry 目录下
docker fun -d -p 5000:5000 -v /opt/data/registry:/tmp/registry registry # 将仓库安装到指定的位置
docker tag image:tag registryhost/username/name:tag # 标记一个镜像
docker push registryhost/username/name:tag
# 使用官方 docker 创建私有仓库
docker run -d -p 5000:5000 -v /opt/data/registry:/tmp/registry registry
# -v 将数据保存到本地 -p 进行端口映射
# 然后就可以在内网中进行镜像的上传下载了
自建创建
# 生成证书
docker run -it --rm -v certs:/home/certs alpine sh
apk add openssl
openssl req -newkey rsa:4096 -nodes -sha256 -x509 -days 365 -keyout myrepo.key -out myrepo.crt
# 启动镜像
docker run -d \
--name xregistry \
-p 5000:5000 \
-v /home/user/registry-config/config.yml:/etc/docker/registry/config.yml \ # 配置文件
-v /opt/data/registry:/var/lib/registry \ # 数据存储位置
-e REGISTRY_HTTP_TLS_CERTIFICATE=/etc/docker/certs.d/x-900HA:5000/myrepo.crt \ # 证书文件
-e REGISTRY_HTTP_TLS_KEY=/etc/docker/certs.d/x-900HA:5000/myrepo.key \ # 证书文件
--restart=always \
registry:2
registry:2.1
上传、下载
docker tag ubuntu myrepo.com:5000/ubuntu # 改个标签
docker push myrepo.com:5000/ubuntu # 上传
docker pull myreop.com:5000/ubuntu # 下载
docker tag myrepo.com:5000/ubuntu ubuntu # 改个标签
配置 TLS 证书
可以设置 Docker daemon 启动参数 DOCKER_OPTS="--INSECURE-REGISTRY myreop.com:5000" 表示信任该地址的创建,而不需要证书
可以自动生成一个,如果需要公开发行,则需要去申请一下。
# 生成证书
数据管理
容器保存数据时,可以直接保存到本地目录,也可以创建一个数据卷容器,然后保存到这个数据卷容器中
保存到本地目录
# 可以挂载目录也可以挂载文件,但是挂文件不推荐(因为在外部修改文件时会引起错误)
# 如果宿主为 windows 主机,则必须使用绝对路径
docker run -d -P --name 'demo' -v [localpath:]containerlocakpath:[ro|rw] dockerid 'command'
# -P 将内部端口映射到本地临时端口
# -ro readonly
# -rw read|write
数据卷容器
# 专门创建一个容器供其它容器共享数据卷
# -v 指定数据目录
# --volumes-from 指定挂载的容器
docker run -it -v /dbdata --name dbdata ubuntu # 创建一个带有数据卷的容器
docker run -it --volumes-from dbdata --name db1 ubuntu # 创建窗口,并从其它容器中加载数据卷
docker run -it --volumes-from dbdata --name db2 ubuntu # 同上
# 做数据卷的容器可以不用运行(未测试)
# 删除容器时,数据卷不会被删除,只有删除最后一个挂载的窗口时使用 docker rm -v 指定同时删除数据卷窗口
数据卷内容备份、移除
可以将数据卷直接打包复制到其它机器上还原
也可以将数据卷中的内容直接打包,复制到其它机器上进行还原
# 备份数据卷中的数据至 tar 文件
docker run --volumes-from dbdata -v $(pwd):/backup --name worker ubuntu 'tar cvf /backup/backup.tar /dbdata'
# 1. 创建一个worker 容器, 挂载 dbdata 中的数据卷, 将当前目录挂载到 /backup
# 2. 容器运行时将 /dbdata 中的内容备份到 /backup/backup.tar
# 将复制出来的数据还原到数据卷的方法
docker run -v /dbdata --name dbdata2 ubuntu '/bin/bash'
docker rum --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar
# 1. 创建需要使用备份好数据的容器 dbdata2
# 2. 创建一个容器挂载 dbdata2 并将 tar 所有目录挂载进来, 运行脚本 将 tar 文件解压缩,即导入完成
端口映射与容器互联
docker run -d -P dockerid 'command' # 随机映射一个端口
docker run -d -p 5000:5000 dockerid # 映射到本地所有地址上
docker run -d -p 127.0.0.1:5000:5000 dockerid # 映射到本地指定地址上
docker run -d -p 127.0.0.1::5000 dockerid # 映射到本地指定地址上的随机端口上
docker run -d -p 127.0.0.1:5000:5000/udp dockerid
docker port container # 查看主机映射端口
容器互联
可以使用 --link 将两个容器进行互联,这样就不用直接暴露端口了
docker run -d -P --name container imagename # 容器命名
docker inspect -f "{{ .Name }}" container # 查看容器名称
# --link 参数使两个容器互联
docker run -d --name dbData imagename # 创建一个容器
docker run -d --name mysqlDb --link dbData:innerData imageid # 链接一个容器,后面为链接的名称 --link container:alias
# 查看链接情况
docker ps
# 在被连接的容器中会有一些环境变量被设置为关联主机相关参数
# 1. 环境变量中 链接别名_* 开头的变量
# 2. 本机 hosts 中有指向主机的记录
使用 Dockerfile
基本语法
# 在线教程 https://yeasy.gitbooks.io/docker_practice/content/image/build.html
# Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
# 基础镜像, 必须是第一条数据。 如果不要操作系统,使用 scratch, 比如 go 开发的程序
FROM debian:jessie
# 维护者
MAINTAINER x@x.com
# 声明镜像的元数据标签,可以多条
LABEL version='1.0'
# 声明镜像内监听端口,好像没什么用,就是声明一下
EXPOSE 22 80 8443
# 指定环境变量, 可以被命令行设置的变量覆盖, 可以多行; <key> <value> 或是 <key>=<value>
ENV PG_MAJOR 9.3
# 挂载数据卷, 指定从本地或是数据卷中挂载目录
VOLUME ["/data"]
# 复制文件到镜像
ADD # 可以是相对 dockerfile 所在目录文件或目录,URL, tar(自动解压), 目标目录可以是绝对目录或是相对于 WORKDIR 目录
ADD *.c /code/
COPY # 添加相对 dockerfile 目录的文件或目录到镜像, 这个会快一些,推荐使用该命令
# COPY 仅仅进行复制,比 ADD 简单,所以建议使用 COPY
# 运行命令, 一说 RUN 在构建中运行
RUN SHELLCOMMAND # 直接在 shell 中运行命令
RUN ["EXECUTABLE", "PARAM1", "PARAM2"] # 相当于 exec 不会启用 shell 环境
# 每 run 一次会创建一次镜像,所以可以使用 && 将多个命令串起来,这样只创建一个镜像
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps
# 容器启动后的运行命令,如果有多条,只有最后一条会被运行, 手工启动时会被命令参数覆盖
# 指定 ENTRYPOINT 后,会作为 ENTRYPOINT 的参数
CMD SHELLCOMMAND
CMD ["EXECUTABLE", "PARAM1", "PARAM2"]
# 容器启动后的运行命令, 如果有多个,只有最后一个起作用
# 可以被手工启动时的 --entrypoit 覆盖
# 如果使用该命令时, CMD 命令变为该命令的参数
ENTRYPOINT SHELLCOMMAND # shell 中调用
ENTRYPOINT ["EXECUTABLE", "PARAM1", "PARAM2"] # exec 中调用
# 指定运行容器时的用户名,并使后续 RUN 指令也使用该用户运行
USER daemon
# 设置当前工作目录,为 run cmd entrypoint 等设置目录
# 可以多次设置 WORKDIR, 如果设置为相对目录,则相对于上次 WORKDIR 后的目录
WORKDIR /path/to/workdir
# 构建参数, 指定后 build 参数中就可以使用 --build-arg name=value 来进行设置
ARG name=value
# 当被其它镜像引用时构建新镜像时的命令。
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
# 系统健康度检测函数,指明什么命令来检测健康度
HEALTHCHECK [OPTIONS] SHELLCOMMAND # 返回是否为 0 表示是否健康
# --interval=30s # 多长时间检测一次
# --timeout=30s # 等待结果超时时间
# --retries=3 # 失败后重试几次
HEALTHCHECK NONE # 表示禁用健康度检测
构造
# 构建指定目录下的 dockerfile 文件
docker build [options] folder # -t 镜像名:tag
#使用 .dockerignore 文件设置忽略文件列
常用操作系统
BusyBox
带有很多常用工具,而只有 2.4 M
Alpine
只有 5M 左右
国内镜像
/etc/apk/repositories # 仓库地址, 将源添加到这个地址即可
清华TUNA镜像源:https://mirror.tuna.tsinghua.edu.cn/alpine/
中科大镜像源:http://mirrors.ustc.edu.cn/alpine/
阿里云镜像源:http://mirrors.aliyun.com/alpine/
比如:
http://mirrors.aliyun.com/alpine/v3.9/main/
http://mirrors.aliyun.com/alpine/v3.9/community/
echo http://mirrors.ustc.edu.cn/alpine/v3.6/main > /etc/apk/repositories
echo http://mirrors.ustc.edu.cn/alpine/v3.6/community >> /etc/apk/repositories
安装
apk add openrc --no-cache
apk del ****
rm -rf /tmp/* /var/cache/apk/* # 清除安装缓存
服务管理
apk add openrc --no-cache # 安装服务管理器
rc-update add *** # 添加一项服务
rc {runlevel} # 切换服务级别 rc boot
rc-status --manual # 手工启动服务
rc-status --crashed # crashed 服务
rc-service --list | grep -i nginx
rc-status # 查看服务状态
rc-status --list # 查看服务级别 boot|nonetwork|default|sysinit|shutdown , 看说明 https://www.cyberciti.biz/faq/how-to-enable-and-start-services-on-alpine-linux/
# docker 中如果出现相关报错
VOLUME ["/sys/fs/cgroup"]
RUN touch /run/openrc/softlevel
rc-update add {service-name} {run-level-name} # 添加一个服务
rc-update delete {service-name} {run-level-name} # 删除一个服务
rc-update show {service-name} {run-level-name} # 显示一个服务
rc-service {service-name} start # 启动服务
/etc/init.d/{service-name} start # 启动服务
rc-service {service-name} stop
/etc/init.d/{service-name} stop
rc-service {service-name} restart
/etc/init.d/{service-name} restart
启用 ssh
apk add openssh
rc-update add sshd # 添加到服务
rc-status # 看状态
/etc/init.d/sshd start # 启动ssh 服务
docker 会退出
docker run -d --name myalpine alpine tail -f /dev/null # 这样不会直接退出
其它
Debian/Ubuntu
CentOS/Fedora
DockerFile
我的一个例子,不太成功
# 构建 docker build -t sshd:mydemoex .
# 运行 docker run -d --name myalpine alpine tail -f /dev/null
FROM alpine
LABEL Author=JXU VERSION=1.0
# 挂载目录,不然启动服务会报错
VOLUME ["/sys/fs/cgroup"]
# 添加国内镜像源
RUN echo http://mirrors.ustc.edu.cn/alpine/v3.6/main > /etc/apk/repositories
RUN echo http://mirrors.ustc.edu.cn/alpine/v3.6/community >> /etc/apk/repositories
# 安装服务管理器
RUN apk add openrc --no-cache
# 安装 openssh
RUN apk add openssh --no-cache
# 删除安装缓存
RUN rm -rf /tmp/* /var/cache/apk/*
# 加入服务, 好像加入服务就直接启动了
RUN rc-update add sshd
# 显示服务状态
RUN rc-status
# 不然会报错
RUN touch /run/openrc/softlevel
# 复制 key, 服务启动的时候会自动生成 key
# RUN mkdir -p /etc/ssh
# COPY ssh /etc/ssh
# 声明 22 端口开放
EXPOSE 22
# 启动服务
# RUN /etc/init.d/sshd start
CMD rc-service sshd restart
DockerFile (更新)
Dockerfile
# 创建
# docker build -t counter-image:v3 .
# docker attach --sig-proxy=false core-counter # 挂入容器,并在退出时不中止容器
# 环境变量,使用 ${DOCKER_USERNAME} 引用,可以使用 --build-arg 参数重新定义
# 如果在 From 前定义,则只能使用在 FROM 语句中,如要在 FROM 后使用,必须在 FROM 后定义
ARG DOCKER_USERNAME=library
# 指定基础镜像,必须为第一行 ; FROM scratch 指定空镜像,如果像 go 之类的自带运行库,
# FROM mcr.microsoft.com/dotnet/aspnet
FROM mcr.microsoft.com/dotnet/runtime
# 为镜像指定元数据, 常用 key https://github.com/opencontainers/image-spec/blob/master/annotations.md
LABEL VERSION=1.2
LABEL key="value"
# 设置环境变量,可以使用 %VERSION 引用变量内容 ,只能在 FROM 后定义
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
# 切换当前用户身份, 用户必须提前创建好
USER <用户名>[:<用户组>]
# 手册建议使用 gosu 命令使用不同身份运行, 而不是 su 或 sudo
# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]
# 切换默认使用的 shell
SHELL ["pwsh","-command"]
# 指定工作目录,会被设置为当前目录,
# 如果是相对路径,则为上一次使用 WORKDIR 时指定的相济路径
# 不要使用 cd 切换路径,要用 WORKDIR 进行指定. RUN pwd 得到当前路径
WORKDIR /App
# run 构建容器时使用的命令,run 一次生成一层
# 安装 powershell, ln -s 创建链接,这样可以直接运行, 设置可执行
# RUN dotnet tool install --global PowerShell \
# && ln -s /root/.dotnet/tools/pwsh /usr/bin/pwsh \
# && chmod 755 /root/.dotnet/tools/pwsh
# 使用 run 例行一组命令,一行命令生成一层镜像,建议使用 \ && 将多个命令串成一层
RUN mkdir Datas
# 匿名挂载目录。某些保存的数据不希望保存于容器中,可以使用匿名挂载。
# 如果在创建容器时忘了指定挂载,那么会在物理机目录创建目录进行挂载(不同的操作系统会有不同)
VOLUME ["/data1","/data2"]
# 声明使用端口,仅声明而已,不会自动映射。帮助使用者查看 ,从而是使用 -p <宿主端口>:<容器端口> 参数进行映射
# 如果使用 -P 参数物理机会使用随机端口对 EXPOSE 端口进行映射
EXPOSE 3322 6643
# 复制文件,可以使用通配符,也可以指定用户身份 COPY --chown=55:mygroup files* /mydir/
# copy 可以使用通配符, 规则 https://golang.org/pkg/path/filepath/#Match
# add 也可以复制文件, 下载文件复制,自动解压缩 gzip, bzip2 以及 xz,除了解压缩文件,其它不推荐使用
COPY bin/release/net5.0/publish/ App/
# ONBUILD 在子镜像中才会被执行。比如复制各个不同项目中的 package.json 文件,而基础镜像构建时不能进行复制
# 有点像父类,定义好执行顺序,但只有子类中执行
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
# 指定窗口是否正常工作的命令,默认以主进程是否退出判断,如果定义 HEALTHCHECK, 则间隔调用 HEALTHCHECK 来判断容器是否工作正常
# 命令返回 0 成功, 1 工作不正常。 容器状态 starting|healthy|unhealthy
HEALTHCHECK [选项] CMD <命令>
# 容器运行时,启动主程序命令,与 CMD 相同,如果都存在的话, cmd 的内容会附加在 ENTRYPOINT 后,效果如 <ENTRYPOINT> "<CMD>"
# 比如 docker run myip -i ,如果使用 cmd ,则 cmd 参数变为 -i; 如果使用 ENTRYPOINT ,则 -i 参数附加至原参数后面
# 比如使用 ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ] ,可以使得 docker 参数附加一些参数,丰富功能
# 也可以使用 ENTRYPOINT 调用一个初始化脚本,使用 cmd 调用主程序命令
# 可以在容器启动参数中使用 --entrypoint 指定
ENTRYPOINT ["dotnet", "NetCore.Docker.dll"]
# 容器运行的时候,运行的主程序,如果主程序退出,由容器退出
# ``` docker run -it ubuntu cat /etc/os-release ``` 可以在运行容器时使用参数指定 cmd
# 注意,不要调用服务或后台程序,因为服务或后台程序在后台运行,会导致前台没有程序运行。于是容器退出了
CMD ["nginx", "-g", "daemon off;"]
Dockerfile 多阶段构建
https://yeasy.gitbook.io/docker_practice/image/multistage-builds
在某此场景下,可能需要在多个镜像中生成文件。并复制到一个最终镜像中 比如 go 编译可能会生成很多文件,生成的镜像会很大,而且源码在里面。使用多个 Dockerfile 文件生成一组镜像。可以在第一个镜像中进行编译,然后使用scp
复制出编译后的文件,然后删除原来的镜像文件。这样可以省下几百兆的空间多阶段构建可以前多个编译的 Dockerfile 合并在一起
使用 from 进行每个 Dockerfile 的区分 ,并使用 as 命名,然后在 scp 中指定从哪个镜像中复制文件
## 使用 go 镜像,并命名为 builder
FROM golang:1.9-alpine as builder
## 进行 go 编译
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go get -d -v github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
## 构建第二个镜像
FROM alpine:latest as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
## 从第一个镜像中复制文件
COPY --from=builder /go/src/github.com/go/helloworld/app .
CMD ["./app"]
save / load
docker save alpine -o filename
导出镜像文件,复制并导入docker load -i alpine-latest.tar.gz
导出为压缩文件$ docker save alpine | gzip > alpine-latest.tar.gz
不建议使用,建议使用 Docker Registry
docker-compose.yml
将一组窗口组织成一个项目,一起运行
# 参数 https://yeasy.gitbook.io/docker_practice/compose/commands
docker-compose --version # 查看版本
build # 创建项目
docker-compose up # 尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。
docker-compose up -d # 后台执行
down # 停止并删除容器
start/stop/restart/kill <# 强制停止容器 #>
docker-compose scale web=3 db=2 # 设置服务的数据。如果多了自动启动,如果少了自动停止
logs # 看日志
文件样本
version: '3'
services:
counter:
build: . # 自动构建
environment: # 环境变量
- LANG=C.UTF-8
depends_on:
- mariadb # 指定依赖关系,决定启动次序
dns:114.114.114.114 # 指定 dns 列表
tmpfs: /run # 挂载入 tmpfs 文件(内存文件,所以会很快,但会丢)
expose: # 在项目内的容器中暴露端口,不映射入宿主机
- "3000"
extra_hosts: # 修改 hosts 文件
- "googledns:8.8.8.8"
healthcheck: # 健康校验命令
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
network_mode:"bridge" # 网络模式,同 docker --network
networks: “x-network” # 连接网络
volumes: # 挂载目录
- /var/lib/mysql # 物理目录
- cache/:/tmp/cache # 数据卷
- ~/configs:/etc/configs/:ro # 只读方式载入
mariadb:
image: "mariadb" # 使用现有镜像
restart: always
volumes:
- xdata:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: xdatabase
adminer:
image: adminer
restart: always
volumes:
# - ./adminer/plugins-enabled/login-password-less.php:/var/www/html/plugins-enabled/login-password-less.php
environment:
# ADMINER_PLUGINS: "login-password-less"
ports:
- 8080:8080 # 宿主机映射端口
volumes:
xdata:
networks:
x-network:
数据卷
docker volume create my-vol # 创建数据卷
docker volume ls # 列出数据卷
docker volume inspect my-vol # 查看数据卷
docker volume rm my-vol ## 删除数据卷
docker volume prune # 删除无主数据卷
-v my-vol:/usr/share/nginx/html # 挂载数据卷
--mount source=my-vol,target=/usr/share/nginx/html # 挂载数据卷
-v /src/webapp:/usr/share/nginx/html # 挂载目录
--mount type=bind,source=/src/webapp,target=/usr/share/nginx/html # 挂载目录
-v $HOME/.bash_history:/root/.bash_history # 挂载文件
网络
-P # 映射随机端口至内部声明的端口 $ docker run -d -P nginx:alpine
-p # 指定ip:port 进行映射
# 格式 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
-p 80:80 # 所有地址
-p 127.0.0.1:80:80 # 指定地址、端口
-p 127.0.0.1::80 # 指定地址、随机端口
-p 127.0.0.1:80:80/udp # 指定协议
docker port CONTAINER [PRIVATE_PORT[/PROTO]] # 查看容器映射情况
容器互访
创建一个网络,然后将窗口都 link 至此网络
docker network create -d bridge my-net # 创建网线 my-net -d 网络类型
docker run -it --rm --name busybox1 --network my-net busybox sh # 将容器联系到 my-net
# 多个容器之间可以使用名称进行互访
DNS
默认容器映射主机的 /etc/resolv.conf 文件(linux机器上)
可配置 /etc/docker/daemon.json 指定容器的 dns
-h HOSTNAME 或者 --hostname=HOSTNAME 设定容器的主机名
--dns=IP_ADDRESS 添加 DNS 服务器到容器的 /etc/resolv.conf 中
--dns-search=DOMAIN 设定容器的搜索域
高级网络设置
默认主机上创建 bridge0 网桥,所有虚拟机默认都连接在此网桥上。所以所有窗口默认可以互联。可以在 /etc/docker/daemon.json --icc=false 关闭容器互联
端口映射
linux 下, -p 参数在 iptables 中创建一些端口转发,在外部访问指定端口时转发至指定容器 ip 的端口。容器内部访问外部时使用 NAT
配置自定义 docker0 网桥
--bip=CIDR IP 地址加掩码格式,例如 192.168.1.5/24 --mtu=BYTES 覆盖默认的 Docker mtu 配置
自定义网桥 https://yeasy.gitbook.io/docker_practice/advanced_network/bridge
网络参数
docker run
的时候通过 --net
参数来指定容器的网络配置,有4个可选值:
--net=bridge
这个是默认值,连接到默认的网桥。--net=host
告诉 Docker 不要将容器网络放到隔离的命名空间中,即不要容器化容器内的网络。此时容器使用本地主机的网络,它拥有完全的本地主机接口访问权限。容器进程可以跟主机其它 root 进程一样可以打开低范围的端口,可以访问本地网络服务比如 D-bus,还可以让容器做一些影响整个主机系统的事情,比如重启主机。因此使用这个选项的时候要非常小心。如果进一步的使用--privileged=true
,容器会被允许直接配置主机的网络堆栈。--net=container:NAME_or_ID
让 Docker 将新建容器的进程放到一个已存在容器的网络栈中,新容器进程有自己的文件系统、进程列表和资源限制,但会和已存在的容器共享 IP 地址和端口等网络资源,两者进程可以直接通过lo
环回接口通信。--net=none
让 Docker 将新容器放到隔离的网络栈中,但是不进行网络配置。之后,用户可以自己进行配置。
网络配置
--net=none
后,可以自行配置网络, https://yeasy.gitbook.io/docker_practice/underly/network
其它说明
host
与主机共享 ip , 能在低段的端口上进行监听,但不能与已有端口冲突
macvlan
这是 linux 功能,可以让物理网络在局域网中模拟出多个 mac 以绑定不同的 ip。 检测系统是否支持 macvlan lsmod | grep macvlan
Networking using a macvlan network | Docker Documentation --- 使用 macvlan 网络进行联网 |码头工人文档
容器可ping 外网 / 给容器局域网 ip · PHP/Python/前端/Linux 等等 学习笔记 · 看云 (kancloud.cn)
群晖docker开启局域网桥接,获取独立IP - 周杰个人博客 (zhoujie218.top)
(20条消息) Docker容器获取局域网ip(使用macvlan)_docker内网ip_章鱼鱼鱼鱼鱼的博客-CSDN博客
# 版权声明:本文为CSDN博主「章鱼鱼鱼鱼鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
# 原文链接:https://blog.csdn.net/weixin_39827918/article/details/124184288
#创建网络
docker network create -d macvlan \
--gateway=192.168.188.1 \
--subnet=192.168.188.0/24 \
-o parent=enp0s9\
macvlan-net-1
#查看网卡
docker network ls
#删除网卡
docker network rm macvlan-net-1
# 创建一个测试容器
docker run --rm -dit \
--network macvlan-net-1 \
--name my-macvlan-alpine \
--ip=192.168.188.11 \
alpine:latest \
ash
# 测试ip获取是否正常,也可以在宿主机去ping容器的ip
docker exec my-macvlan-alpine ip addr show eth0
docker exec my-macvlan-alpine ip route
# 测试完成之后,删除容器(因为启动的时候时候添加了--rm参数,只需要停止容器就行了)
docker container stop my-macvlan-alpine
其它设置局域网 ip 方法
这里还有一个使用 pipework
给容器在局域网中分配 ip 的方法,有空再看
docker使用pipework给容器分配路由器下的局域网ip - tangshow - 博客园 (cnblogs.com)
privileged 说明
在容器技术中,"privileged"(特权)是一个参数,用于指定容器是否拥有主机操作系统的完全权限。当容器被设置为"privileged"时,它将具有主机操作系统上的超级用户权限,这意味着容器内的进程可以执行一些普通容器无法执行的操作。
当容器设置为"privileged"时,它可以执行以下操作:
访问主机的设备节点:普通容器无法直接访问主机上的设备文件,而"privileged"容器可以直接访问这些设备节点,例如/dev/sda(硬盘)或/dev/tty1(终端)。
修改主机的网络配置:"privileged"容器可以修改主机的网络配置,包括网络接口、IP地址和路由规则等。
安装和加载内核模块:普通容器通常无法安装和加载内核模块,但"privileged"容器可以执行这些操作。这对于一些特定的应用和服务可能是必需的。
请注意,将容器设置为"privileged"可能存在一些安全风险,因为容器内的进程可以对主机进行更深入的操作,这可能导致意外的结果或潜在的安全漏洞。因此,在使用"privileged"参数时应谨慎,并确保只有在必要情况下才使用该参数。