Skip to main content

Docker Note & Other Virtual Develop Environment

1. 常用指令

docker -v # 版本
docker version # client, server 版本

# 这种方式可能不准
# 推荐 ps -ef | grep <container id> 找到 找到容器对应的进程, 然后通过 top <pid > 查看实际内存, cpu 占用
docker stats # 监视容器 CPU 内存使用率


########## 更新 容器启动配置

# 容器随 docker 自动启动
docker update <container> --restart=always
docker update --restart=no <container-id>


######################### prune 命令 (清理)


docker container prune
docker images prune
docker volume prune
docker network prune
docker system prune # 删除以上所有空闲



####################### image 相关



# 拉取 docker pull
docker pull <image>:<tag>

# 查看镜像
docker images
docker image ls [image_name][:tag] #查看所有/指定的镜像
docker image ls -a #显示包括中间层镜像在内的所有镜像
# 自定义结果镜像查询
docker image ls --format "{{.ID}}: {{.Repository}}" # 列出镜像结果,并且只包含镜像ID和仓库名
docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}" #带标题, 但是自己定义列
docker image ls -q # 仅仅显示image的id

# 删除镜像
docker image rm <xxx> 删除指定镜像(可以是id, name....)
docker image rm $(docker image ls -q redis) 和其他命令配合删除
docker image rm $(docker image ls -q -f before=mongo:3.2) 配合
docker image prune 批量清理临时镜像文件
# or
docker rmi image_name


# 过滤镜像
# //使用grep和awk过滤得到镜像ID。(匹配镜像名称为image-b,标签为除了v1d0-7之外的其他行,获取第三列的ID)
docker images | grep image-b | grep -v "v1d0-7" | awk '{print $3}'

# // 使用docker rmi删除过滤得到的镜像
docker rmi $(docker images | grep image-b | grep -v "v1d0-7" | awk '{print $3}')

# // 删除5天前的镜像
docker rmi $(docker images | grep "<none>"| grep "5 days ago" | awk '{print $3}')



# ###########################docker machine的命令

docker-machine ls #查看运行docker server的虚拟机
docker-machine create --driver=virtualbox default #创建一个虚拟机用于运行docker
docker-machine env [default] #查看环境变量(机器ip在里面可以看到), 默认查看名为default的机器



############################ docker ps



docker ps | less -S #查看所有运行的container, 每条记录只占用一行显示
docker ps -q # 仅仅显示 id 列, 配合 filter 清理容器时非常好用
docker ps -a # 查看所有(包括停止的container)
docker ps -s # 显示容器文件大小 (如 211MB (virtual 1.83GB), 一个是容器真实增加的大小,一个是整个容器的虚拟大小, 容器虚拟大小 = 容器真实增加大小 + 容器镜像大小)


###################### 过滤 filter


# [-f] 过滤, 等同 --filter
# [--filter "until=24h"] 删除24h以前的; [--filter "label!=keep"]只删除标签不为keep的
#
# - 选项后跟的都是键值对 key=value (可不带引号),如果有多个过滤条件,就多次使用 filter 选项
# - 相同条件之间的关系是或,不同条件之间的关系是与
# - id 和 name,支持正则表达式,使用起来非常灵活。
#
docker ps --filter id=a1b2c3 --filter name=bingohuang
docker ps --filter name=bingo --filter name=huang --filter status=running # 以上过滤条件会找出 name 包含 bingo 或 huang 并且 status 为 running 的容器。

docker ps --filter name=^/bingohuang$ # 精确匹配 name 为 bingohuang 的容器。注意,容器实际名称,开头是有一个正斜线 / ,可用 docker inspect 一看便知
docker ps --filter name=.*bingohuang.* # 匹配 name 包含 bingohuang 的容器,和 --filter name=bingohuang 一个效果。

# 清理名称包含 bingohuang,且状态为 exited 或 dead 的容器
docker rm $(docker ps -q --filter name=.*bingohuang.* --filter status=exited --filter status=dead 2>/dev/null)





# ########################## Format 格式化显示

# 基于 Go template(https://golang.org/pkg/html/template/)语法

docker ps --format "{{.ID}}: {{.Command}}"

# 如果想带上表格列头,需要再 template 中加上 table 指令:
docker ps --format "table {{.ID}}\t{{.Command}}"

# 支持的其他占位符
# ID, Names, Image, Command, CreatedAt, RuningFor (存活了多久了), Ports, Status, Size, Mounts (volumns 的名字), Networks
# Labels (所有贴上的标签名字), Label (指定标签名的标签值, 格式: '{{.Label "xxx_label_value"}}'),






# ########################## 查看元信息


docker system df #查看镜像、容器、数据卷所占用的空间, 运行状况(通过表格的形式)(常用)
docker info # docker的基本信息(多少container, 多少image, 各个container状况 ,docker root dir)
docker network ls #查看docker网络
docker build -t <nginx:v3> <.> #根据Dockerfile构建镜像
# -t 指定镜像名称
# . 表示构建上下文 context 为当前目录
docker top <id, container_name>



######################## docker run/exec



docker run <IMAGE> env 查看镜像支持的环境变量
docker run --name webserver -d -p <host_port>:<container_port> nginx 指定container名字运行, 端口映射, -p <宿主端口>:<容器端口>
* `-d` 后台运行(不加, 则前台运行, 日志输出到前台, ctrl+c不会终止container, 但会终止日志输出)
* `-p` 指定端口映射
* `--name` 指定container名字
docker run <image_name>:<tag> <cmd> 运行指定镜像启动容器, 并执行一条指令
docker run image_name apt-get install -y app_name 运行镜像, 在容器中安装新的程序, 完成后退出
* -y : 不要进入交互模式
docker exec -itd <container_name/id> <cmd> 进入容器, 执行命令后退出
* -d : 后台运行, 去掉后, cmd执行完成后会呆在container内部
docker run -it --rm <image_name>:<tag> bash 运行并进入指定容器内部, 打开bash
* -i:交互式操作
* -t 终端
* --rm:容器退出后随之将其删除
docker exec -it webserver bash 进入某个容器内, 并执行一条命令, 以`exit` 退出
* 不推荐 docker attach <xxx>, exit退出, container也随之终止

docker cp <container_name/id>:<container_path> <host_path> 复制文件, 从container到本机




# docker container

docker container ls 或 docker ps 运行着的容器
* -q : 只返回容器id
docker container start <stoped_container> 启动终止状态的容器
docker container restart <xxx> 重启
docker-compose restart <service_name> 必须在上下文目录, 是service name, 而非container_name

docker stop <xxx> 停止容器
docker container stop <xxx>
docker start/restart <xxx> 启动
docker kill $(docker ps -a -q) 停止所有正在运行的容器
docker rm <xxx> 删除容器
docker rm -fv <容器ID> 停止、删除容器,并清除数据
docker container rm <xxx>
docker container prune 删除所有的停止的容器



# docker inspect 容器详细信息/容器配置(json格式)

docker inspect <container> 容器详细信息(json格式, 大量信息 包括ip)
docker inspect -f='{{.Name}}' $(sudo docker ps -a -q) 查看所有容器name (可以不带等号)
docker inspect --format '{{ .NetworkSettings.IPAddress }}' <container> 获取指定容器ip
* 用 $(sudo docker ps -a -q) 替换<container> 获取所有container的ip
docker inspect <id,container_name> | grep "IPAddress" 查看ip


# docker log 查看日志

docker logs <xxx>
docker container logs <xxx> # 查看指定container的日志(全部)
docker logs -f -t --since="2017-05-31" --tail=10 <container> # 追踪日志文件
#* -f : 查看实时日志
#* -t : 带上时间戳
#* --since : 此参数指定了输出日志开始日期
#* --tail=10 : 后10行




# docker commit 提交 docker history 提交历史


docker commit \ # 提交修改后的容器形成新的镜像
--author "Tao Wang <twang2218@gmail.com>" \ # 修改人
--message "修改了默认网页" \ # 修改说明
webserver \ # 修改的container
nginx:v2 # 仓库名:标签名

docker history nginx:v2 查看指定镜像的历史提交记录


docker rename <old容器名> <new容器名> 修改容器名称 重命名



# 网络相关命令

docker network create -d <network-type> --subnet=172.18.0.0/16 <my-network> # 创建网络
* -d 网络类型, 默认 bridge
* --subnet 指定网段
docker network rm my-net # 删除
docker network connect my-net my-nginx # 运行着的 container 加入网络
docker network disconnect my-net my-nginx # 断开网络

docker run -itd --name <test1> --network bridge --ip 172.17.0.10 <centos:latest> </bin/bash >
* 用 --network 参数,可以指定网络类型
* --ip 172.17.0.10指定ip

2. 开启远程连接

用于开发环境, 不安全

原生 docker: https://blog.csdn.net/yjt520557/article/details/99715097 或者 https://www.cnblogs.com/weiyiming007/p/10168733.html

wsl 下的 docker: https://www.jianshu.com/p/c7bc8fa1ee5f 修改 /lib/systemd/system/docker.service

3. shipyard ui管理界面

web 管理控制台, 管理容器, 默认用户名密码 admin/shipyard

同类: docker-ui

4. 介绍

4.1. 基本架构

docker client和docker server: docker是c/s架构, client和server可以在同一台机器也可以不再同一台机器

Repository: 具体某个镜像的仓库, 只存储某一个项目的镜像(比如Tomcat下面有很多个版本(用tag表示)的镜像,它们共同组成了Tomcat的Repository。)

Registry: 所有的镜像, 官方为Docker Hub, 也可以部署私有registry(Registry上有很多的Repository,Redis、Tomcat、MySQL等等Repository组成了Registry。)

4.2. 使用场景-得到什么好处

传统虚拟机在宿主操作系统上先虚拟出一套硬件, 硬件上再运行操作系统,在该系统上再运行所需应用进程;

  • 而 docker 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便

  • Docker给应用提供了一个从开发到上线 统一的环境

  • 数据库的数据与数据库程序本身分离开:用一个container A作为数据存储,然后另一个container B运行数据库。当你想升级数据库时,用新的container C替换掉container B即可 . 方便的迁移应用

  • 使用docker build完成单元测试(创建隔离的环境进行测试, 如用jenkins ci这样的ci工具启动一个用于测试的container)

  • 尤其适合微服务

    • 后端应用是微服务化的,有很多个独立的 java 应用,开发、测试和线上多套环境。

    • docker 之前的部署方案: 打 war 包,上传到 ecs,重启tomcat; docker 部署方案:编译机器上编译 jar 包,打出 docker-img(把jar包放进去), push 到 docker registry,线上机器pull docker-img, 然后 docker stop, docker run。

4.3. 容器实现的原理

容器: 是一个 视图隔离, 资源可限制, 独立文件系统的进程集合

Linux 提供三大技术

  • namespace 实现资源视图隔离, 如独立的主机名, 可以限制只能看到部分其他进程
  • cgroup 控制资源使用率, 如可控制cpu, 内存使用率
  • chroot 创建独立的文件系统 , 如吧某个目录设置为子系统的根目录

5. 安装

https://www.runoob.com/docker/ubuntu-docker-install.html

安装docker前检查环境是否匹配:

  • uname -a 查看内核版本
  • 升级内核版本: 先通过sudo apt-get update 更新atp-get, 然后通过sudo atp-get install <指定linux内核>安装, 最后通过sudo update-grub加载新内核. 还需要重启sudo reboot
  • 检查是否以device mapper作为存储驱动 grep device-mapper /proc/devices

安装参考: https://docs.docker.com/engine/install/centos/#install-using-the-convenience-script

