Skip to content

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"参数时应谨慎,并确保只有在必要情况下才使用该参数。