Docker从入门到实践

  • 基本概念

  1. 镜像
    Docker 镜像就是一个只读的模板,镜像可以用来创建 Docker 容器
  2. 容器
    容器是从镜像创建的运行实例。 它可以被启动、 开始、 停止、 删除。 每个容器都是相互隔离的、 保证安全的平台
  3. 仓库
    仓库是集中存放镜像文件的场所.
    仓库注册服务器上往往存放着多个仓库, 每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)最大的公开仓库是 Docker Hub, 存放了数量庞大的镜像供用户下载。 国内的公开仓库包括 Docker Pool等, 可以提供大陆用户更稳定快速的访问。
    push命令上传到仓库
    pull命令拉取一个镜像
  • CENTOS 7 安装Docker

#安装Docker
sudo yum install docker
#启动Docker服务
sudo service docker start
#随系统自动加载
sudo chkconfig docker on

镜像

1. 从仓库获取镜像

命令
docker pull ubuntu:12.04
上面命令相当于
docker pull registry.hub.docker.com/ubuntu:12.04
docker pull nginx

#创建一个容器,让其运行bash应用
docker run -t -i ubuntu:12.04 /bin/bash
docker run -it nginx /bin/bash
#列出本地镜像
docker images
image.png
2. 管理本地主机上的镜像
  • 修改已有的镜像
  1. 先启动容器得到容器ID

[root@www docker]# docker run -it nginx /bin/bash
root@5f42bff05683:/#

5f42bff05683即为容器ID

  1. 安装其他的应用
  2. 提交更新后的副本

[root@www docker]# docker commit -m "ADD TEST" -a "ZZJ" 5f42bff05683 myrepository/nginx:20180101
sha256:e365f0423f76ee746c7226f4e164fd6f98094e636d9928290694279eb55564cf
myrepository/nginx:20180101 为:
仓库名:tag信息

image.png
  • 利用Dockerfile来创建镜像
mkdir springboot_hello
touch Dockerfile

Dockerfile文件内容

#This is a comment
FROM ubuntu:14.04
MAINTAINER Docker Newbee <newbee@docker.com>
RUN apt-get -qq update
RUN apt-get -qqy install ruby ruby-dev
RUN gem install sinatra
  • #表示注释
  • FROM 表示基于哪个镜像
  • MAINTAINER 是维护者的信息
  • RUN表示在创建中运行
    编写完Dockerfile后用docker build生成镜像
    docker build -t="ouruser/sinatra:v2" .
  1. -t 标记来添加tag
  2. "." 是Dockerfile所在的路径,可以替换为其他
  3. ADD命令复制本地文件到镜像
  4. EXPOSE命令向外部开放端口
  5. 用CMD命令描述容器启动后运行的程序
  6. 用docker tag 命令修改镜像的标签

如:

# put my local web site in myApp folder to /var/www
ADD myApp /var/www
# expose httpd port
EXPOSE 80
>\# the command to run
CMD ["/usr/sbin/apachectl", "-D", "FOREGROUND"]

docker tag 5db5f8471261 ouruser/sinatra:devel

镜像不能超过127层

  • 导入本地镜像
    cat ubuntu-14.04-x86_64-minimal.tar.gz |docker import - ubuntu:14.04
  • 上传镜像
    docker push ouruser/sinatra
  • 存出镜像(保存镜像到本地文件)
    sudo docker save -o ubuntu_14.04.tar ubuntu:14.04
  • 载入镜像(将本地保存出来的镜像导入到镜像库)
docker load --input ubuntu_14.04.tar
或者
docker load < ubuntu_14.04.tar
  • 移除本地镜像
    docker rmi training/sinatra:[tag]
  • 移除容器
    docker rm ...
注: 移除镜像之前要先移除该镜像的所有容器
3. 镜像的基本原理

Union FS

Docker容器

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

  • 启动容器
  • 新建并启动容器

[root@www springboot_hello]# docker run -it docker.io/centos:latest /bin/echo "Hello World"

Docker run在后台的标准操作有:

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

docker start

  • 守护态运行

docker run -d docker.io/centos:latest /bin/bash -c "while true ;do echo hello world done;"
docker ps 查看容器信息
docker logs 查看容器的输出信息

  • 停止容器

