Getting Started
推送自己的image
和github类似,首先有一个hub来作为仓库,存储我们的image,以docker官方的hub为例,在https://hub.docker.com/上注册账号,并创建好repo。
登录账号
docker login -u YOUR_USER_NAME
在本地构建好镜像后,执行:
docker push YOUR_USER_NAME/repo_name
初次执行时,会执行失败,这是因为当前没有叫做YOUR_USER_NAME/repo_name的镜像(就可以docker image ls查看所有镜像),需要执行
docker tag getting-started YOUR_USER_NAME/repo_name
docker tag 用于给镜像打标签,语法如下:
https://www.kancloud.cn/noahs/linux/1557706
docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
类似于本地版本控制,可以为镜像打不同的tag,这样可以执行不同版本的镜像。同时可以关联到不同的仓库下,打完tag后,可以直接使用这个新tag来执行push和pull。
如果执行tag的时候没有给出tagname(冒号后的tag),那么docker会默认指定为latest
容器volume
volume是指容器用来挂载的空间,如果不挂载到宿主机的某个目录下,容器挂掉后,数据是没有持久化存储的,而且容器之间数据是不共享的,如果两个容器挂载了同一个目录,那么容器间就可以共享文件,和执行共同的文件操作。
命名volume
命名volume是指用名字指代的volume,类似于命名管道一样,不需要知道他具体挂载到了哪里,只要使用同一个名字的多个容器,都可以进行共享。
首先创建命名volume:
docker volume create todo-db
在启动容器时,需要指定volume挂载到宿主机哪个目录
docker run -dp 2000:3000 -v todo-db:/etc/todos getting-started
-v参数后跟的是 本地挂载路径:容器中的路径
由于是命名volume所以不知道确切的本地路径,而在容器中的/etc/todos进行文件操作,能够在宿主机和挂载了todo-db的其他容器中可见
查看命名volume的信息
docker volume inspect todo-db
# output
[
{
"CreatedAt": "2021-11-29T12:55:36Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
"Name": "todo-db",
"Options": {},
"Scope": "local"
}
]
因为是命名volume,我们没有显示的指定挂载的路径,所以docker会帮我们决定一个路径,这个路径通常只能root用户访问。
我们可以不用docker volume create,直接在docker run的时候使用一个新的命名volume名字,docker会自动识别,会自动帮我们创建一个新的命名volume。
绑定挂载
这个其实就是我们指定本机的一个路径,然后和容器中的一个路径映射,也就是上面的命名volume改成一个本机路径而已
docker run -d -v "$(pwd)/helloworld:/container/dir" image:tag
命名volume和绑定挂载的区别:

需要注意的是,如果挂载的本机路径没有容器需要的东西,例如getting_started这个镜像需要/app/package.json才能正常启动,如果此时-v /my_dir:/app,如果my_dir下没有package.json,那么会启动失败,因为容器找不到该文件:

docker run命令行参数
-it:表示interactive 以交互的方式运行容器
-d:detach,表示后台运行容器
-p:端口映射,宿主机端口:容器端口,例如2000:3000,访问宿主机的2000端口,就会访问到容器,容器里的服务绑定的端口为3000
-w:指定容器的工作路径,-w /app,同时也是命令(通常是sh)的工作路径
-e:指定容器执行所需的环境变量,-e MYSQL_ROOT_PASSWORD=secret
查看容器日志
docker logs container_id
docker logs -f container_id 能够实时查看日志,类似tail -f
或者
1、获取日志文件路径
docker inspect --format '{{.LogPath}}' container_id
2、打开日志文件
进入容器内部
首先要容器已经启动了
然后
docker exec -it container_id /bin/bash
不一定所有的容器都可以执行/bin/bash这,要和具体的镜像来看。
例如,mysql的镜像不包含这个sh可执行程序,不能这么交互,在mysql镜像中/bin/bash这个命令要相应替换成mysql -hxx -uxxx- pxxx
获取镜像
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
Docker Registry 地址是指docker镜像所存放的仓库地址,默认是docker.io
不给出则使用默认值,也可以自己指定要拉取的地址
仓库名一般对应就是你的项目(也就是你的镜像名),然后标签表示镜像的版本。
查看镜像信息
docker image ls -a
docker inspect image_name:tag
返回的是一大串json信息,描述了这个镜像
容器通信
每个容器都是独立而且隔离的,是不知道别的容器的,想要容器之间通信,那么就需要使用network,只有在同一个网络的容器才能通信。
容器网络有三种类型,bridge、host、null,默认创建的都是bridege类型
docker network create todo-app
上面的代码指定了一个网络,名为todo-app,后续启动容器时可以使用--network指定通信的网络:
docker run -d \
--network todo-app --network-alias mysql \
-v todo-mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=todos \
mysql:5.7
--network-alias指定了这个容器在网络中的别名,类似于hostname,docker可以自动解析这个别名得到容器的地址和端口(相当于docker做了一个dns解析)。
在上面的启动代码中可以看到,容器不一定要向外暴露端口,也即主机不一定要访问通过ip+端口来访问容器的服务,可以容器在自己内部玩,而内部的端口也要注意不能冲突,具体内部使用的是什么端口,这是由镜像决定的,例如mysql内部用的是3306。
docker-compose
docker-compose是一个容器编排的工具,通过编写docker-compose.yml文件,按照语法和关键词设置好每一个镜像的启动方式,其实每一个容器的配置对应的都是使用docker run的命令参数。
这里给出一个简单的例子:
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 2000:3000
working_dir: /app
volumes:
- ./:/app
environment:
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD: secret
MYSQL_DB: todos
mysql:
image: mysql:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
volumes:
todo-mysql-data:
其中,version指的是当前使用的docker-compose的版本,和cmake如出一辙,起始先写好版本。services中的每一项(第二级)对应的是每一个容器的名字,这个名字就是network-alias,docker会自动为services中每一个容器创建一个network,无需我们操作指定。
在使用命名volume时,docker-compose不像docker run那样会自动帮我们创建volume,我们需要指定,所以要在第一级volumes对象中,指定volume的名字,如果没有特殊的配置,可以在冒号后省略,可配置的参数可参考:https://docs.docker.com/compose/compose-file/compose-file-v3/#volume-configuration-reference
环境变量在environment项中给出。
在使用docker-compose.yml文件启动编排的服务时,需要确保相关的相似配置的容器没有启动,否则会冲突,例如端口重复绑定。
在docker-compose.yml文件所有的目录下,执行docker-compose up -d,以运行容器,其中-d也是detach后台启动的意思。
如果想要查看容器的日志,可以使用:
docker-compose logs -f
docker-compose logs -f service_name
如果服务之间有启动的先后关系,例如先要启动了mysql,然后再启动具体服务,那么需要自己来保证,因为docker没有内置的方法来确定哪个容器已经完全起好了。
启动之后,docker-compose会默认以yml文件所在的目录名作为项目名,所以容器实例的名字格式为:
projectname_containername_replica
项目名_容器名_实例号
想要全部一次性下线服务的话,使用docker-compose down
要注意,这个命令不会去删除volume,如果要删除的话,我们需要使用标志位:
docker-compose down --volumes
一些docker实践的方法
安全性检查
使用docker scan image_name,可以检查镜像的安全性,不过检查前要登陆docker hub,使用Snyk来检查,需要连接。
查看镜像的层级
docker image history image_name
可以查看镜像每一层的大小:

输出结果中,最底层的在最下面,最顶层的在在上面。
镜像层缓存
docker镜像是一层层的结构,如果底层的已经有了,就会缓存起来,不需要再次拉取。
假设层级为A(最高)、B、C、D(最底)。
某一层级发生变化后,其上的所有层都需要重新构建,例如B变更,则A也要重新构建。可以理解成下面的是上面层的地基,地基变了,上层自然跟着变。
我们写的Dockerfile,其中每一行命令都对应了一层layer,所以我们编写的时候就需要考虑顺序问题;在Dockerfile中,从上往下阅读,越先执行的就是越底层的layer。
# 1
FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
# 2
FROM node:12-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "src/index.js"]
上面第二个的写法会比第一个好,如果我们修改了任何一个文件,此时copy . .对应的layer变更了,其上的所有layer都要变更,所以yarn要重新执行安装。
而第二种写法中,只有修改了package.json、yarn.lock文件才会执行yarn的reinstall。
使用.dockerignore文件,在文件中指定要忽略的目录或者文件名,例如:
# .dockerignore
node_modules
要保证文件名/目录要和.dockerignore文件在同一级下。
多阶段构建
https://docs.docker.com/develop/develop-images/multistage-build/
我们可能会存在使用某个镜像,来编译依赖项和最终的可执行文件,如果编译和最后的服务启动相关的环境都在同一个镜像中,就会很大,因为我们需要提供编译能力的镜像层;要么就是我们手动先用镜像A编译了代码,然后将可执行文件复制到另外一镜像,再进行启动,中间还需要写一个脚本:
https://yeasy.gitbook.io/docker_practice/image/multistage-builds
使用多阶段构建可以在一个Dockerfile中实现这个能力:
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package
FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
每个From可以指定使用不同的基础镜像,可以看到第二个Copy --from=build,表示从前面构建好的结果,复制到另外一个镜像,其中build就是第一个镜像的别名,如果不使用别名as,那么可以用索引,从0开始--from=0。
每一个from对应的是一个stage,最终镜像是最后的stage指定的内容,例如上面的在build构建后,镜像是tomcat。我们也可以在构建时,使用--target来指定。
docker build --target builder -t alexellis2/href-counter:latest .
builder是From xxx as YYY中的YYY,我们可以指定构建到某个stage。
查看容器启动命令
docker inspect container-id
docker run -itd --name test1 --network bridge --ip 172.17.0.10 centos:latest
/bin/bash