Docker使用手册
docker官网教程(学习一样新东西最快的方法是去看官方文档,讲解的清楚,教程简单,但是包括的面十分的广,只记录自己觉得有用的命令,详细请去官网查看Docker start),其中也发现一篇很好的教程,简洁明了,我也在这里学到了很多,也摘录总结在了这里,推荐大家去Docker--从入门到实践进行学习。也可以fork那个项目下来进行贡献。如果网速有问题可以docker一个,直接本地访问(教程提供的方法,我只是搬运一下)
$ docker pull dockerpracticecn/docker_practice
$ docker run -it --rm -p 4000:80 dockerpracticecn/docker_practice
访问127.0.0.1:4000即可
- 初识Docker
了解docker的祖师级别的语句,如同代码里的hello-world
docker run hello-world
查看版本
docker --version
- 容器
需要了解dockerfile,docekrfile其实很好懂,容器是由镜像创建的,镜像呢,现在来说是dockerfile创建的。以下三个文件是
Dockerfile
# Use an official Python runtime as a parent image
FROM python:2.7-slim
# Set the working directory to /app
WORKDIR /app
# Copy the current directory contents into the container at /app
ADD . /app
# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt
# Make port 80 available to the world outside this container
EXPOSE 80
# Define environment variable
ENV NAME World
# Run app.py when the container launches
CMD ["python", "app.py"]
requirements.txt
Flask
Redis
app.py
from flask import Flask
from redis import Redis, RedisError
import os
import socket
# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
app = Flask(__name__)
@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"
html = "<h3>Hello {name}!</h3>" \
"<b>Hostname:</b> {hostname}<br/>" \
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)
创建app(注意有个点)
docker build -t friendlyhello .
找到新建的镜像
$ docker images
REPOSITORY TAG IMAGE ID
friendlyhello latest 326387cea39
跑起来
docker run -d -p 4000:80 friendlyhello
好不容易创建了一个镜像,想分享一下,肿么办
登录
docker login
打标签
docker tag image username/repository:tag
举个例子
docker tag image yugougou/get-started:part2
这里的于狗狗是你的用户名,get-started是仓库名
公布镜像
docker push username/repository:tag
例如
docker push yugougou/get-started:part2
测试(拉一把 看看能不能跑起来)
docker run -p 4000:80 username/repository:tag
例如一个
docker run -p 4000:80 yugougou/get-started:part2
如果能成功跑起来,那就没什么问题了
- 服务
要是想要起服务的话,那就要用上编排工具了 docker-compose.yml
docker-compose.yml(里面有services还有networks)
version: "3"
services:
web:
# replace username/repo:tag with your name and image details
image: yugougou/get-started:part2
deploy:
replicas: 5
resources:
limits:
cpus: "0.1"
memory: 50M
restart_policy:
condition: on-failure
ports:
- "80:80"
networks:
- webnet
networks:
webnet:
启动第一个负载均衡的app,肯定是少不了要用集群的,这里使用的镜像就是上一节我们制作的镜像。
docker swarm init
启动它 给他起一个漂亮的名字
docker stack deploy -c docker-compose.yml getstartedlab
看看我们起的服务如何
docker service ls
yugougou@yugougoudeMacBook-Pro:~/Desktop $docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
9cinwb60b78b getstartedlab_web replicated 10/10 yugougou/get-started:part2 *:80->80/tcp
看看服务里的任务有哪些吧
docker service ps getstartedlab_web
yugougou@yugougoudeMacBook-Pro:~/Desktop $docker service ps getstartedlab_web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
tgbba2qixjoa getstartedlab_web.1 yugougou/get-started:part2 moby Running Running 6 minutes ago
eg9oo085u2ud getstartedlab_web.2 yugougou/get-started:part2 moby Running Running 6 minutes ago
yjc31x8ac2o0 getstartedlab_web.3 yugougou/get-started:part2 moby Running Running 6 minutes ago
dmoye5s5n09x getstartedlab_web.4 yugougou/get-started:part2 moby Running Running 6 minutes ago
q16fo41swnai getstartedlab_web.5 yugougou/get-started:part2 moby Running Running 6 minutes ago
idkc65whadin getstartedlab_web.6 yugougou/get-started:part2 moby Running Running 6 minutes ago
s6k7b8o81m35 getstartedlab_web.7 yugougou/get-started:part2 moby Running Running 6 minutes ago
rtod8scwkwt7 getstartedlab_web.8 yugougou/get-started:part2 moby Running Running 6 minutes ago
z29qubyqa29b getstartedlab_web.9 yugougou/get-started:part2 moby Running Running 6 minutes ago
pbq7qr4e8jhv getstartedlab_web.10 yugougou/get-started:part2 moby Running Running 6 minutes ago
只查看端口
docker ps -q
哎呀 发现之前的编排文件dockerfile里的副本主机起的不够,肿么办,面对这种状况,分两步,第一步打开冰箱,不是 第一步修改dockerfile,然后重启喽,记得名字不要写错
yuzhipeng@yuzhipengdeMacBook-Pro:~/Desktop $docker stack deploy -c docker-compose.yml getstartedlab
Updating service getstartedlab_web (id: 9cinwb60b78bmon03mjakt6mb)
其实我什么也没有改--__--
关掉app(关掉之前用docker service ls 看看)
docker stack rm getstartedlab
(关掉之前用docker service ls 看看)
砸了集群
docker swarm leave --force
(再用docker service ls看看)
- 集群 Swarms
大家好 我是分隔符
docker run hello-world
docker --version
docker pull --help
运行jenkins(需要制定端口):
docker run --name myjenkins -p 8080:8080 index.alauda.cn/alaudaorg/alauda-jenkins
查看:
docker ps -a | grep jenkins
进入docker
docker exec -ti c3c bash
编辑docker
cat /var/run/docker.sock
docker login index.alauda.cn
docker-compose up -d
拉取并运行镜像: docker run --name webserver -d -p 80:80 nginx
删除容器: docker container rm webserver
停止容器:docker stop containername
进入容器修改: docker exec -i -t webserver bash
今天要总结一下近期来使用学习docker的经验,很早之前就想来摸索一下docker,但是前些日子一直在忙于找工作,没什么时间,但是现在找到的公司做的是基于docker平台开发paas服务的公司,所以借机好好刷一把docker
Docker的安装
sudo apt-get update
安装 这是用比较新的方法 也可以用
sudo apt-get install docker.io
但是这个下载的版本会比较低 不建议这样下载
sudo apt-get install curl
curl -sSL https://get.docker.com/ | sh
下载完毕启动Docker的守护进程
sudo service docker start
检查Docker是否安装成功
sduo docker run hello-world
如果提示Hello from Docker!
当然是里面有提示,并不是一开始的提示就是这个,说明安装成功了提示(Ubnable to find image 'hello-world lateset' locally)
如果不想总是输入sudo 可以输入以下命令解决
sudo usermod -aG docker yuzhipeng
体验Docker
使用docker指令创建 启动几个Docker应用,比如WordPress,gitlab服务
- 通过搭建WordPress来试试Docker
这几个应用下载的话会比较慢 要等几分钟
sudo docker run --name db --env MYSQL_ROOT_PASSWORD=example -d mariadb
sudo docker run --name MyWordPress --link db:mysql -p 8080:80 -d wordpress
--name参数创建了两个Docker容器,db和MyWordPress 通过docker ps可以查到名字
通过ifconfig查看本机的IP地址,在本机地址后加上端口号8080,会有惊喜。简直不能更爽
这个我在京东云上部署了一下,感觉用起来很快,部署效果非常好,比之前部署项目快的过,真的是几条命令就可以跑起来服务,厉害了 访问
- 搭建Gitlab服务
首先启动postgresql
sudo docker run --name gitlab-postgresql -d --env 'DB_NAME=gitlabhq_production' --env 'DB_USER=gitlab' --env 'DB_PASS=password' sameersbn/postgresql:9.4-12
然后启动redis
sudo docker run --name gitlab-redis -d sameersbn/redis:latest
最后启动gitlab
sudo docker run --name gitlab -d --link gitlab-postgresql:postgresql --link gitlab-redis:redisio --publish 10022:22 --publish 10080:80 --env 'GITLAB_PORT=10080' --env 'GITLAB_SSH_PORT=10022' --env 'GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alpha-numeric-string' sameersbn/gitlab:8.4.4
后来就可以访问端口10080即可进入gitlab,确实是太好用了,这时候用户名为root,密码是yu123456 连接(如果可用) 点击进入
- 项目管理系统 Redmine
要使用saneersbn/redmine的镜像。两条指令
sudo docker run --name=postgresql-redmine -d --env='DB_NAME=redmine_production' --env='DB_USER=redmine' --env='DB_PASS=password' sameersbn/postgresql:9.4-12
docker run --name=redmine -d --link=postgresql-redmine:postgresql --publish=10083:80 --env='REDMINE_PORT=10083' sameersbn/redmine:3.2.0-4
10分钟小任务认识Docker
版本号
docker version
查找镜像 (镜像的全称是 <username>/<repository>
)
docker search tutorial
下载镜像
docker pull learn/tutorial
docker ps -l
用docker run来创建和运行docker容器(docker可以创建容器并在容器中运行指定的命令)
docker run learn/tutorial echo "hello world"
修改容器(安装ping)
docker run learn/tutorial apt-get install -y ping
通过docker ps -l找出安装过ping包的容器的ID号,
docker ps -l
然后将容器提交为新的镜像,这时会返回一个新的ID便是新生成的镜像的ID
docker commit 09c2e9353f01 learn/ping
在基于新镜像的容器中执行 ping www.google.com这条指令(新镜像要使用全名 learn/ping)
docker run learn/ping ping www.google.com
查询容器信息
docker ps 查询所有运行的容器
docker inspect a102 查看单个容器的信息(根据docker ps列出的容器名,选取前三四个字符即可)
新镜像上传仓库
docker images 查询本机的镜像列表
查询结果中有
REPOSITORY TAG IMAGE ID CREATED SIZE
learn/ping latest 714c84471b9f 11 minutes ago 139MB
所以把镜像推送到Docker官仓
docker push learn/ping
Docker常用词
查看所有的镜像
docker images
查看相关进程
sudo docker ps
查看版本
docker version
ps只是看一些大体容器内容 inspect是看容器的详细内容
docker ps
docker inspect gitlab
docker push michelesr/ping
docker基础概念与常用命令
1.仓库、镜像、容器
2.基本指令
docker指令操作对象主要针对四个方面:
1 针对守护进程的系统资源设置和全局信息的获取: docker info, docker deamon
2 针对Docker仓库的查询,下载 : docker search, docker pull
3 针对docker镜像的查询,创建与删除: docker images, docker build
4 针对docker容器的查询,创建,开启,停止: docker ps, docker run
5 支持赋值,变量解析,嵌套
docker+命令关键字(command)+一系列参数([arg ])
例如
docker run --name MyWordPress --link db:mysql -p 8080:80 -d wordpress
基于wordpress镜像创建容器MyWordPress,通过docker ps可以查到名字MyWordPress,使用的镜像是woedpress的容器
获取帮助
docker command --help
Docker容器管理
容器标识符
每一个容器创建后都有一个CONTAINER ID作为容器的唯一的标识符,后续对容器的操作都是通过CONTAINER ID来完成,一般docker ps展示前16位,如果查询所有可以使用docker ps --no-trunc
查询容器状态
docker ps -a | grep d0131ae38d9d
停止容器
docker stop d0131ae38d9d
运行容器
docker start d0131ae38d9d
CONTAINER ID比较难记忆,所以创建容器时会有--name来给容器起一个别名 所以用别名也可以
查询容器信息(使用-f时可以用golang的模板提取出制定信息)
docker inspect -f {{.NetworkSettings.IPAddress}} MyWordPress
查询日志
docker logs MyWordPress
查询容器占用的系统资源
docker stats MyWordPress
容器内部命令
- 单容器
需求:登入Docker容器执行命令
方案: Docker提供原生的方式支持登入docker exec
docker exec + 容器名 + 容器内执行的命令
查看MyWordPress容器内的进程
docker exec MyWordPress ps aux
在容器内连续执行命令 加上 -it 参数即可,相当于以root身份登入,完成后通过 exit 退出
docker exec -it MyWordPress /bin/bash
- 多容器
Docker理念是‘一个容器一个进程’,如果一个服务由多个进程组成,就创建多个容器组成一个系统
在同一个主机下,docker run命令提供 ‘--link’建立容器之间的互联,而且他们是有顺序的,比如如果创建B容器的时候需要使用 ‘--link containerA’,那么创建B的时候,那么
在创建B容器的时候A容器必须已经创建且已经启动
对于WordPress,数据库容器(db)要先于Apache容器(MyWordPress)启动,所以启动方式应该是
docker start db
docker start MyWordPress
停止WordPress服务,先停止Apache容器(MyWordPress),再停止数据库容器(db),或者同时停止这两个容器
docker stop db MyWordPress
- Docker compose
但是如果比较多的容器启动的话就会比较麻烦,本着偷懒的原则,Docker提供了一个容器编排工具--Docker Compose,允许用户用一个模板定义一组相关联的应用容器,会根据配置模板的‘--link’参数对启动的优先级进行排序,只用‘docker-compose up’一条语句即可将一个服务中多个容器依次创建与启动
安装 Docker compose:(在https://github.com/docker/compose/releases/download这里可以找到随时更新的命令)
curl -L https://github.com/docker/compose/releases/download/1.18.0-rc2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
使用1:
- 首先关掉WordPress的两个容器
docker stop db MyWordPress
-
创建一个文件夹 ~/wordpress,文件夹下创建docker-compose.yml的文件:
wordpress:
image: wordpress
links:
- db:mysql
ports:
- 8080:80
db:
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: example
作用:创建了两个容器wordpress跟db,使用image指定了使用的镜像 -
创建启动WordPress服务
cd ~/wordpress &&docker-compose up
- 检测
开另一个命令终端, 输入docker ps查看容器
这是启动停止就变的简单了,
启动:
docker-compose start
停止:
docker-compose stop
- 删除原来的容器
查看所有容器(删除与未删除的)
docker ps -a
删除(根据名字(NAMES)来删除)
docker rm MyWordPress db
硬删的话就用 docker rm -f
使用2:gitlab改造
原来的用法
首先启动postgresql
sudo docker run --name gitlab-postgresql -d --env 'DB_NAME=gitlabhq_production' --env 'DB_USER=gitlab' --env 'DB_PASS=password' sameersbn/postgresql:9.4-12
然后启动redis
sudo docker run --name gitlab-redis -d sameersbn/redis:latest
最后启动gitlab
sudo docker run --name gitlab -d --link gitlab-postgresql:postgresql --link gitlab-redis:redisio --publish 10022:22 --publish 10080:80 --env 'GITLAB_PORT=10080' --env 'GITLAB_SSH_PORT=10022' --env 'GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alpha-numeric-string' sameersbn/gitlab:8.4.4
改造结果
postgresql:
image: sameersbn/postgresql:9.4-12
environment:
- DB_USER=gitlab
- DB_PASS=password
- DB_NAME=gitlabhq_production
redis:
image: sameersbn/redis:latest
gitlab:
image: sameersbn/gitlab:8.4.4
links:
- redis:redisio
- postgresql:postgresql
ports:
- "18008:80"
- "18009:22"
environment:
- GITLAB_PORT=18008
- GITLAB_SSH_PORT=18009
- GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alphaumeric-srting
创建一个项目~/gitlab,然后将上面放在docker-compose.yaml并放在该项目下
删除旧容器:
docker rm -f gitlab gitlab-redis gitlab-postgresql
启动新容器组
cd ~/gitlab/ && docker-compose up -d
登录
http://23.99.104.249:18008/
root/yu123456
Docker镜像管理
docker镜像查看
docker images -a
查看镜像分了多少层
docker history sameersbn/redis
方式:通过对容器的可写层修改来生成新镜像
问题:如果底层镜像出了问题,或者达到两个文件系统允许的最多层数(aufs最多支持128层)
解决方式: dockerfile
Docker 仓库管理
作用:镜像的存储,是镜像分发部署的关键
两种: 官方共有仓库 Docker hub,自己搭建私有仓库
- 共有仓库
上传镜像:
docker push ubuntu
搜索镜像:
docker search centos
下载镜像:
docker pull centos
- 私有仓库
使用docker-registry构建
方式1:
从Docker-registry拉取镜像
docker run -p 5000:5000 registry
Docker 网络和存储管理
- Docker 网络
默认情况下使用网桥(bridge)+ NAT的通信模型
Docker在启动是默认自动创建网桥设备Docker0,同一个host的容器与容器之间可以通过docker0 通信
容器与外部网络之间通信使用NAT
- Docker数据管理
Docker数据卷(data volume) 可以持久化数据,用于容器之间共享数据
- docker可以在容器内部创建一个数据卷,但是在删除之后,如果没有其他容器引用该数据卷,对应的Host目录就会被删除。
- 不想被删除的话,可以挂载Host的目录到数据卷,作为容器的数据卷(将Host上的/data/volume1挂载容器中的 /volume1可以在Host与容器之间进行数据交换,这时容器中的数据可以写到volume1上,即使容器被删除,数据仍然保留到Host上)
- 挂载Host的文件作为数据卷
主要应用于Host与容器之间共享配置文件,否则每个配置文件一个镜像会造成镜像版本过多,管理不便
数据卷容器
备份、恢复和迁移数据卷
Docker 小点
容器:
按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。
数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。
运行镜像
$ docker run -it --rm \
ubuntu:16.04 \
bash
-
-it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。
-
--rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。
-
ubuntu:16.04:这是指用 ubuntu:16.04 镜像为基础来启动容器。
-
bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash。
-
列出镜像
docker image ls
- 查看镜像、容器、数据卷所占用的空间
docker system df
- 删除镜像
docker image rm imageid
docker image rm $(docker image ls -q redis) //删除大法
- 一行码感受一下docker的威力
$ docker run --name webserver -d -p 80:80 nginx
Dockerfile
书写Dockerfile
touch Dockerfile
vi Dockerfile
//键入
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
Dockerfile 中每一个指令都会建立一层,RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。
一个比较屌的镜像 (其实run指令里面的命令就是shell命令)
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
所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个 RUN 对应不同的命令,而是仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来。
一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步
Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式
运行Dockerfile
$ docker build -t nginx:v3 . //假设该该目录下含有Dockerfile 注意后面有个点,后面会解释
指定了最终镜像的名称 -t nginx:v3
生成的镜像
查看镜像运行历史
docker history nginx:v3
通过镜像运行容器
docker run --name web2 -d -p 81:80 nginx:v2
此时通过127.0.0.1:81访问即可访问到docker服务
点的解释(镜像构建上下文(Context))
-
原理
docker build
的工作原理。Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如docker
命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种docker
功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。 -
使用场景
当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。在这种客户端/服务端的架构中,服务端应该如何获得本地文件,这就是这个上下文的使用场景。 -
使用方式
当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
如果在 Dockerfile 中这么写:
> COPY ./package.json /app/
这并不是要复制执行 docker build 命令所在的目录下的 package.json,也不是复制 Dockerfile 所在目录下的 package.json,而是复制 上下文(context) 目录下的 package.json。
- 应用
刚才的命令 docker build -t nginx:v3 . 中的这个 .,实际上是在指定上下文的目录,docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。
Dockerfile命令
- COPY
格式
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]
COPY
指令将从构建上下文目录中 <源路径>
的文件/目录复制到新的一层的镜像内的 <目标路径>
位置。比如:
COPY package.json /usr/src/app/
<源路径>
可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match
规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径>
可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR
指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
-
ADD
可以自动解压缩 如果是链接也会自动下载,但是功能环境不同,各种功能不能完全预测,所以最佳实践会推荐
所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。 -
CMD 容器启动命令
Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。
在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu 镜像默认的 CMD 是 /bin/bash,如果我们直接 docker run -it ubuntu 的话,会直接进入 bash。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。 -
ENTRYPOINT 入口点(RUN或者其他命令要与参数空一个空格)
ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。
ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。
当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<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 run myip
当前 IP:61.148.226.66 来自:北京市 联通
嗯,这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加参数呢?比如从上面的 CMD 中可以看到实质的命令是 curl,那么如果我们希望显示 HTTP 头信息,就需要加上 -i 参数。那么我们可以直接加 -i 参数给 docker run myip 么?
$ docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
显然出了问题,但是entrypoint就不会有这样的问题
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
这次我们再来尝试直接使用
docker run myip -i:
$ docker run myip
当前 IP:61.148.226.66 来自:北京市 联通
$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive
当前 IP:61.148.226.66 来自:北京市 联通
没有问题,最后的-i被entrypoint当做参数加进去了。
- ENV 设置环境变量
格式有两种:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。对含有空格的值用双引号括起来的办法
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
- ARG 构建参数
格式:ARG <参数名>[=<默认值>]
构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。
Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。
- VOLUME 定义匿名卷
格式为:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
VOLUME /data
这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:
docker run -d -v mydata:/data xxxx
在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。
- EXPOSE 声明端口
格式为 EXPOSE <端口1> [<端口2>...]。
要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
-
WORKDIR 指定工作目录
格式为 WORKDIR <工作目录路径>。
使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。 -
USER 指定当前用户
格式:USER <用户名>
USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。
当然,和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
- HEALTHCHECK 健康检查
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
这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用
curl -fs http://localhost/ || exit 1
作为健康检查命令。