docker stop

  • 当Docker容器中指定的应用终结时, 容器也自动终止
    docker ps -a 可看到终止状态的容器
    docker restart 重启一个容器

  • 进入容器
  1. docker attach
    但是使用 attach 命令有时候并不方便。 当多个窗口同时 attach 到同一个容器的时候, 所有窗口都会同步显示。 当某个窗口因命令阻塞时,其他窗口也无法执行操作了。
  2. nsenter
    安装命令如下:

$ cd /tmp; curl https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz | tar
$ ./configure --without-ncurses
$ make nsenter && sudo cp nsenter /usr/local/bin

$ wget https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz; tar xzvf util-
$ cd util-linux-2.24
$ ./configure --without-ncurses && make nsenter
$ sudo cp nsenter /usr/local/bin

获取进程的PID
PID=$(docker inspect --format "{{ .State.Pid }}" <container>)

image.png

wget -P ~ https://github.com/yeasy/docker_practice/raw/master/_local/.bashrc_docker;
https://github.com/yeasy/docker_practice

  • 导出容器快照

docker export 7691a814370e > ubuntu.tar

  • 导入容器快照

cat ubuntu.tar | sudo docker import - test/ubuntu:v1.0
docker import http://example.com/exampleimage.tgz example/imagerepo
注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库, 也可以使用 docker import 来导入一个容器快照到本地镜像库。 这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态) , 而镜像存储文件将保存完整记录, 体积也要大。 此外, 从容器快照文件导入时可以重新指定标签等元数据信息。

  • 删除容器

docker rm -f ...

docker rm -f $(docker ps -a -q)
docker images|grep none|awk '{print $3 }'|xargs docker rmi

Docker仓库

仓库(Repository) 是集中存放镜像的地方
对于仓库地址 dl.dockerpool.com/ubuntu 来说, dl.dockerpool.com 是注册服务器地址, ubuntu 是仓库名

Docker Hub

  • 登录
    可以通过执行 docker login 命令来输入用户名、 密码和邮箱来完成注册和登录。 注册成功后, 本地用户目录的 .dockercfg 中将保存用户的认证信息
  • 基本操作
    用户无需登录即可通过 docker search 命令来查找官方仓库中的镜像, 并利用 docker pull 命令来将它下载到本地
    docker search centos

私有仓库

docker-registry 是官方提供的工具, 可以用于构建私有的镜像仓库。

  • 安装运行 docker-registry

本地安装

yum install -y python-devel libevent-devel python-pip gcc xz-devel
python-pip install docker-registry
No package python-pip available. 解决方法
sudo yum -y install epel-release
sudo yum -y install python-pip
sudo yum clean all

解决上面问题时又报错

Error: Package: python-pip-7.1.0-1.el6.noarch (epel)
Requires: python(abi) = 2.6
Installed: python-2.7.5-58.el7.x86_64 (@base)
python(abi) = 2.7
python(abi) = 2.7
Available: python34-3.4.5-4.el6.i686 (epel)
python(abi) = 3.4

所以安装Python 2.6
CentOS 7 编译安装Python2.6.1
centos7 python多版本切换

  • 在私有仓库上传、 下载、 搜索镜像
  • docker tag 标记一个镜像

格式为 :
docker tag IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
如: docker tag ba58 192.168.7.26:5000/test

  • 使用 docker push 上传标记的镜像

docker push 192.168.7.26:5000/test

  • 用 curl 查看仓库中的镜像

curl http://192.168.7.26:5000/v1/search

  • 另外一台机器去下载这个镜像

docker pull 192.168.7.26:5000/test

  • 可以使用 这个脚本 批量上传本地的镜像到注册服务器中, 默认是本地注册服务器 127.0.0.1:5000

wget https://github.com/yeasy/docker_practice/raw/master/_local/push_images.sh; sudo chmod a+x push

#!/bin/sh

# This script will upload the given local images to a registry server ($registry is the default value).
# See: https://github.com/yeasy/docker_practice/blob/master/_local/push_images.sh
# Usage:  push_images image1 [image2...]
# Author: yeasy@github
# Create: 2014-09-23

#The registry server address where you want push the images into
registry=127.0.0.1:5000