最简单的 curl -fsSL https://get.docker.com -o get-docker.sh && sudo sh get-docker.sh

sudo systemctl enable docker
sudo systemctl start docker
docker run hello-world

5.1. ubuntu 22 install docker

# According to the official doc , these two comand is the easiest way 
# but I occurred a issue : lsb_release can not be found
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh ./get-docker.sh --dry-run

# after check, this can resolve the issue above
apt-get update && apt-get install -y lsb-release && apt-get clean all

# It's fucking disgusting
# finally I find this link : https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-22-04
# https://cloudcone.com/docs/article/how-to-install-docker-on-ubuntu-22-04-20-04/
# Every thing is ok!

5.2. 使用国内镜像

ailiyun加速https://7lwsinal.mirror.aliyuncs.com

临时使用 docker run hello-world --registry-mirror=https://docker.mirrors.ustc.edu.cn

持久使用, 修改 or 新增 vim /etc/docker/daemon.json,

{ 

"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]

}


{
"registry-mirrors": [
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"
]
}

systemctl daemon-reload
systemctl restart docker

https://www.cnblogs.com/nihaorz/p/12131873.html

6. 镜像 image

可以通过Dockerfile构建image,也可以将image运行,使其变成container

https://juejin.cn/post/6850418112237502472 奇怪异常

6.1. packaging image 导入导出

docker save -o local-image.tar image:tag
docker save -o local-images.tar image1:tag1 image2:tag2

# save all local images
docker save -o all-local-images-in-namespace.tar $(docker image ls --format '{{json .}}' | jq -r 'select(.Repository!="<none>") | if (.Tag=="<none>") then .Repository else (.Repository+":"+.Tag) end')

docker load < local-images.tar

6.2. 版本命名意义

空镜像scratch

工具镜像busybox
本身只有 1.16M,非常便于构建小镜像

slim Represents the minimum image size

是完整镜像的配对版本。这个镜像通常只安装运行特定工具所需的最小包,

相比于 alpine, 带有一些工具

在使用这个镜像时,一定要进行彻底的测试!如果遇到无法解释的错误,请尝试切换到完整的镜像

Alphine

高度精简又包含了基本工具的轻量级 Linux 发行版,基础镜像只有 4.41M, 但是太精简,可能出现一些奇怪的环境问题, but on the other hands it also cannot work very well if you wanna use some basic tools

using apk, /bin/sh

使用了一个更小的musl lib代替glibc。如果您的应用程序有特定的libc需求,您可能会遇到问题。

buster 适用与 debian 10, apt, /bin/bash
stretch 适用于 debian 9
jessie 适用于 debian 8
bullseye、bookworm 开发版本,处于未稳定状态

6.3. 镜像实现原理

docker 镜像都是层层叠加,每一层都可能被不同的镜像共享 (在Dockerfile中每一步都会产生一层layer,每一步的结果产出变成文件)

Docker 使用 Union FS 将这些不同的层结合到一个镜像中去。

通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。

6.4. 获取镜像

通过 docker pull --help 查询可知格式为:

docker pull [选项] [Docker Registry地址 [:端口号] / ] 仓库名[:标签]

  • Docker 镜像仓库地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。

  • 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。

例如:

docker pull ubuntu:16.04

上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。而镜像名称是 ubuntu:16.04,因此将会获取官方镜像 library/ubuntu 仓库中标签为 16.04 的镜像。

从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性

6.5. 运行镜像

# 以上面的 ubuntu:16.04 为例,如果我们打算启动里面的 bash 并且进行交互式操作的话,可以执行下面的命令
$ docker run -it --rm \
ubuntu:16.04 \
bash

6.6. 列出镜像

  • docker image ls [xxx:xxx] 列出所有/指定下载的顶层镜像

  • docker image ls -a 列出所有包括中间层镜像

  • docker images 也可查看

6.7. 删除本地镜像

docker image rm [选项] <镜像1> [<镜像2> ...]

7. 制作镜像的方法

7.1. work with golang

create a main.go, then create dockerfile in the same directory:

FROM golang:1.16-buster AS builder
RUN mkdir /src
ADD . /src
WORKDIR /src

RUN go env -w GO111MODULE=auto
RUN go build -o main .

FROM gcr.io/distroless/base-debian10

WORKDIR /

COPY --from=builder /src/main /main
EXPOSE 3000
ENTRYPOINT ["/main"]

generate the image:

docker build . -t xiaoyureed/hellok8s:v1

# when build, this error occurred:
# 出现“failed to solve with frontend dockerfile.v0: failed to create LLB definition: failed to copy: httpReadSeeker: failed open: failed to do request:”
# Just need to setup the docker desktop config:
# "features": { buildkit: false}

# start
docker run -p 3000:3000 --name hellok8s -d xiaoyureed/hellok8s:v1

7.2. working with frontend

FROM node:latest AS builder
WORKDIR /usr/src/app
COPY package.json yarn.lock ./
RUN yarn
COPY . ./
RUN yarn build

FROM nginx
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]

7.3. 最佳实践 优化体积

https://github.com/phusion/baseimage-docker 体积最小的 Linux

https://www.infoq.cn/article/3-simple-tricks-for-smaller-docker-images https://segmentfault.com/a/1190000017858358 https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ https://fuckcloudnative.io/posts/docker-images-part1-reducing-image-size/ TODO

# 编译
FROM node:latest AS builder
maintainer xiaoyu <775000738@qq.com>
WORKDIR /usr/src/app
#
COPY package.json yarn.lock ./
RUN yarn
# 复制当前路径(通过 docker build -t xxx . 这个 点 指定)下所有文件到 目录 ${workdir}
COPY . ./
RUN yarn build

# 实际镜像
FROM nginx
COPY --from=builder /usr/src/app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]



7.4. 通过 dockerfile 来 build 镜像

docker build [-f MyDockerfile] -t xiaoyureed/myImage:v1 .

7.5. commit容器成为镜像(不推荐)

将自定义的容器保存为镜像, 一般不用commit制作镜像, 因为会有大量的无关内容被添加进来,如果不小心地清理,将会导致镜像极为臃肿。)

docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]

镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层

先看一个例子: 定制一个 web server

# 用 nginx 镜像启动一个容器,命名为 webserver,并且映射了 80 端口,这样我们可以用浏览器去访问这个 nginx 服务器
$ docker run --name webserver -d -p 80:80 nginx

# 进入container内部, 打开bash
docker exec -it webserver bash
# 用 <h1>Hello, Docker!</h1> 覆盖了 /usr/share/nginx/html/index.html 的内容
echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

浏览器输入虚拟机的ip地址即可访问;(docker-machine env可以查看虚拟机ip)

现在希望commit这个新的镜像

# 先退出container
exit

# 看下图
docker commit --author "xy<775000738@qq.com>" --message "修改 index.html" webserver nginx:v2

# 启动第二个container, 浏览器访问81端口;
# 至此, 手动操作给旧的镜像添加了新的一层,形成新的镜像
docker run --name test_1 -d -p 81:80 nginx:v2

docker history nginx:v2 查看某个Repository的历史提交记录

7.6. 从压缩包导入

格式:docker import [选项] <文件>|<URL>|- [<仓库名>[:<标签>]]

eg:

# 这条命令自动下载了 ubuntu-14.04-x86_64-minimal.tar.gz 文件,并且作为根文件系统展开导入,并保存为镜像 openvz/ubuntu:14.04
$ docker import \
http://download.openvz.org/template/precreated/ubuntu-14.04-x86_64-minimal.tar.gz \
openvz/ubuntu:14.04

7.7. docker save 和 docker load

Docker 还提供了 docker load 和 docker save 命令,用以将镜像保存为一个 tar 文件,然后传输到另一个位置上,再加载进来。这是在没有 Docker Registry 时的做法,现在已经不推荐,镜像迁移应该直接使用 Docker Registry,无论是直接使用 Docker Hub 还是使用内网私有 Registry

docker save alpine | gzip > alpine-latest.tar.gz 保存之后复制到其他机器, docker load -i alpine-latest.tar.gz加载镜像

docker save <镜像名> | bzip2 | pv | ssh <用户名>@<主机名> 'cat | docker load'从一个机器将镜像迁移到另一个机器,并且带进度条的功能

7.8. 构建多平台镜像

https://zhuanlan.zhihu.com/p/639663364

8. Dockerfile

通过dockerfile来build定制镜像

是 一种构建image的 文件DSL

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

8.1. from

`FROM`指令: 指定基础镜像; 一个 DockerfileFROM 是必备的指令,并且必须是第一条指令。

Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像. 如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。

可以 from xxx as builder, 后面可以在 scratch 中引用 ${builder} 以缩小 image 体积

编写完毕, 可以键入构建命令: docker build [选项] < 上下文路径/URL/- >

# 在dockerfile所在的目录运行, -t表示指定最终镜像名称, 这里的[.]表示上下文路径
docker build -t nginx:v3 .

8.2. 镜像的构建上下文context

docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?

docker build -t nginx:v3 . 这里的[.]不是命令当前所在的目录, 而是指定docker context路径, Dockerfile中的路径都是相对于这个contxt路径来说的

一般应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。

在默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile;实际上 Dockerfile 的文件名并不要求必须为 Dockerfile,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php 参数指定某个文件作为 Dockerfile。

docker build还支持从git repo中拉取文件构建docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14这行命令指定了构建所需的 Git repo,并且指定默认的 master 分支,构建目录为 /8.14/,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建

还支持docker build http://server/context.tar.gz从压缩包构建

8.2.1. dockerignore

similar To gitignore

8.3. COPY 和 ADD

复制文件

和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用:

COPY <source1>[source2]... <target>

<源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则;

<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定), 如果不是一个文件,则必须使用/结束

若 source 是文件, target 是 / 结尾, 则 docker 会把 target 视为目录, source will be copied to the dir, if target dir missing, will auto create
若 source 是文件, target 不是 / 结尾,则 docker 会把 target 视为文件,
if the target missing, it will be auto created (equal to source file)
if the target exist, it will be override with the source file content,
if the source is a folder and the sources only contains one folder(文件夹本身会被复制, 内部的文件也会被复制), and the target missing(无论是不是 / 结尾都会视为文件夹), the target will be auto created
if the source is a folder and the sources contains more than one folder (文件夹本身不会被复制, 仅仅内部文件复制)



eg:
COPY package.json /usr/src/app/
COPY hom* /mydir/
COPY hom?.txt /mydir/




add命令: 更高级的复制命令, 类似 copy, 还支持从 URL 复制文件, 还支持复制本地tar文件并自动解压缩

8.4. WORKDIR 指定工作目录

使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),就是将当前执行命令的目录定义为确定值, 以后各层的当前目录就被改为指定的目录

格式为 WORKDIR <absolutePath/relativePath>。(如该目录不存在,WORKDIR 会帮你建立目录, relative path: 它将相对于前一个 WORKDIR 指令的路径, 为了避免出错,推荐WORKDIR指令中只使用绝对路径)

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
# 输出将会是 /a/b/c

WORKDIR 指令可以使用前面 ENV 设置的环境变量

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

8.5. run cmd 和 entrypoint 容器启动命令

8.5.1. 设置时区

ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo Asia/Shanghai > /etc/timezone


# 将时区设置为东八区并安装bash
RUN echo "https://mirrors.aliyun.com/alpine/v3.8/main/" > /etc/apk/repositories \
&& echo "https://mirrors.aliyun.com/alpine/v3.8/community/" >> /etc/apk/repositories \
&& apk add --no-cache tzdata \
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo Asia/Shanghai > /etc/timezone \
&& apk del tzdata \
&& apk update \
&& apk upgrade \
&& apk add --no-cache bash \
bash-doc \
bash-completion \
&& rm -rf /var/cache/apk/*

8.5.2. 常见的错误用法

# case1: pid 的问题

# shell 格式底层使用 /bin/sh -c 来执行, 这会导致容器中 PID 为 1 的进程是 /bin/sh 而不是 ENTRYPOINT 中指定的 Java
# 所以当我们使用 docker stop 停止容器时,接收到 SIGTERM 信号的是 /bin/sh 而不是 Java,我们的 Java 进程就不能优雅退出了
ENTRYPOINT java -jar /app.java

# 解决:

# 如果一定要用 shell 格式, 可以使用 exec
ENTRYPOINT exec java -jar /app.java

# or (推荐)
# exec 格式 就是使用 exec 来执行的,可以确保容器中 PID 为 1 的进程是 ENTRYPOINT 中指定的进程
ENTRYPOINT ["java", "-jar", "/app.jar"]

# or Shell 脚本
COPY run.sh run.sh
RUN chmod +x run.sh
ENTRYPOINT ["./run.sh"]
#
#!/bin/sh
exec java -jar /app.jar
#


# case2: 变量替换的问题

# 直接使用 exec 执行,并没有使用 Shell, 不支持变量替换
ENTRYPOINT ["java", "${JAVA_OPTS}", "-jar", "/app.jar"]

# 解决:
# 通过 shell 脚本 (推荐)
#
#!/bin/sh
exec java ${JAVA_OPTS} -jar /app.jar ${@}

# or
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]

8.5.3. 都支持两种格式 shell 和 exec 的区别

shell 格式 在启动时是使用 /bin/sh -c 来执行的,这会导致容器中 PID 为 1 的进程是 /bin/sh 而不是 ENTRYPOINT 中指定的 Java,所以当我们使用 docker stop 停止容器时,接收到 SIGTERM 信号的是 /bin/sh 而不是 Java,我们的 Java 进程就不能优雅退出了

为了解决这个问题,在使用 shell 格式时, 一般在执行的命令前加上 exec: ENTRYPOINT exec java -jar /app.java

而 exec 格式 就是使用 exec 来执行的,可以确保容器中 PID 为 1 的进程是 ENTRYPOINT 中指定的进程,

一般推荐使用这种格式,而且还可以配合 CMD 指令内置程序的命令行参数,shell 格式 下 CMD 是无效的。

当以shell形式执行指令时,由于它会调用 /bin/sh -c 并进行正常的shell处理, 会发生变量替换, 而 exec 格式就不会有变量替换

ENV name John Dow
ENTRYPOINT echo "Hello, $name" # will replace

8.5.4. run

RUN指令: 执行命令 (每一个 RUN 都是启动一个容器 -> 执行命令 -> 然后提交存储层文件变更), 用于在新图层中执行命令并创建新图像

    两种格式 , shell 格式:RUN <命令>; 就像直接在命令行中输入的命令一样

# 所有的命令只有一个目的,就是编译、安装 redis 可执行文件。
FROM debian:jessie

RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
# 必要的清理工作, 保证每一层没有垃圾
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps

还有一种 exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式

8.5.4.1. 典型错误

如下, 是错误的, 两条run命令在内存上实际是没有联系的

# 希望在 /app 下创建文件
RUN cd /app
RUN echo "hello" > world.txt

修改:

WORKDIR /app
RUN echo "hello" > world.txt

8.5.5. cmd指令

CMD 指令就是用于指定默认的容器主进程的启动命令, 等效于 命令行中 image 后跟的命令

若命令行 image 后没有跟命令, 则执行 dockerfile 中的 cmd, 若 image 后有命令, 则覆盖 dockerfile 中的 cmd

比如,ubuntu 镜像默认的 CMD 是 /bin/bash,如果我们直接 docker run -it ubuntu 的话,会直接进入 bash。 docker run -it ubuntu cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。

cmd 结束了, 容器也就退出了

eg: CMD ["nginx", "-g", "daemon off;"] 直接执行 nginx 可执行文件,并且要求以前台形式运行(CMD service nginx start是错的, 此时是"sh"这只程序作为主程序, 它运行结束, 容器就退出了)

格式:

CMD ["可执行文件", "参数1", "参数2"...]

CMD ["param1","param2"] 在使用 ENTRYPOINT 指定了起始命令后,cmd 语法就变了, 参数会传递给 entrypoint

8.5.6. ENTRYPOINT 入口点

指定容器启动后执行的命令

类似 cmd, 只是一旦和 cmd 一起使用, 可以将cmd当作参数传给 enterpoint (利用这点可以制作这种镜像: 可以当做可执行文件运行的镜像, 即能够在镜像名后面直接跟参数)

但是 shell & exec 格式区别很大:

ENTRYPOINT的Exec形式允许您设置命令和参数,然后使用CMD来设置更可能更改的其他参数

:

ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"] # 这里的命令可以被命令行覆盖

# 若命令行直接运行, 会正常打印 Hello world, 若命令行后跟 xxx, 会打印 Hello xxx

# Shell形式的ENTRYPOINT忽略任何CMD或运行命令行参数。

8.5.6.1. eg: 让镜像变成像命令一样使用

# 获取自己的公网ip, Dockerfile如下:
# 命令无法在后面接curl的参数, 比如直接在后面加"-i", 会把默认的cmd整个替换而出错,
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://ip.cn" ]

# 构建镜像
$ docker build -t myip .
# 测试:查询ip
$ docker run myip

# 不完美的解决方法如下: 通过 curl -s http://ip.cn -i 覆盖镜像内的 curl -s http://ip.cn
$ docker run myip curl -s http://ip.cn -i

## 上面的命令太啰嗦了, 正确的方法是修改Dockerfile, 用ENTERYPOINT代替CMD
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

# 现在可以正常使用"-i"参数了, 命令行的参数会直接跟在entrypoint 后面
$ docker run myip -i

8.5.6.2. eg: 应用运行前的准备工作

(这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作。

这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 <CMD>)作为命令,在脚本最后执行)

# 可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了 ENTRYPOINT 为 docker-entrypoint.sh 脚本。
FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD [ "redis-server" ] # 这里会作为参数传递给 Entrypoint

# docker-entrypoint.sh ,该脚本的内容就是根据 CMD 的内容来判断,
# 如果是 redis-server 的话,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行

#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
chown -R redis .
exec su-exec redis "$0" "$@"
fi

exec "$@"

8.6. ENV 和 arg 设置变量

env 是设置环境变量, 定义后, 可以在 dockerfile/命令行 中使用, 也可以在 container 内部供程序使用

格式有两种:

  • ENV <key> <value>
  • ENV <key1>=<value1> <key2>=<value2>...

eg:

ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"

ENV NODE_VERSION 7.2.0

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs

ARG 是设置构建参数, 仅仅用于 dockerfile/命令行 中使用, 在将来容器运行时是不会存在这些环境变量的

但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的

可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。

Define a variable that users can pass a value to at build-time with docker build

ARG JAR_FILE=target/*.jar

8.7. VOLUME 定义匿名数据卷

对于数据库类需要保存动态数据的应用,其数据库文件/动态数据应该保存于卷(volume)中, 而不要直接写入容器存储层

为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载到匿名卷,这样在运行时如果用户不指定挂载,动态数据会写入匿名卷

格式为:

  • VOLUME ["<路径1>", "<路径2>"...]
  • VOLUME <路径>

VOLUME /data 这里的 /data 目录是容器内的目录, 会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。可以进入 container, 使用 ll 查看, 会发现容器自动创建了目录 /data, 并挂载为匿名数据卷

当然,运行时可以覆盖这个挂载设置docker run -d -v mydata:/data xxxx在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置

为什么在 dockerfile 中无法使用 类似 docker -run -v /host_path:/container_path 的效果? 这时出于可移植和分享的考虑, 因为不是每个 宿主机都一定有这样的 "host_path" 特定目录

匿名数据卷的数据在宿主机哪里呢? docker inspect <container> 可以在json的 volumes 值看到

8.8. EXPOSE 声明端口

格式为 EXPOSE <端口1> [<端口2>...]

EXPOSE 指令是声明运行时容器提供服务的端口, 这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务 (必须程序开通端口, 这里的声明才有效)

要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射

那既然只是一个声明, 并没有实际暴露端口, 有什么用呢? 在 Dockerfile 中写入这样的声明有两个好处

  • 一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;
  • 另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口

8.9. USER 指定当前用户

USER指令用于指定容器执行程序的用户身份,默认是 root用户。

在docker run 中可以通过 -u 选项来覆盖USER指令的设置。

eg: docker run -i -t -u mysql newmysqldb /bin/bash, 可以发现显示的shell提示符是: mysql@57cd57edba38:/$

docker容器中的root用户密码是随机分配的

USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份

格式:USER <用户名>

这个用户必须是事先建立好的,否则无法切换。

RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

eg2:希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu

# 建立 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.7/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]

8.10. HEALTHCHECK 健康检查

HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常(来自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。)

为什么出现呢?

在没有 HEALTHCHECK 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求

生效的原理: 当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy

和 CMD, ENTRYPOINT 一样,HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效

格式:

HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令; 命令部分的格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败

# 设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1 作为健康检查命令
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1

支持如下选项:

  • --interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
  • --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
  • --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。

查看健康检查日志:

$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
"FailingStreak": 0,
"Log": [
{
"End": "2016-11-25T14:35:37.940957051Z",
"ExitCode": 0,
"Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-family: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
"Start": "2016-11-25T14:35:37.780192565Z"
}
],
"Status": "healthy"
}

另外, HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

8.11. ONBUILD 给别人用

格式:ONBUILD <其它指令>

ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行

下面以一个例子说明:

制作 Node.js 所写的应用的镜像: Node.js 使用 npm 进行包管理,所有依赖、配置、启动信息等会放到 package.json 文件里。在拿到程序代码后,需要先进行 npm install 才可以获得所有需要的依赖。然后就可以通过 npm start 来启动应用

Dockerfile:

FROM node:slim
RUN mkdir /app # 可以省掉?
WORKDIR /app
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
CMD [ "npm", "start" ]

把这个 Dockerfile 放到 Node.js 项目的根目录,构建好镜像后,就可以直接拿来启动容器运行。

现在考虑这样的情景:

我们还有第二个 Node.js 项目, 和这个类似, 如果需要制作镜像, 需要再复制一份Dockerfile到项目根目录, 构建. 这里就涉及到重复了, 如果第一个项目新发现有问题, 修复后, 第二个项目也需要手动再次修复

初级改进: 制作一份基础镜像, 后续的Dockerfile FROM这个镜像

# 基础镜像, 名字为 my-node
FROM node:slim
RUN mkdir /app
WORKDIR /app
CMD [ "npm", "start" ]

# 其他子镜像,
# 基础镜像变化后,各个项目都用这个 Dockerfile 重新构建镜像,会继承基础镜像的更新
FROM my-node
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/

现在考虑这个场景: 如果子 Dockerfile 里面有些东西需要调整呢?比如 npm install 需要加一些参数,那怎么办?这一行 RUN 是不可能放入基础镜像的,那么只能每个 子 dockerfile 都修改 npm install 这条命令了, 能否将子file中的命令提到公共file 中而不执行呢?

ONBUILD 可以解决这个问题

基础镜像:

FROM node:slim
RUN mkdir /app
WORKDIR /app
# 下面三行在当前 dockerfile 构建时候不会执行
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]

然后各个子项目的 Dockerfile 就变成了简单地

# 当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,之前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install,生成应用镜像
FROM my-node

8.12. multi-stage 多阶段构建

一种方式是将所有的构建过程包含在一个 Dockerfile 中,包括项目及其依赖库的编译、测试、打包等流程,这里可能会带来的一些问题:

  • Dockerfile 特别长,可维护性降低

  • 镜像层次多,镜像体积较大,部署时间变长

  • 源代码存在泄露的风险

好处是可以减小镜像体积

8.12.1. 清理缓存的镜像

crontab -e

# 每天夜里1点钟删除2天(48h)之前的image
0 1 * * * docker image prune -a --force --filter "until=48h"

systemctl restart crond.service

9. 使用容器

容器是独立运行的一个或一组应用,以及它们的运行态环境

9.1. use docker in github action

https://dev.to/mihinduranasinghe/using-docker-containers-in-jobs-github-actions-3eof

https://yonatankra.com/2-ways-to-use-your-docker-image-in-github-actions/

9.2. 查看

  • docker container ls -aordocker ps -a 查看所有, 包括处于终止状态的容器
  • docker container lsordocker ps 仅查看运行状态的容器

9.3. 启动

启动容器有两种方式,一种是基于镜像新建一个容器并启动(常用),另外一个是将在终止状态(stopped)的容器重新启动。

新建容器并启动: docker run <容器名:版本> <命令>)

启动已有的容器: docker start <container> or docker container start/restart xxx

当利用 docker run 来创建容器时,Docker 在后台进行如下流程:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序 (如果有的话)
  • 执行完毕后容器被终止

9.3.1. -it 维持交互终端

eg:

# 输出一个 “Hello World”,之后终止容器
$ docker run ubuntu:14.04 /bin/echo 'Hello world'
Hello world

# 启动一个 bash 终端,允许用户进行交互
# 其中,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开(以便交互式操作)。 -it 一般一起使用
$ docker run -t -i ubuntu:14.04 /bin/bash
root@af8bae53bdd3:/#

9.3.2. -d 后台运行

前面都是在前台运行的, 如何以守护状态运行? 通过 docker run -d

eg:

# 如果不使用 -d 参数运行容器,容器会把输出的结果 (STDOUT) 打印到宿主机上面
$ docker run ubuntu:17.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world

# 如果使用了 -d 参数运行容器
# 此时容器会在后台运行, 宿主机器没有任何输出(输出结果可以用 docker logs 查看)
$ docker run -d ubuntu:17.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a

# 查看容器日志
docker container logs <container ID or NAMES>
docker logs <container id or name>

9.3.3. --privileged=true 赋予特权

是否让docker 应用容器 获取宿主机root权限

使用该参数,container内的root拥有真正的root权限。 否则,container内的root只是外部的一个普通用户权限。 privileged启动的容器,可以看到很多host上的设备,并且可以执行mount。 甚至允许你在docker容器中启动docker容器。

9.4. 终止container

docker container stop <xxx>

此外,当 Docker 容器中指定的应用终结时,容器也自动终止(例如: 只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。)

docker container ls -a 查看所有的container, 包括停止的container

9.5. 删除container

container终止后, 不会删除, 需要手动删除

  • docker container rm <xxx> 删除指定container(容器处于终止状态)
  • docker container rm -f <xxx>如果要删除一个运行中的容器,可以添加 -f 参数。Docker 会发送 SIGKILL 信号给容器
  • docker container prune 删除所有终止的容器

9.6. 进入容器

在使用 -d 参数时,容器启动后会进入后台, 某些时候需要进入容器进行操作,包括使用 docker attach 命令或 docker exec 命令(推荐后者, 因为后者exit时不会导致容器退出)

  • docker attach <xxx> 进入container, 当exit时, 容器也随之终止

  • docker exec -it <xxx> bash 进入container, 退出时容器不会终止(注意, 后面要跟一个命令bash)

9.7. 容器的导入和导出

导出

$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7691a814370e ubuntu:14.04 "/bin/bash" 36 hours ago Exited (0) 21 hours ago test
$ docker export 7691a814370e > ubuntu.tar

这样将导出容器快照到本地文件

导入容器快照为镜像:

$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
test/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB

# 或者
$ docker import http://example.com/exampleimage.tgz example/imagerepo

docker import和docker load比较: 容器快照文件(docker import)将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件(docker load)将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

9.8. 拷贝容器内文件到主机

docker cp <container_name>:/path <host_path>

10. 访问镜像仓库

10.1. dockerhub

docker login登录 docker logout 退出

docker search <xxx> 搜索 docker pull <xxx> 下载到本地

推送镜像到自己的仓库:

# 标记新的tag用于推送到远程
# 即修改镜像名字 (不过一般在 build image 时就应该规范指定好最终名字)
$ docker tag <image_old_name/old_id> xiaoyureed/ubuntu:17.10

$ docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 17.10 275d79972a86 6 days ago 94.6MB
username/ubuntu 17.10 275d79972a86 6 days ago 94.6MB

# 推送
$ docker push xiaoyureed/ubuntu:17.10

# 现在搜索可以搜到了
$ docker search username

NAME DESCRIPTION STARS OFFICIAL AUTOMATED
xiaoyureed/ubuntu

此外, docker hub 支持 自动创建(Automated Builds), 对于需要经常升级镜像内程序来说,十分方便

10.1.1. publish by using github action

https://twbhub.com/post/github-actions_publish_images/ todo

10.2. 私有仓库

10.2.1. allow http

想让本网段的其他主机也能把镜像推送到私有仓库。你就得把例如 192.168.199.100:5000 这样的内网地址作为私有仓库地址,这时你会发现无法成功推送镜像。这是因为 Docker 默认不允许非 HTTPS 方式推送镜像。我们可以通过 Docker 的配置选项来取消这个限制:

/etc/docker/daemon.json

{
"registry-mirror": [
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com"
],
"insecure-registries": [
"192.168.199.100:5000"
]
}

10.2.2. harbor(推荐)

https://github.com/goharbor/harbor , 带 gui, user management

由VMWare在Docker Registry的基础之上进行了二次封装,加进去了很多额外程序,而且提供了一个非常漂亮的web界面。

从私服拉取镜像: 先登录 docker login -u admin -p xxx <harbor ip>, 再拉取 docker pull 182.168.xxx.xxx/org_name/img_name:v

10.2.2.1. k8s 配置harbor

在任意节点 docker 登录 harbor 后, /root/.docker/config.json 能查看密码, 将 json 内容 base64 编码 cat /root/.docker/config.json | base64 -w O, 创建 secret.yml:

apiVersion: v1
kind: Secret
metadata:
# 值 "login" 会在 Deployment 中使用
name: login
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: <base64_value>

kc apply -f secret.yml

在 deployment 中如何使用?

...
kind: Deployment
...
...
spec:
containers:
- image: <harbor url>/org/img_name:version
...
imagePullSecretes:
# 配置k8s 登录 harbor 时使用的 secret 名
- name: login

10.2.3. Nexus

多用于 搭建 Maven repo, 也能存储 docker image

10.2.4. docker-registry

docker hub 提供, 一个镜像即可拉起来, 没有 gui, 但是社区有 gui

docker pull docker.io/registry:latest
docker pull konradkleine/docker-registry-frontend:v2

docker run -d -p 5000:5000 --restart=always --name registry docker.io/registry:latest

docker run -d --link registry:registry --name registry-frontend -e ENV_DOCKER_REGISTRY_HOST=registry -e ENV_DOCKER_REGISTRY_PORT=5000 -p 9090:80 konradkleine/docker-registry-frontend:v2

11. docker数据管理

Docker container 产生的数据在 container 删除后就丢失了, 除非 commit 这个 container 生成新的 image, 那么如何保存这些数据呢:

  • 数据卷(Volumes)

  • 挂载主机目录 (Bind mounts)

11.1. 数据卷

docker 默认在主机上会有一个特定的区域(/var/lib/docker/volumes/ Linux),该区域用来存放 volume。volume 在生成的时候如果不指定名称,便会随机生成

数据卷 是被设计用来持久化数据的 , 类似linux下目录的mount; 它绕过 UFS,可以提供很多有用的特性:

  • 数据卷 可以在容器之间共享和重用

  • 对 数据卷 的更新,不会影响镜像

  • 数据卷 默认会一直存在,即使容器被删除

    它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷


`docker volume create my-vol` 创建一个volume
`docker volume ls` 查看所有的volume
`docker volume inspect my-vol` 查看指定的volume
`docker inspect web` 在容器中查看volume信息(数据卷 信息在 "Mounts" Key 下面, 这里的web为container名)
`docker volume rm my-vol`删除volume
`docker volume prune` 清理无主的volume
docker rm -v <container_id> 删除容器同时删除 volume

# 创建一个container同时挂载一个/多个数据卷
# 创建一个名为 web 的容器,并加载一个 数据卷 到容器的 /webapp 目录。, 如果/webapp中有内容则暂时会被屏蔽
$ docker run -d -P \
--name web \
# -v my-vol:/wepapp \
# 等同
--mount source=my-vol,target=/webapp \
training/webapp \
python app.py

11.2. 挂载主机目录

使用 --mount 标记可以指定挂载一个本地主机的目录到容器中去 (也可以挂在数据卷到容器中去)

# 加载主机的 /src/webapp 目录到容器的 /opt/webapp目录
$ docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp \
--mount type=bind,source=/src/webapp,target=/opt/webapp \
training/webapp \
python app.py

这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径

Docker 挂载主机目录的默认权限是 读写,用户也可以通过增加 readonly 指定为 只读:

$ docker run -d -P \
--name web \
# -v /src/webapp:/opt/webapp:ro \
--mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
training/webapp \
python app.py

查看使用docker inspect <container name>, 挂载主机目录 的配置信息在 "Mounts" Key 下面;

也可以挂载主机单个文件

$ docker run --rm -it \
# -v $HOME/.bash_history:/root/.bash_history \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu:17.10 \
bash

root@2affd44b4667:/# history
1 ls
2 diskutil list

这样就可以记录在容器输入过的命令了

--mount 和 -v 区别:

http://einverne.github.io/post/2018/03/docker-v-and-mount.html

  • -v 选项将所有选项集中到一个值, 更精简

    -v 或 --volume: 包含三个 field,使用 : 来分割,所有值需要按照正确的顺序。第一个 field 是 volume 的名字,并且在宿主机上唯一,对于匿名 volume,第一个field通常被省略;第二个field是宿主机上将要被挂载到容器的path或者文件;第三个field可选,比如说 ro

  • --mount 选项将可选项分开, 更啰嗦

    --mount: 包含多个 key-value 对,使用逗号分割。--mount 选项更加复杂,但是各个值之间无需考虑顺序。

    type,可以为 bind, volume, tmpfs, 通常为 volume source 也可以写成 src,对于 named volumes,可以设置 volume 的名字,对于匿名 volume,可以省略 destination 可以写成 dst或者 target 该值会挂载到容器 readonly 可选,如果使用,表示只读 volume-opt 可选,可以使用多次

如:

docker run -d \
--name=nginxtest \
--mount source=nginx-vol,destination=/usr/share/nginx/html \
nginx:latest

docker run -d \
--name=nginxtest \
-v nginx-vol:/usr/share/nginx/html \
nginx:latest

11.3. volume 的共享

--volume-from 为当前创建的容器指定其他容器的 volume

有镜像 imageA, 派生容器 container1, 挂载有匿名卷 /data, docker run --name container2 --volume-from container1 imageA 那么 /data 目录会被两个 容器共享

12. 网络互联

12.1. docker 默认初始创建3 个网络

docker 安装的时候,会在宿主机建立一块虚拟网卡 docker0 作为网桥, 宿主机 通过这个网卡 桥接到了docker内局域网。在docker容器内,通过 docker0 的 IP 地址就能访问宿主机的服务了。

宿主执行 ifconfig, eth0, eth1 ... 代表真实网卡, docker0 是 docker 新建的虚拟网卡, 作为容器访问宿主的网桥, veth2ddf66cc, vethxxxxx ... 和容器内的 eth0 绑定

Docker安装后,默认会创建下面三个 network, 除非另有指定,否则新启动的容器会链接 bridge 这个 network。

$ docker network ls
NETWORK ID NAME DRIVER SCOPE

9781b1f585ae bridge bridge(默认) local --> 桥接网络, 默认, 重启下,Docker的IP地址就变了, 同个默认桥接网络下的两个容器只能通过docker内部 ip 访问, 除非创建容器时候使用--link指定链接别名

生产一般自定义一个桥接网络(因为自定义桥接网络优先级高于默认桥接网络, 不要在生产环境使用默认 bridge network

链接到同一个 自定义桥接网络的两个容器互相能够通过 容器名字访问

1252da701e55 host host local --> 主机网络, 容器的网络会附属在主机上,两者是互通的, 没有隔离 (在容器中运行一个Web服务,监听8080端口,则主机的8080端口就会自动映射到容器中)

237ea3d5cfbf none null local --> 无指定网络, docker 容器就不会分配局域网的IP

12.2. 创建固定ip

自己创建一个新的bridge网络bridge1,在创建bridge1的时候同时创建子网,那么在创建容器的时候指定网络为bridge1并指定ip即可

# 创建网络, 指定网段, 默认是 bridge 类型, 所以可省略
# --driver 或者 -d 指定网络类型
docker network create [--driver bridge] --subnet=172.18.0.0/16 my-network

docker run -itd --name my-container --network my-network --ip 172.18.0.10 my-image </bin/bash >
* 用 --network 参数,可以指定网络类型
* --ip 172.17.0.10指定ip

12.3. 查看container的ip

# 查看docker0的网络(宿主机上操作)
ip addr show docker0
# 到容器内部查看ip
ip addr show eth0
# or
docker inspect <id,container_name> | grep "IPAddress"

12.4. 外部访问容器

如果是 bridge network, 可使用 -p 暴露接口

-p <host port>:<container port> 指定端口映射
-P 使用宿主随机端口映射到容器, 可通过 docker ls 查看到底哪个端口

如果是 host network, 则容器的端口会自动暴露到宿主 (宿主连容器)

或者查询宿主机 docker0 网卡的地址 ip addr show docker0 作为 宿主在 docker 局域网的地址 (然后容器连宿主)

# 端口自动映射, 需要手动查看端口是多少
$ docker run -d -P training/webapp python app.py

$ docker container ls -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse

# 映射所有接口地址
# 使用 hostPort:containerPort 格式本地的 5000 端口映射到容器的 5000 端口; 此时默认会绑定本地所有接口上的所有地址
$ docker run -d -p 5000:5000 training/webapp python app.py

# 映射到指定地址的指定端口
# 可以使用 ip:hostPort:containerPort 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1
$ docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py

# 映射到指定地址的随机端口
# 使用 ip::containerPort 绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。
$ docker run -d -p 127.0.0.1::5000 training/webapp python app.py

# 还可以使用 udp 标记来指定 udp 端口, 默认为tcp
$ docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py

查看当前映射的端口配置,也可以查看到绑定的地址:

$ docker port <container name> [port]
127.0.0.1:49155

12.5. 容器互联

一个是通过 docker network


# 创建一个叫做 my-bridge 的网桥:
docker network create -d bridge my-bridge
# 显示所有 bridge :
docker network ls
# 创建容器mynginx2并添加到 my-bridge 网络中:
docker run --name mynginx2 --network my-bridge -p 8080:80 -d nginx:latest
# 使用 inspect 查看:
docker inspect mynginx2
# 查看 my-bridge 网络里面的容器:
docker inspect my-bridge
# 手动将某个运行着的容器加入网桥:
docker network connect my-bridage test2s



############ 实例

# BusyBox
# 是一个集成了三百多个最常用Linux命令和工具的软件

# 创建docker网络, 启动两个容器, 加入网络
$ docker network create -d bridge my-net
$ docker run -it --rm --name busybox1 --network my-net busybox sh
# 需要打开新的terminal
$ docker run -it --rm --name busybox2 --network my-net busybox sh
# 最后在各自的terminal中ping测试
ping busybox2
ping busybox1


# MySQL

docker network create -d bridge some-network
docker run -d --name some-mysql --network some-network -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql:tag
# 启动一个临时的 MySQL client
docker run -it --rm --network some-network mysql mysql -hsome-mysql -uexample-user -p

一个是通过 --link (不推荐), docker run --link可以用来链接2个容器,使得源容器(被链接的容器)和接收容器(主动去链接的容器)之间可以互相通信,并且接收容器可以获取源容器的一些数据,如源容器的环境变量。

#--link <container_name>:<link_name> , link name 相当于 container 中的 hostname
docker run -d --name node --link selenium_hub:hub selenium/node-chrome-debug
docker exec -it node /bin/bash
ping hub # 可以 ping 通

更方便的容器互联使用 Docker Compose

12.6. 配置dns 和 主机名 hostname

Docker 利用虚拟文件来挂载容器的 3 个相关配置文件。

在容器中使用 mount 命令可以看到挂载信息:

$ mount
/dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ...
/dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ...
tmpfs on /etc/resolv.conf type tmpfs ...

这种机制可以让宿主主机 DNS 信息发生更新后,所有 Docker 容器的 DNS 配置通过 /etc/resolv.conf 文件立刻得到更新

配置全部容器的 DNS ,也可以在 /etc/docker/daemon.json 文件中增加以下内容来设置:

{
"dns" : [
"114.114.114.114",
"8.8.8.8"
]
}

这样每次启动的容器 DNS 自动配置为 114.114.114.114 和 8.8.8.8。使用以下命令来证明其已经生效:docker run -it --rm ubuntu:17.10 cat etc/resolv.conf

如果用户想要手动指定容器的配置,可以在使用 docker run 命令启动容器时加入如下参数:

  • -h HOSTNAME 或者 --hostname=HOSTNAME 设定容器的主机名,它会被写到容器内的 /etc/hostname 和 /etc/hosts。但它在容器外部看不到,既不会在 docker container ls 中显示,也不会在其他的容器的 /etc/hosts 看到。

  • --dns=IP_ADDRESS 添加 DNS 服务器到容器的 /etc/resolv.conf 中,让容器用这个服务器来解析所有不在 /etc/hosts 中的主机名。

  • --dns-search=DOMAIN 设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host 的主机时,DNS 不仅搜索 host,还会搜索 host.example.com。

13. docker-compose

13.1. some significant materials

https://www.composerize.com/ convert docker command into docker-compose file

https://github.com/docker/awesome-compose

13.2. compose简介

通俗的理解: 可以同时定义和运行多个 Docker 容器. 用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)

Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应用

Compose 项目由 Python 编写,调用了 Docker 服务提供的 API 来对容器进行管理

Compose 中有两个重要的概念:

  • 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。

    Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理

  • 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

安装卸载

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

  1. 通过 Python 的包管理工具 pip 进行安装,
  2. 也可以直接下载编译好的二进制文件使用
  3. 能够直接在 Docker 容器中运行

前两种方式是传统方式,适合本地环境下安装使用;最后一种方式则不破坏系统环境,更适合云计算场景;

Docker for Windows 自带 docker-compose 二进制文件,安装 Docker 之后可以直接使用。

# 直接下载对应的二进制包(推荐)
$ sudo curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

# 还可以使用pip安装
$ sudo pip install -U docker-compose

# 命令补全功能需要下载一个文件
$ curl -L https://raw.githubusercontent.com/docker/compose/1.8.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose

# 如何在linux中执行? 下载的run.sh实际是下载docker/compose 镜像并运行
$ curl -L https://github.com/docker/compose/releases/download/1.8.0/run.sh > /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose

# 测试是否安装成功, 第一次会下载compose镜像
docker-compose version

卸载:

# 如果是二进制包方式安装的,删除二进制文件即可。
$ sudo rm /usr/local/bin/docker-compose

# 如果是通过 pip 安装的,则执行如下命令即可删除。
$ sudo pip uninstall docker-compose

看一个 quick-start:

eg: 用Python 来建立一个能够记录页面访问次数的 web 网站

在文件夹project/下新建app.py

from flask import Flask
from redis import Redis

app = Flask(__name__)
redis = Redis(host='redis', port=6379)

@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello World! 该页面已被访问 {} 次。\n'.format(count)

if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)

新建 Dockerfile

FROM python:3.6-alpine
ADD . /code
WORKDIR /code
RUN pip install redis flask
CMD ["python", "app.py"]

新建 docker-compose.yml

# 这里的版本和docker-compose有关, 如果报错, 则改为'2'
version: '3'
services:

web:
build: . # 在当前目录寻找 dockerfile构建
ports:
- "5000:5000"

redis:
image: "redis:alpine"

运行: docker-compose up

13.3. 命令使用

默认的命令对象是项目,这意味着项目中所有的服务都会受到命令影响

格式: docker-compose [-f=<arg>...] [options] [COMMAND] [ARGS...]

options有这些:

-f, --file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可以多次指定。

-p, --project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名。

--x-networking 使用 Docker 的可拔插网络后端特性

--x-network-driver DRIVER 指定网络后端的驱动,默认为 bridge

--verbose 输出更多调试信息。

-v, --version 打印版本并退出

13.3.1. up

  • 格式为 docker-compose up [options][SERVICE...]

  • 该命令十分强大,它将尝试自动完成包括 构建, 启动...

  • 默认情况,docker-compose up 启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。

    当通过 Ctrl-C 停止命令时,所有容器将会停止

    如果使用 docker-compose up -d,将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项 (-d 必须接在 up 之后不能在之前)

  • docker-compose up --no-recreate只会启动处于停止状态的容器,而忽略已经运行的服务

    默认情况,如果服务容器已经存在,docker-compose up 将会尝试停止容器,然后重新创建

  • docker-compose up --no-deps -d <SERVICE_NAME> 只想重新部署某个服务, 通过这个命令来重新创建服务并后台停止旧服务,启动新服务,并不会影响到其所依赖的服务

所有可用的选项:

-d 在后台运行服务容器。(必须在 docker-compose [flags] <command> 中的 command 之后, 不能在之前)

-f 指定 yml 文件
docker-compose -f xxx.yml up [-d]

-p, --project-name :指定工程名称,默认使用 docker-compose.yml 文件所在目录的名称;

--no-color 不使用颜色来区分不同的服务的控制台输出。

--no-deps 不启动服务所链接的容器。

--force-recreate 强制重新创建容器,不能与 --no-recreate 同时使用。

--no-recreate 如果容器已经存在了,则不重新创建,不能与 --force-recreate 同时使用。

--no-build 不自动构建缺失的服务镜像。

-t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)

13.3.2. run

格式为 docker-compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]

在指定服务上执行一个命令。

eg:

# 将会启动一个 ubuntu 服务容器,并执行 ping docker.com 命令
$ docker-compose run ubuntu ping docker.com

默认情况下,如果存在关联,则所有关联的服务将会自动被启动,除非这些服务已经在运行中

如果不希望自动启动关联的容器,可以使用 --no-deps 选项

# 不会启动 web 容器所关联的其它容器
$ docker-compose run --no-deps web python manage.py shell

该命令类似启动容器后运行指定的命令,相关卷、链接等等都将会按照配置自动创建, 但是有两个不同点

  1. 给定命令将会覆盖原有的自动运行命令
  2. 不会自动创建端口,以避免冲突

所有可选的选项:

-d 后台运行容器。

--name NAME 为容器指定一个名字。

--entrypoint CMD 覆盖默认的容器启动指令。

-e KEY=VAL 设置环境变量值,可多次使用选项来设置多个环境变量。

-u, --user="" 指定运行容器的用户名或者 uid。

--no-deps 不自动启动关联的服务容器。

--rm 运行命令后自动删除容器,d 模式下将忽略。

-p, --publish=[] 映射容器端口到本地主机。

--service-ports 配置服务端口并映射到本地主机。

-T 不分配伪 tty,意味着依赖 tty 的指令将无法运行。

13.3.3. scale

  • 格式为 docker-compose scale [options][SERVICE=NUM...]

  • 设置指定服务运行的容器个数。

  • 通过 service=num 的参数来设置数量。例如

    • eg

      # 将启动 3 个容器运行 web 服务,2 个容器运行 db 服务
      $ docker-compose scale web=3 db=2
  • 当指定数目多于该服务当前实际运行容器,将新创建并启动容器;反之,将停止容器

可用选项:

-t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

13.3.4. start

格式为 docker-compose start [SERVICE...]

启动已经存在的服务容器

13.3.5. build

  • 格式为 docker-compose build [options][SERVICE...]

  • 构建(重新构建)项目中的服务容器

  • 服务容器一旦构建后,将会带上一个标记名,例如对于 web 项目中的一个 db 容器,可能是 web_db

  • 可以随时在项目目录下运行 docker-compose build 来重新构建服务

所有选项:

--force-rm 删除构建过程中的临时容器。

--no-cache 构建镜像过程中不使用 cache(这将加长构建过程)。

--pull 始终尝试通过 pull 来获取更新版本的镜像。

13.3.6. config

验证 Compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因

13.3.7. exec

进入指定的容器

13.3.8. images

列出 Compose 文件中包含的镜像。

13.3.9. kill

格式为 docker-compose kill [options][SERVICE...]

通过发送 SIGKILL 信号来强制停止服务容器。

支持通过 -s 参数来指定发送的信号,例如通过如下指令发送 SIGINT 信号。

$ docker-compose kill -s SIGINT

13.3.10. down

此命令将会停止 up 命令所启动的容器,并移除网络

13.3.11. pause

格式为 docker-compose pause [SERVICE...]

暂停一个服务容器。

13.3.12. unpause

格式为 docker-compose unpause [SERVICE...]

恢复处于暂停状态中的服务。

13.3.13. restart

格式为 docker-compose restart [options][SERVICE...]

重启项目中的服务。

选项:

-t, --timeout TIMEOUT 指定重启前停止容器的超时(默认为 10 秒)。

13.3.14. stop

格式为 docker-compose stop [options][SERVICE...]

停止已经处于运行状态的容器,但不删除它。通过 docker-compose start 可以再次启动这些容器。

选项:

-t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

13.3.15. rm

格式为 docker-compose rm [options][SERVICE...]

删除所有(停止状态的)服务容器。推荐先执行 docker-compose stop 命令来停止容器。

选项:

-f, --force 强制直接删除,包括非停止状态的容器。一般尽量不要使用该选项。

-v 删除容器所挂载的数据卷。

13.3.16. logs

格式为 docker-compose logs [options][SERVICE...]

查看服务容器的输出。默认情况下,docker-compose 将对不同的服务输出使用不同的颜色来区分。可以通过 --no-color 来关闭颜色。

该命令在调试问题的时候十分有用。

13.3.17. top

查看各个服务容器内运行的进程

13.3.18. version

格式为 docker-compose version。

打印版本信息。

13.3.19. port

格式为 docker-compose port [options] SERVICE PRIVATE_PORT。

打印某个容器端口所映射的公共端口。

选项:

--protocol=proto 指定端口协议,tcp(默认值)或者 udp。
--index=index 如果同一服务存在多个容器,指定命令对象容器的序号(默认为 1)。

13.3.20. ps

格式为 docker-compose ps [options][SERVICE...]

列出项目中目前的所有容器。

选项:

-q 只打印容器的 ID 信息。

13.3.21. pull

格式为 docker-compose pull [options][SERVICE...]

拉取服务依赖的镜像。

选项:

--ignore-pull-failures 忽略拉取镜像过程中的错误。

13.3.22. push

推送服务依赖的镜像到 Docker 镜像仓库。

13.4. docker-compose.yml

https://docs.docker.com/compose/compose-file/

默认的模板文件名称为 docker-compose.yml

模版中的命令基本跟 docker run 中对应参数的功能一致。

如果变量名称或者值中用到 true|false,yes|no 等表达 布尔 含义的词汇,最好放到引号里, 这些符号包括:

如果值的含义是密码, 应该用引号, 不管是不是数字

y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF
version: "3"

services:
webapp:
# 指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像
# 每个服务都必须通过 image 指令指定镜像或 build 指令(需要 Dockerfile)等来自动构建生成镜像。
# 可以不加引号
image: "examples/web"
# 若果当前目录下有 dockerfile, 则是用 dockerfile 构建出的镜像, 此时 image 属性可以不设置
build: ./
# 如果时多模块项目, xxx 就是子模块目录目录名
# 使用 build 指令,在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOLUME, ENV 等) 将会自动被获取,无需在 docker-compose.yml 中再次设置。
# 指定 Dockerfile 所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)
build: ./dir_name
ports:
- "80:80"
volumes:
- "/data"
# cap_add, cap_drop 指定容器的内核能力(capacity)分配
cap_add:
- ALL # 让容器拥有所有能力
cap_drop:
- NET_ADMIN # 去掉 NET_ADMIN 能力
# 覆盖容器启动后默认执行的命令
command: echo "hello world"
# 仅用于 Swarm mode
configs:
# 仅用于 Swarm mode
deploy:
# 指定父 cgroup 组,意味着将继承该组的资源限制; 例如,创建了一个 cgroup 组名称为 cgroups_1
cgroup_parent: cgroups_1
# 指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式
# 注意: 指定容器名称后,该服务将无法进行扩展(scale),因为 Docker 不允许多个容器具有相同的名称
container_name: docker-web-container
# 指定设备映射关系
devices:
- "/dev/ttyUSB1:/dev/ttyUSB0"
# 解决容器的依赖、启动先后的问题。以下例子中会先启动 redis db 再启动 webapp
# 注意:webapp 服务不会等待 redis db 「完全启动」之后才启动
depends_on:
- db
- redis
# 自定义 DNS 服务器。可以是一个值,也可以是一个列表
# dns: 8.8.8.8
dns:
- 8.8.8.8
- 114.114.114.114
# 配置 DNS 搜索域。可以是一个值,也可以是一个列表
# dns_search: example.com
dns_search:
- domain1.example.com
- domain2.example.com
# 挂载一个 tmpfs 文件系统到容器。可以为单个, 也可以为列表
# tmpfs: /run
tmpfs:
- /run
- /tmp
# 设置环境变量。你可以使用数组或字典两种格式
# 只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。
# environment:
# RACK_ENV: development
# SESSION_SECRET:
environment:
- RACK_ENV=development
- SESSION_SECRET
# 从文件中获取环境变量,可以为单独的文件路径或列表
# 如果通过 docker-compose -f FILE 方式来指定 Compose 模板文件,则 env_file 中变量的路径会基于模板文件路径
# 如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准
# 环境变量文件中每一行必须符合格式(PROG_ENV=development),支持 # 开头的注释行
# env_file: .env
env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
# 暴露端口,但不映射到宿主机,只让内部的服务访问
expose:
- "3000"
- "8000"
# 暴露端口信息。
# 使用宿主端口:容器端口 (HOST:CONTAINER) 格式,或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。
ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"
# 链接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的外部容器。不建议使用
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
# 类似 Docker 中的 --add-host 参数,指定额外的 host 名称映射信息
# 会在启动后的服务容器中 /etc/hosts 文件中添加host映射条目8.8.8.8 googledns和52.1.157.61 dockerhub
extra_hosts:
- "googledns:8.8.8.8"
- "dockerhub:52.1.157.61"
# 通过命令检查容器是否健康运行。
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
# 为容器添加 Docker 元数据(metadata)信息。例如可以为容器添加辅助说明信息
labels:
com.startupteam.description: "webapp for a startup team"
com.startupteam.department: "devops department"
com.startupteam.release: "rc3 for v1.0"
# 配置日志选项。
logging:
driver: syslog # 支持三种日志驱动类型: "json-file", "syslog", "none"
# 配置日志驱动的相关参数
options:
syslog-address: "tcp://192.168.0.42:123"
# 需要先声明
networks:
- some-network
- other-network
# 设置网络模式。使用和 docker run 的 --network 参数一样的值
network_mode: "bridge" # 支持"bridge", "host", "none", "service:[service name]", "container:[container name/id]"
# 跟主机系统共享进程命名空间。打开该选项的容器之间,以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。
pid: "host"
# 指定容器模板标签(label)机制的默认属性(用户、角色、类型、级别等)。例如配置标签的用户名和角色名
security_opt:
- label:user:USER
- label:role:ROLE
# 设置另一个信号来停止容器。在默认情况下使用的是 SIGTERM 停止容器
stop_signal: SIGUSR1
# 配置容器内核参数, 两种方式
# sysctls:
# net.core.somaxconn: 1024
# net.ipv4.tcp_syncookies: 0
sysctls:
- net.core.somaxconn=1024
- net.ipv4.tcp_syncookies=0
# 指定容器的 ulimits 限制值
# 例如,指定最大进程数为 65535,指定文件句柄数为 20000(软限制,应用可以随时修改,不能超过硬限制) 和 40000(系统硬限制,只能 root 用户提高)。
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
# 数据卷所挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。
volumes:
- /var/lib/mysql # 匿名 volume
- cache/:/tmp/cache
- ~/configs:/etc/configs/:ro
- xxName:/tmp/data # 为容器内的匿名volume 名为为 xxName

db:
build: ./dir
# 配置容器连接的网络。注意这里是引用和services同级的networks下的项
networks:
- some-network
- other-network
# 存储敏感数据,例如 mysql 服务密码, 注意这里是引用
secrets:
- db_root_password
- my_other_secret
# 另一种指定Dockerfile的方式, 自由度更高
redis:
build:
context: ./dir # Dockerfile所在文件夹
dockerfile: Dockerfile-alternate # Dockerfile 文件名
# 指定构建镜像时的变量
args:
buildno: 1
cache_from: # 指定构建镜像的缓存
- alpine:latest
- corp/web_app:3.14
# 声明网络
networks:
some-network:
other-network:
secrets:
my_secret:
file: ./my_secret.txt
my_other_secret:
external: true

volumes:
# 声明一个命名的 volume
xxName:

Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 .env 文件中的变量

version: "3"
services:

db:
# 如果执行 MONGO_VERSION=3.2 docker-compose up 则会启动一个 mongo:3.2 镜像的容器;如果执行 MONGO_VERSION=2.8 docker-compose up 则会启动一个 mongo:2.8 镜像的容器
image: "mongo:${MONGO_VERSION}"

若当前目录存在 .env 文件,执行 docker-compose 命令时将从该文件中读取变量

在当前目录新建 .env 文件并写入以下内容。

# 支持 # 号注释
MONGO_VERSION=3.6

执行 docker-compose up 则会启动一个 mongo:3.6 镜像的容器

docker-compose.yml模版文件中的其他指令:

指定服务容器启动后执行的入口文件。

entrypoint: /code/entrypoint.sh
指定容器中运行应用的用户名。

user: nginx
指定容器中工作目录。

working_dir: /code
指定容器中搜索域名、主机名、mac 地址等。

domainname: your_website.com
hostname: test
mac_address: 08-00-27-00-0C-0A
允许容器中运行一些特权命令。

# 允许container内的root拥有真正的root权限, 可以看到很多host上的设备,并且可以执行mount。甚至允许你在docker容器中启动docker容器。
privileged: true

#指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped。
# 命令行中 --restart always, 重启策略是面向生产环境的一个启动策略,在开发过程中可以忽略
# 共有如下策略:
# no,默认策略,在容器退出时不重启容器
# on-failure,在容器非正常退出时(退出状态非0),才会重启容器
# on-failure:3,在容器非正常退出时重启容器,最多重启3次, 可自己指定
# always,在容器退出时总是重启容器
# unless-stopped,在容器退出时总是重启容器,但是不考虑在Docker守护进程启动时就已经停止了的容器
# 查看容器重启次数 docker inspect -f "{{ .RestartCount }}" ba-208
# 查看容器最后一次的启动时间 docker inspect -f "{{ .State.StartedAt }}" ba-208
restart: always

# 以只读模式挂载容器的 root 文件系统,意味着不能对容器内容进行修改。
read_only: true
打开标准输入,可以接受外部输入。

stdin_open: true
模拟一个伪终端。

tty: true

14. docker-machine

Docker Machine 是 Docker 官方编排(Orchestration)项目之一,负责在多种平台上快速安装 Docker 环境, 包括虚拟机、本地主机和云平台

linux上安装

$ sudo curl -L https://github.com/docker/machine/releases/download/v0.13.0/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/docker-machine
$ sudo chmod +x /usr/local/bin/docker-machine

`docker-machine -v` # 查看版本

使用virtualbox驱动创建本地主机实例:

docker-machine create -d virtualbox test

15. docker-swarm,swarm-mode

Docker Swarm 是 Docker 官方三剑客项目之一,提供 Docker 容器集群服务,是 Docker 官方对容器云生态进行支持的核心方案; 使用它,用户可以将多个 Docker 主机封装为单个大型的虚拟 Docker 主机,快速打造一套容器云平台;Docker 1.12.0+ Swarm mode 已经内嵌入 Docker 引擎,成为了 docker 子命令 docker swarm, 因此一般使用docker的swarm mode

16. docker实践

https://github.com/wenshunbiao/docker

16.1. nacos

https://hub.docker.com/r/nacos/nacos-server

https://hub.docker.com/r/zhusaidong/nacos-server-m1

docker run -d -p 8848:8848 --env MODE=standalone  --name nacos  zhusaidong/nacos-server-m1:2.0.3

16.2. 搭建持续集成系统

# 查看端口占用
lsof -i:8080
# 查看指定端口的进程
netstat -tunlp | grep 8000


# 搭建 jenkins

# 初始密码到jenkins内部[/var/jenkins_home/secrets/initialAdminPassword]看
# -u 0 表示覆盖镜像内部的帐号, 使用外部的root, 用于解决volume权限问题
# -v xxx:yyy 表示 volume:container中的目录
docker run -d -p 8090:8080 -p 50000:50000 -v /srv/docker/jenkins:/var/jenkins_home --name jenkins -u 0 jenkins

# 或者 使用docker-compose编排
# docker-compose.yml:(http://www.voidcn.com/article/p-keekrhyk-brk.html)
version: '2'

services:
gitlab:
image: gitlab/gitlab-ce:latest
ports:
- "20443:443"
- "20080:80"
- "20022:22"
networks:
- devops
volumes:
- /data/gitlab/config:/etc/gitlab
- /data/gitlab/logs:/var/log/gitlab
- /data/gitlab/data:/var/opt/gitlab

jenkins:
image: jenkins:latest
ports:
- "28080:8080"
- "50000:50000"
networks:
- devops
volumes:
- /data/jenkins:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock

sonarqube:
image: sonarqube:latest
ports:
- "29000:9000"
- "29092:9092"
networks:
- devops

networks:
devops:
driver: bridge

搭建gitlab, 上传代码时候, git remote add origin http://43.254.2.51:20080/root/rest-api-starter.git, git push --set-upstream origin master

自动部署需要jenkins的插件 Publish over SSH;

16.3. docker和springboot

老外的一篇博文: https://www.callicoder.com/spring-boot-docker-example/, 先没有使用maven工具, 然后引入maven插件, 两种实现

使用了 maven 插件: http://www.ityouknow.com/springboot/2018/03/19/spring-boot-docker.html

16.4. oracle11g

https://blog.csdn.net/qq_33982232/article/details/83133870 https://www.35youth.cn/685.html (https://registry.hub.docker.com/r/jaspeen/oracle-11g)

16.5. 使用 mysql 镜像

docker pull mysql
docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=raincloud-hello mysql
mysql -h127.0.0.1 -uroot -p # password is 'hello'

# 若 local 没有安装 MySQL client, 则使用 container client
docker network create my-net
docker network connect my-net my-mysql
docker run -it --network my-net --rm mysql mysql -hmy-mysql -uroot -p

# or
docker run -it --rm --link my-mysql:xxxhost mysql mysql -hxxxhost -uroot -p


# 外部存储 volume
# https://www.cnblogs.com/sablier/p/11605606.html
# mysql8 (手动敲入, 不要复制粘贴, 容易出非法字符)
docker run -d --name mysql8 -p 3306:3306 -v /root/mysql/conf:/etc/mysql -v /root/mysql/logs:/var/log/mysql -v /root/mysql/data:/var/lib/mysql -v /root/mysql/mysql-files:/var/lib/mysql-files/ -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=face mysql:latest --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci # utf8mb4_unicode_ci 可以考虑替换为 更快的 utf8mb4_general_ci

# mysql5.7
docker run -d --name mysql5_7 -p 3306:3306 -v /root/mysql/conf:/etc/mysql -v /root/mysql/logs:/var/log/mysql -v /root/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=face mysql:5.7.34 --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci

mysql容器的数据持久化问题

导入导出

在宿主机上连接 MySQL 容器, 地址是 127.0.0.1:3306 , 而不是 localhost, (查container ip:cat /etc/hosts)

Authentication plugin 'caching_sha2_password' cannot be loaded 异常 - https://www.cnblogs.com/chuancheng/p/8964385.html

Access denied for user 'root'@'172.19.0.1' 异常 - 进入dockers内部 docker exec -it <xxx> bash, mysql -uroot -p 登陆MySQL, update mysql.user set host = '%' where user = 'root'; 修改root用户允许所有ip访问, 默认只能localhost访问; 如果还不行 ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root'; flush privileges; 修改root用户的权限 (https://blog.csdn.net/iw1210/article/details/54646093)

version: "3.2"
services:
mysql:
image: mysql
ports:
- 3306:3306
environment:
# 校正时区
- TZ=Asia/Shanghai
# 设置编码
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
# 数据卷
volumes:
- ./data:/var/lib/mysql
# root 的密码, 不过貌似没有生效 不要紧 手动设置也可
environment:
- MYSQL_ROOT_PASSWORD=root

root, root 登陆

16.6. 使用postgres镜像

docker run -d --name Postgres -p 5432:5432 [-e POSTGRES_USER=dev] -e POSTGRES_PASSWORD=dev123 postgres:alpine

If the db users is not been specified, the password will be setup on the head of "postgres" (the db user which has a root access)

or

version: "3"
services:
postgresdb:
image: postgres
volumes:
#- ./data:/var/lib/postgresql/data # 挂载本地目录
# 使用数据卷
- pgdata:/var/lib/postgresql/data # data volume
ports:
- 5432:5432
# data volume 需要在这里声明
#
volumes:
pgdata:

可能错误: data directory "/var/lib/postgresql/data" has wrong ownership

ref: https://stackoverflow.com/questions/49148754/docker-container-shuts-down-giving-data-directory-has-wrong-ownership-error-wh

16.7. 使用 rabbitmq镜像

version: "3"
services:
rabbitmq:
image: rabbitmq:management
ports:
- "5672:5672"
- "15672:15672"

# 带 management 的版本有 web 界面
docker pull rabbitmq:3.8.3-management
# 15672 为 web ui 端口, 登录 guest/guest
docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.8.3-management

docker run -d --name rabbitmq3.7.7 -p 5672:5672 -p 15672:15672 -v `pwd`/data:/var/lib/rabbitmq --hostname myRabbit -e RABBITMQ_DEFAULT_VHOST=my_vhost -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin df80af9ca0c9

16.8. redis镜像

version: "3"
services:
redis:
image: redis
container_name: redis
command: redis-server --requirepass 123456
ports:
- "6379:6379"
volumes:
- ./db/redis:/data


docker run -p 6379:6379 -d redis:latest redis-server

# 数据持久化存方式启动
docker run -p 6379:6379 -v $PWD/data:/data --name redis -d redis redis-server --appendonly yes

# 自定义配置文件, 先创建 /usr/local/docker/redis.conf
docker run -p 6379:6379 --name myredis -v /usr/local/docker/redis.conf:/etc/redis/redis.conf -v /usr/local/docker/data:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes

# 带密码启动
docker run --name redis_with_auth -p 6379:6379 -d --restart=always redis:latest redis-server --appendonly yes --requirepass "@w-(*102Y613}-Redis"


# redis client
docker exec -ti d0b86 redis-cli [-h localhost -p 6379]
# remote client
docker exec -it redis_image redis-cli -h 192.168.1.100 -p 6379 -a your_password #如果有密码 使用 -a参数

redis.conf

bind 127.0.0.1 #注释掉这部分,这是限制redis只能本地访问
# or
# 允许外网访问redis
bind 0.0.0.0

protected-mode no #默认yes,开启保护模式,会限制为本地访问

daemonize no#默认no,改为yes意为以守护进程方式启动,可后台运行,除非kill进程,改为yes会使配置文件方式启动redis失败

databases 16 #数据库个数(可选)

dir ./ #输入本地redis数据库存放文件夹(可选)

appendonly yes #redis持久化(可选)

# 设置访问redis的密码为123(可选)
requirepass 123


redis-commander: 提供 redis web 管理界面

docker run --rm -d --name redis-cmder -p 8081:8081 --link redis:re -e REDIS_HOSTS=re:6379 rediscommander/redis-commander

16.9. ubuntu镜像

docker pull ubuntu
docker run -it ubuntu bash

apt update
# 安装 vim ping wget ifconfig
apt install vim iputils-ping wget net-tools zsh
# 2、把默认的Shell改成zsh
chsh -s /bin/zsh
# 安装 oh-my-zsh 需要先装git
apt install git
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

16.10. zookeeper

docker run --name some-zookeeper -d -p 2181:2181 zookeeper
# 查看日志
docker logs -f --tail=10 some-zookeeper
# 进入命令行 client (进入后 执行 zkCli.sh -server <ip>)
docker run -it --rm --link some-zookeeper:zk zookeeper zkCli.sh -server zk


###### zookeeper client cmd
ls / # 查看根节点
get /zookeeper

16.11. kafka

https://github.com/lensesio/fast-data-dev

https://hub.docker.com/r/bitnami/kafka/ https://flxdu.cn/2023/01/23/Kafka-in-Docker-%E5%8D%95%E8%8A%82%E7%82%B9-%E5%A4%9A%E8%8A%82%E7%82%B9/

https://github.com/provectus/kafka-ui

(可能碰到 springboot 找不到 卡夫卡的地址的问题, 需要吧 docker id 和 127.0.0.1 在 host 中映射)

version: "3"
networks:
kafka-tier:
driver: bridge
services:
# zookeeper:
# image: docker.io/bitnami/zookeeper:3.8
# container_name: zookeeper
# networks:
# - kafka-tier
# ports:
# - "2181:2181"
# environment:
# - ALLOW_ANONYMOUS_LOGIN=yes
kafka:
image: docker.io/bitnami/kafka:3.5
container_name: kafka
ports:
- "9092:9092"
networks:
- kafka-tier
environment:
- ALLOW_PLAINTEXT_LISTENER=yes

kafka-ui:
container_name: kafka-ui
image: provectuslabs/kafka-ui:latest
networks:
- kafka-tier
ports:
- "9093:8080"
depends_on:
- kafka
environment:
DYNAMIC_CONFIG_ENABLED: 'true' # not necessary, added for tests
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092




16.12. zipkin

https://zipkin.io/pages/quickstart.html

docker run --name zipkin-x -d -p 9411:9411 openzipkin/zipkin

http://localhost:9411/zipkin/

16.13. nginx

https://github.com/nginx-proxy/nginx-proxy 容器里的 nginx 反向代理

# 保证 nginx 下有 conf, conf 文件夹复制自 nginx docker 内部 (docker container cp nginx:/etc/nginx .)
docker run -d --name nginx -p 80:80 -v ~/docker_data/nginx/html:/usr/share/nginx/html -v ~/docker_data/nginx/logs:/var/nginx/logs -v ~/docker_data/nginx/conf:/etc/nginx nginx

16.14. mongodb

# --auth:需要密码才能访问容器服务。(默认没有账户密码)
docker run -d --name mongo -p 27017:27017 -v /Users/xiaoyu/one/docker_data/mongo/data:/data/db -v /Users/xiaoyu/one/docker_data/mongo/backup:/data/backup mongo:latest --auth


docker exec -it mongo mongo admin
# 创建一个名为 admin,密码为 123456 的用户。
> db.createUser({ user:'admin',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'},"readWriteAnyDatabase"]});
# 尝试使用上面创建的用户信息进行连接。
> db.auth('admin', '123456')

# 备份
docker exec mongo sh -c 'exec var=`date +%Y%m%d%H%M` &amp;&amp; mongodump -h localhost --port 27017 -u jsmith -p password -d dbname -o /data/backup/$var_test1.dat'


# 远程连接
docker run -it --rm mongo mongo --host 192.168.10.130 --port 27017

16.15. gitlab

# 拉取gitlab镜像
docker pull beginor/gitlab-ce:11.0.1-ce.0

# 创建 volume 目录
mkdir -p ./gitlab/etc ./gitlab/log ./gitlab/data
# 复制配置文件到宿主的 volume 目录里
# 可以手动复制,
# 也可以通过创建一个容器然后关闭来达到复制的效果 (推荐)
docker run --rm --privileged=true -v /root/gitlab/etc:/etc/gitlab -v /root/gitlab/log:/var/log/gitlab -v /root/gitlab/data:/var/opt/gitlab beginor/gitlab-ce:11.0.1-ce.0
# Ctrl+c 关闭, 此时配置文件被复制到宿主的 volume 目录了

# 修改 gitlab.rb,
# 添加 external_url 'http://<宿主机 host>:<宿主机 port>' (意思是告诉容器内的 gitlab, 在容器内部也要使用这个端口, 以和宿主机准备暴露的端口保持相同. 若宿主采用 80 作为 gitlab 对外端口, 则 external_url 的端口可省略, )
#
# attention!!! 这里端口配置为a_port, 那么 docker run 运行容器时 , 需要 -p a_port:a_port
#
cd gitlab
vim etc/gitlab.rb

# 修改 gitlab.yml
# 修改 gitlab.host: 宿主机ip地址, gitlab.port: 宿主机暴露的端口, gitlab.https: false
vim data/gitlab-rails/etc/gitlab.yml

# 若宿主准备使用默认端口 80, 那么可以 -p 80:80 , 此时修改 external_url 时, 可省略端口
docker run -d --name gitlab -p 8443:443 -p 80:80 --restart unless-stopped -v /root/gitlab/etc:/etc/gitlab -v /root/gitlab/log:/var/log/gitlab -v /root/gitlab/data:/var/opt/gitlab beginor/gitlab-ce:11.0.1-ce.0

# 自定义暴露端口为 8090, 则需要修改 external_url 时加上端口 8090
docker run -d --name gitlab -p 8443:443 -p 8090:8090 --privileged=true --restart unless-stopped -v /root/gitlab/etc:/etc/gitlab -v /root/gitlab/log:/var/log/gitlab -v /root/gitlab/data:/var/opt/gitlab beginor/gitlab-ce:11.0.1-ce.0

16.16. etcd

docker pull bitnami/etcd:latest
# 单节点
$ docker run -p 2379:2379 -e ALLOW_NO_AUTH=yes --name Etcd bitnami/etcd


16.17. influxdb

docker run -it --rm --name influxdb -p 8086:8086 influxdb:1.8.10

# 管理 gui
docker run -p 8085:80 -it --link influxdb:influxdb timeseriesadmin/timeseriesadmin:latest

17. kali

http://uni.mirrors.163.com/kali-images/kali-2020.4/ http://mirrors.ustc.edu.cn/kali-images/ http://old.kali.org/kali-images/kali-2020.4/

https://www.kali.org/docs/virtualization/install-virtualbox-guest-vm/

默认用户名是 root,默认密码是 toor kali/kali

18. 虚拟网卡 macvlan

https://fuckcloudnative.io/posts/netwnetwork-virtualization-macvlan/

19. 虚拟机管理工具

19.1. vagrant

https://github.com/hashicorp/vagrant

https://github.com/utmapp/UTM ios 平台, mac 平台

firecracker, kata 之类新轻量虚拟机

19.1.1. 基本使用

https://www.vagrantup.com/downloads.html 下载 https://www.virtualbox.org/wiki/Downloads virtualbox 下载 https://learn.hashicorp.com/collections/vagrant/getting-started 入门

http://www.vagrantbox.es/ 搜索 box https://app.vagrantup.com/boxes/search 搜索 box https://c4ys.com/archives/1230 国内镜像收集 (清华 centos)

如何手动下载 box? 例如 找到 kali 的 box 页面 https://app.vagrantup.com/kalilinux/boxes/rolling, 点进 指定版本的页面 https://app.vagrantup.com/kalilinux/boxes/rolling/versions/2020.4.0 , 那么可以 curl -L https://app.vagrantup.com/kalilinux/boxes/rolling/versions/2020.4.0/providers/virtualbox.box -O kali-2020.4.0.box

# verify the installation
vagrant -v

vagrant box list # list all available vm

# 下载好 box 文件然后
# 加载到 vagrant
vagrant box add <xxx_box_name> <box_file_Url_path>
vagrant init <xxx_box_name> #初始化, create a vagrantfile in current folder
vagrant up # 启动环境

# 也可以不下载 box file, 直接 init a box (box name can be find from some website), 但是可能很慢
vagrant init centos/7
vagrant up # 启动的系统

# 使用 vagrant 用户连接 虚拟机 , 不用密码, 若仅仅一个 machine, 无需指定 machine name
# 切换 root, 也可使用 root/vagrant 登录 or : sudo su
vagrant ssh [machine_name]
vagrant upload <source> [dest] [name|id] # 上传文件

vagrant reload # 重启
vagrant halt # 关闭虚拟机
vagrant status # 查看虚拟机运行状态
vagrant destroy # 销毁当前虚拟机

# 打包自己的 box 进行分发, 打包完成后会在当前目录生成一个 package.box 的文件
vagrant package


# 上传, 默认传到 vagrant home
vagrant upload xxx_file [dest_path]

19.1.2. vagrant 设置 ip

配置虚拟机为固定 ip: 修改 vagrantfile , 配置为私有网络 (需要先使用 virtualbox 的主机网络管理器配置新增 hostonly 网络)

config.vm.network "private_network", ip:"virtualbox_host_only_ip"

virtualbox_host_only_ip 就是 宿主机 中 virtualbox 的 hostonly ip, 如 192.168.56.1/24

其实 Vagrant 中一共有三种网络配置,

端口映射(Forwarded port):

config.vm.forwarded_port 80, 8080 将访问宿主计算机8080端口的请求都转发到虚拟机的80端口上
config.vm.forwarded_port 80, 8080, protocol: "udp" 默认只转发TCP包,UDP需要额外添加以下语句

好处:

简单易理解
容易实现外网访问虚拟机

坏处:

如果一两个端口需要映射很容易,但是如果有有很多端口,比如MySQL,MongoDB,tomcat等服务,端口比较多时,就比较麻烦。
不支持在宿主机器上使用小于1024的端口来转发。比如:不能使用SSL443端口来进行https连接。


私有网络(Private network),只有主机可以访问虚拟机,如果多个虚拟机设定在同一个网段也可以互相访问,当然虚拟机是可以访问外部网络的

config.vm.network "private_network", ip: "192.168.50.4" 这里 ip 为 宿主机 上 virtualbox 对应的 host only ip 地址

好处:

安全,只有自己能访问

坏处:

因为私有的原因,所以团队成员其他人不能和你写作

公有网络(Public network),虚拟机享受实体机器一样的待遇,一样的网络配置,vagrant1.3版本之后也可以设定静态IP

config.vm.network "public_network", ip: "192.168.1.120"

公有网络中还可以设置桥接的网卡
config.vm.network "public_network", :bridge => 'en1: Wi-Fi (AirPort)'

好处:
方便团队协作,别人可以访问你的虚拟机

坏处:
需要有网络,有路由器分配IP

19.1.3. VirtualBox四种网络模式

https://blog.csdn.net/qq_28513801/article/details/90138491

19.2. Multipass

类似 vagrant, 快速获取 Linux 环境, 基于 KVM 虚拟化技术,支持 Linux、macOS 和 Windows 平台

https://multipass.run/, it's easy to install on mac: brew install multipass --cask

# 查看可供下载的 Ubuntu 镜像
multipass find

multipass fetch # 命令下载镜像

# launch a local virtual linux
multipass launch
multipass launch --name=test1 --cpus=1 --mem=2048MiB
# -n:名称
# -c:cpu核数
# -d:磁盘大小
# -m:内存
multipass launch -n node-1 -c 1 -d 10G -m 1G

#
multipass list

multipass info # 命令,即可查看当前运行的虚拟机信息


multipass delete <instance name>
multipass purge # 清空所有 deleted instances

multipass stop/start

# 连接到虚拟机的命令行界面
multipass shell <name>

multipass exec <name> [cmd] # 命令在虚拟机中执行命令。

# 挂载数据卷
multipass mount <宿主机目录> <实例名>:<虚拟机目录>
#卸载数据卷
multipass umount 容器名

# 将宿主机的文件,发送到虚拟机内部
multipass transfer 主机文件 容器名:容器目录




# 通过配置文件初始化
multipass launch --name ubuntu --cloud-init config.yaml
# config.yml, 必须 #cloud-config 开头, 这是cloud-init识别它的方式。
#cloud-config
runcmd:
- curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
- sudo apt-get install -y nodejs

20. drone

基于 docker 的持续集成 ci/cd 工具