### DO NOT MODIFY THE FOLLOWING PART, UNLESS YOU KNOW WHAT IT MEANS ###
echo_r () {
    [ $# -ne 1 ] && return 0
    echo -e "\033[31m$1\033[0m"
}
echo_g () {
    [ $# -ne 1 ] && return 0
    echo -e "\033[32m$1\033[0m"
}
echo_y () {
    [ $# -ne 1 ] && return 0
    echo -e "\033[33m$1\033[0m"
}
echo_b () {
    [ $# -ne 1 ] && return 0
    echo -e "\033[34m$1\033[0m"
}

usage() {
    sudo docker images
    echo "Usage: $0 registry1:tag1 [registry2:tag2...]"
}

[ $# -lt 1 ] && usage && exit

echo_b "The registry server is $registry"


for image in "$@"
do
    echo_b "Uploading $image..."
    sudo docker tag $image $registry/$image
    sudo docker push $registry/$image
    sudo docker rmi $registry/$image
    echo_g "Done"
done

摘自:https://github.com/yeasy/docker_practice/blob/docker-legacy/_local/push_images.sh

Docker 数据管理

  • 数据卷(Data volumes)

数据卷可以在容器之间共享和重用
对数据卷的修改会立马生效
对数据卷的更新, 不会影响镜像
卷会一直存在, 直到没有容器使用

数据卷的使用, 类似于 Linux 下对目录或文件进行 mount

  • 创建一个数据卷

docker run -d -P --name web -v /webapp training/webapp python app.py

注意:也可以在 Dockerfile 中使用 VOLUME 来添加一个或者多个新的卷到由该镜像创建的任意容器。

  • 挂载一个主机目录作为数据卷

docker run -d -P --name web -v /src/webapp:/opt/webapp:ro
training/webapp python app.py

注意:Dockerfile 中不支持这种用法, 这是因为 Dockerfile 是为了移植和分享用的。 然而, 不同操作系统
的路径格式不一样, 所以目前还不能支持。

  • 挂载一个本地主机文件作为数据卷

docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
这样就可以记录在容器输入过的命令了。

  • 数据卷容器(Data volume containers)

curl -L https://github.com/docker/compose/releases/download/1.2.0/docker-compose-uname -s-`u
$ sudo chmod a+x /usr/local/bin/docker-compose

Docker镜像和仓库

-查看docker镜像 docker images

image.png

  • 搜索镜像 docker search puppet
    image.png

仓库Docker Registry

  • 利用镜像直接搭建仓库
docker run -p 5000:5000 registry:2 
[利用阿里云 OSS 搭建私有 Docker 仓库](http://morning.work/page/2016-01/deploying-your-own-private-docker-registry.html)
  • 构建镜像
  • 方式1 commit方式
    `docker commit [容器ID] [目标仓库]/[镜像名]:[tag] -m"描述" -a"作者"

  • 获取容器ID docker ps -l -q

    image.png

  • 方法2 Dockerfile方式

[root@registry docker_srm_portal]# more Dockerfile 
# centos image
FROM centos
MAINTAINER xxx@xxx.com
#copy jdk and tomcat into image
ADD  ./tomcat8.tar.gz /opt/
ADD ./jdk1.8.tar.gz /opt/

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime

#set environment variable
ENV JAVA_HOME /opt/jdk1.8
ENV PATH $JAVA_HOME/bin:$PATH

RUN chmod -R +x /opt/tomcat8/bin

# copy web
COPY ./ROOT.war /opt/tomcat8/webapps/

VOLUME ["/opt/tomcat8/logs"]

EXPOSE 8080

#define entry point which will be run first when the container starts up
ENTRYPOINT /opt/tomcat8/bin/startup.sh && tail -f /opt/tomcat8/logs/catalina.out

exec命令方式执行run

RUN ["apt-get"," install","-y","nginx"]
  • 刷新
ENV REFRESHED_AT 2018-01-01
RUN apt-get -qq update #刷新APT包的缓存
RUN yum -q makecache
  • 构建
docker build --no-cache -t="[仓库]/[镜像名]:[tag]" .
docker build --no-cache -t="[仓库]/[镜像名]:[tag]" . -f file_path
  • docker history


    image.png
  • 查看端口
    docker ps -l
    docker port d96f3be34108

    image.png

  • 启动容器

docker run -d -p 127.0.0.1:80:80 --name web_demo [仓库]/[镜像名]:[TAG] nginx -g "daemon off"

CMD指令用于指定一个容器启动时要运行的命令。CMD可以在容器启动时被替换。当Dockerfile存在两个或多个CMD指令时最有最后一个CMD指令生效。
指定要运行的特定命令:docker run –it centos /bin/sh
添加CMD指令:
CMD[“/bin/true”]
给CMD指令传递参数:
CMD[“/bin/bash”,”-l”]
如有多个命令或多个进程可以使用CMD /bin/bash –l && /bin/true 或考虑类型Supervisor这样的服务管理工具。
CMD ["/bin/bash","-l"]
CMD ["/bin/bash"]

  • ENTRYPOINT命令

ENTRYPOINT指令与CMD指令非常类似,很容易和CMD指令弄混。CMD指令可以在docker run 命令行中覆盖CMD指令,有时候,我们希望容器会按照我们想象的那样去工作,这时候CMD就不太适合了。而ENTRYPOINT指令提供的命令不容易在启动时被覆盖。实际上,docker run 命令行中指定的任何参数都会被当作参数再次传递给ENTRYPOINT指令中指定的命令
添加ENTRYPOINT指令:
ENTRYPOINT[“/usr/sbin/nginx”]
给ENTRYPOINT传递参数:
ENTRYPOINT[“/usr/sbin/nginx”,”-g”,”daemon off;”]
与CMD组合使用:
ENTRYPOINT[“/usr/sbin/nginx”]
CMD[“-h”]
注意:与CMD指令一样,ENTRYPOINT也是以数组的方式来运行,也可以通过增加 /bin/sh –c 来直接运行命令。
如果用户确实需要,用户也可以在运行时通过docker run 的 –entrypoint 标志来覆盖 ENTRYPOINT指令。

ENTRYPOINT ["/usr/sbin/nginx","-g","daemon off"]
CMD ["-h"]

可以用docker run 的--entrypoint 标志覆盖ENTRYPOINT命令
CMD和ENTRYPOINT 的主要区别是:ENTRYPOINT 提供的命令不容易在启动容器时被覆盖

  • WORKDIR

切换工作目录,ENTRYPOINTCMD在指定的工作目录下执行

WORKDIR /opt/
RUN bundle install
WORKDIR /webapp
ENTRYPOINT [ "rackup" ]

可以覆盖:如docker run -it -w /var/log ubuntu pwd

  • ENV

设置环境变量

#一个ENV定义多个环境变量
ENV RVM_PATH=/home/rvm RVM_ARCHFLAGES="-arch i386"
ENV TARGET_DIR /opt/app
#可以引用已经定义的环境变量
WORKDIR $TARGET_DIR 
#–e 标志来传递环境变量
docker run –it –e“WEB_PORT=8080”ubuntu env

运行时也可指定,只在运行时有效,如:docker run -it -e "WEB_PORT=8080" ubuntu env

  • USER
#不指定默认为root
USER nginx:group

docker run 中可以用 -u 指定

  • VOLUME

VOLUME指令用来向基于镜像创建的容器添加卷。一个卷可以存在于一个或者多个容器的特定的目录,这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据进行持久化功能。

1. 卷可以在容器间共享和重用。
2. 一个容器可以不是必须和其他容器共享卷。
3. 对卷的修改时立即生效的。
4. 对卷的修改不会对更新镜像产生影响。
5. 卷会一直存在直到没有任何容器在使用它。
    添加VOLUME指令:
       VOLUME[“/opt/tomcat7/logs”]
   添加VOLUME指令指定多个卷
        VOLUME[“/opt/tomcat7/logs”,”/data”]
VOLUME ["/opt/project"]
#指定多个卷
VOLUME ["/opt/project","data"]

docker cp 和 VOLUME 类似

#镜像里复制出来
docker cp cas_sso_1:/opt/tomcat7/webapps/cas.war .
#宿主复制到容器
docker cp 1.txt cas_sso_1:/opt/tomcat7/webapps
  • ADD

将构建目录下的文件和目录复制到镜像中

ADD software.lic /opt/app/software.lic
#以目的的/判断源是目录还是文件
ADD /opt/app /var/local/app/ #以/结尾为目录
#使用URL格式
ADD http://www.baidu.com/test.txt /root/test.txt
#自动解压gzip,bzip2,gz的包
ADD test.tar.gz /var/www/app

注意
为了让镜像尽量小,最好不要使用ADD指令从远程 URL 获取包,而是使用curl和wget。这样你可以在文件提取完之后删掉不再需要的文件,可以避免在镜像中额外添加一层。(译者注:ADD指令不能和其他指令合并,所以前者ADD指令会单独产生一层镜像。而后者可以将获取、提取、安装、删除合并到同一条RUN指令中,只有一层镜像。)

  • COPY
#只复制Dockerfile同目录的,不解压和提取,容器里面是绝对路径
COPY config/ /etc/app/
#拷贝目录:
COPY conf.d/  /etc/apache2/ 
#拷贝文件
COPY nginx.conf /usr/local/nginx/conf/nginx.conf
  • LABEL

添加元数据,尽量所有的元数据都放到一条LABEL中,替代MAINTAINER

LABEL location="New York" type="Data Center"
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

查看元数据`docker inspect [仓库名]/[镜像名]

  • STOPSIGNAL

STOPSIGNAL 9
STOPSIGNAL SIGKILL

ARG指令用来定义可以在docker build 命令运行时传递给构建运行时变量的指令,在构建时需要使用 –build-arg 标志即可,用户只能在构建时指定在Dockerfile文件中定义过的参数。在Dockerfile定义这些参数后如未设置默认值,且在构建时未传入定义变量的值时,构建将失败。

ARG build
ARG webapp_user=user
docker build --build-arg build=1234 -t [仓库名]/[镜像名] .
  • ONBUILD
ONBUILD ADD . /app/src
ONBUILD RUN cd /app/src && make
  • 实例
[root@registry docker_srm_portal]# ll
total 245312
-rw-r--r-- 1 root root       193 Dec 26 01:45 docker-compose.yml
-rw-r--r-- 1 root root       565 Dec 26 01:49 Dockerfile
-rw-r--r-- 1 root root 185525916 Dec 19 10:32 jdk1.8.tar.gz
-rw-r--r-- 1 root root  57603854 Jan 15 10:56 ROOT.war
-rw-r--r-- 1 root root   8056699 Dec 27 18:48 tomcat8.tar.gz
[root@registry docker_srm_portal]# more Dockerfile 
# centos image
FROM centos
MAINTAINER xxx@xxx.com
#copy jdk and tomcat into image
ADD  ./tomcat8.tar.gz /opt/
ADD ./jdk1.8.tar.gz /opt/

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime

#set environment variable
ENV JAVA_HOME /opt/jdk1.8
ENV PATH $JAVA_HOME/bin:$PATH

RUN chmod -R +x /opt/tomcat8

# copy web
COPY ./ROOT.war /opt/tomcat8/webapps/

VOLUME ["/opt/tomcat8/logs"]

EXPOSE 8080

#define entry point which will be run first when the container starts up
ENTRYPOINT /opt/tomcat8/bin/startup.sh && tail -f /opt/tomcat8/logs/catalina.out
[root@registry docker_srm_portal]# more docker-compose.yml 
version: '2'
services:
  srm_portal:  
    image: 10.100.60.88:8080/srm_portal:1.0
    ports: 
      - "8080:8080"
    volumes: 
      - "./logs:/opt/tomcat8/logs"
      - "./upload:/usr/upload"
  • 查看

docker inspect 容器ID

  • 删除镜像

docker rmi [仓库名]/[镜像名]
docker rmi `docker images -a -q`

docker images [仓库名]/[镜像名]
#使用新的Registry为镜像打标签
docker tag 镜像ID  [主机名]:端口号/[仓库名]/[镜像名]
#推送到新的Registry
docker push [主机名]:端口号/[仓库名]/[镜像名]

资源链接

Docker 主站点: https://www.docker.io
Docker 注册中心API: http://docs.docker.com/reference/api/registry_api/
Docker Hub API: http://docs.docker.com/reference/api/docker-io_api/
Docker 远端应用API: http://docs.docker.com/reference/api/docker_remote_api/
Dockerfile 参考:https://docs.docker.com/reference/builder/
Dockerfile 最佳实践:https://docs.docker.com/articles/dockerfile_best-practices/

推荐阅读更多精彩内容