使用Docker配置Flask开发环境

使用Docker+docker-compose,配置最简Flask开发环境

工具

  • Docker
    一种开源容器应用,供开发者打包自己的开发环境,可以任意移植

  • docker-compose
    一种管理多个Docker容器的工具,可以简化我们启动容器的各种命令

配置文件

首先我们需要一个python基础景象,Docker各种基础镜像都可以从官方找到 https://hub.docker.com/_/python/ 。找到基础镜像之后就可以基于它做相应的配置,这些操作都记录在Dockerfile中。

Dockerfile:

FROM python:3.6-slim # 官网中挑选的python基础镜像

ADD requirements.txt requirements.txt # requirements.txt罗列了需要安装的python模块,将文件复制到容器中

RUN pip install -r ./requirements.txt # 执行模块安装

EXPOSE 5000 # 对外暴露5000端口

requirements.txt

Flask # 需要安装的python模块,如有其他需要安装的模块,如下依次写入。
# redis 
# pymongo

配置完Dockerfile之后,开始配置docker-compose文件。

docker-compose.yml

web:
  build: ../../dockerfile/python/3 # DockerFile所在目录
  ports:
    - "5000:5000" # 对外暴露端口,与Dockerfile中端口号一致
  volumes:
    - ~/workspace/python/redis:/code # 本地工作目录与容器中目录映射
  command:
    - /code/app.py # 处理请求的python脚本

app.py

#!/usr/bin/env python

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Flask Dockerized'

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

容器启动

在docker-compose.yml所在文件夹执行命令 docker-compose up ,在控制台中看到如下输出:

Starting flash_web_1 ...
Starting flash_web_1 ... done
Attaching to flash_web_1
web_1  |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
web_1  |  * Restarting with stat
web_1  |  * Debugger is active!
web_1  |  * Debugger PIN: 166-703-177

同时在浏览器中输入 localhost:5000 ,并正常显示文本 Flask Dockerized ,环境就配置成功啦!对应的log都可以在启动容器的终端中查看到。例如:

web_1  | 172.17.0.1 - - [13/Sep/2017 13:18:17] "GET / HTTP/1.1" 200 -
web_1  | 172.17.0.1 - - [13/Sep/2017 13:18:17] "GET /favicon.ico HTTP/1.1" 404 -

总结

以上配置了最简的Flask开发环境,实际开发中还需要数据库、缓存、nginx等,这些基础容器都可以在Docker官网中找到,并使用docker-compose可以很清晰的将这些容器关联起来。

Ubuntu16.04安装docker

要想使用最新版本的docker需要由以下方法安装

通过docker源安装最新版本

依次输入以下命令

$ sudo apt-get install apt-transport-https
$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys     36A1D7869245C8950F966E92D8576A8BA88D21E9
$ sudo bash -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
$ sudo apt-get update
$ sudo apt-get install lxc-docker

安装之后启动docker服务

$ sudo service docker start

卸载docker

卸载Docker包:

$ sudo apt-get purge docker-engine

卸载Docker包及其以来不再需要使用下面的命令:

$ sudo apt-get autoremove --purge docker-engine

上面的命令不会移除镜像,容器,卷或者是用户创建的配置文件。如果你想删除所有的镜像,容器和卷,运行下面的命令:

$ rm -rf /var/lib/docker

你必须手动删除用户创建的配置文件。

[docker] 制作base镜像并上传至docker.io

本文在Debian下完成,用于笔记:

制作base镜像

  • 安装debootstrap:
apt-get install debootstrap
  • 安装额外的软件包并制作rootfs:
debootstrap --include=openssh-server --arch i386 wheezy debian-wheezy http://httpredir.debian.org/debian
  • 打包镜像并导入到docker:
tar -c . |docker import - debian-wheezy-i386

上传至docker.io

  • 注册账号.
  • 创建空间.
vicer/debian-wheezy-i386
  • 登陆到Docker Hub:
docker login
  • 创建标签:
#docker tag <imageID> <namespace>/<image name>:<tag>
docker tag debian-wheezy-i386 vicer/debian-wheezy-i386
  • 上传镜像:
#docker push <namespace>/<image name>
docker push vicer/debian-wheezy-i386

从零开始使用 Docker 打包 Django 开发环境 (3) Docker Compose

1. 基本概念

Docker Compose 是一个用来定义和运行复杂应用的 Docker 工具。使用 Docker Compose,可以在一个文件中定义一个多容器应用,然后使用一条命令来启动你的应用,完成一切准备工作。

Docker Compose 定位是 ‘defining and running complex applications with Docker’,前身是 Fig,兼容 Fig 的模板文件。

Docker Compose 发展至今,有 Version 1、Version 2、Version 3 三个大版本。如果不声明版本,默认为 Version 1。Version 1 不能使用 volumes,、networks、 build参数。Version 2,必须在版本中申明,所有的服务,都必须申明在 service 关键字下。Version 3 删除了 volume_driver、volumes_from、cpu_shares、cpu_quota、cpuset、mem_limit、memswap_limit、extends、group_add关键字,新增了 deploy,全面支持 Swarm mode。更详细的比较可以查看参考链接。

本文中主要以 Version 2 为例学习 Docker Compose 容器的编排。

2. 工作原理

Docker Compose 将所管理的容器分为三层,工程(project),服务(service)以及容器(contaienr)。Docker Compose 运行的目录下的所有文件(docker-compose.yml、extends文件、环境变量文件等)组成一个工程,若无特殊指定工程名即为当前目录名。

一个工程当中可包含多个服务,每个服务中定义了容器运行的镜像、参数、依赖。一个服务当中可包括多个容器实例,Docker Compose 并没有解决负载均衡的问题,因此需要借助其他工具实现服务发现及负载均衡。

Docker Compose 的工程配置文件默认为 docker-compose.yml,可通过环境变量 COMPOSE_FILE 或 -f 参数自定义配置文件,其定义了多个有依赖关系的服务及每个服务运行的容器。以下是一个简单的配置文件:

version: '2'  
services:  
    web:
        build: .
        ports:
          - "80:90"
        volumes:
          - .:/code
        links:
          - redis
    redis:
        image: redis

其定义了两个服务 web 和 redis。web 服务的镜像需要使用当前目录的 Dockerfile 实时构建,其容器运行时需要在宿主机开放端口 80 并映射到容器端口 90 ,并且挂载存储卷 /code 以及关联服务 redis。redis 服务通过镜像 redis 启动。

Docker Compose 是由 Python 语言实现的,它通过调用 docker-py 库(可参考
https://github.com/docker/docker-py )与 docker engine 通信实现构建 docker 镜像,启动停止 docker 容器等。Docker-py 库调用 docker remote API(可参考 https://docs.docker.com/reference/api/docker_remote_api/ )与 Docker Daemon 通信,可通过 DOCKER_HOST 配置本地或远程 Docker Daemon 的地址。

3. Docker Compose 常用命令

未分类

4. YAML 常用关键字

4.1 build

指定 Dockerfile 所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像。

build: /path/to/build/dir  

4.2 command

覆盖容器启动后默认执行的命令。

command: echo "hello world"  

4.3 dockerfile

如果需要指定额外的编译镜像的 Dockefile 文件,可以通过该指令来指定。例如:

dockerfile: Dockerfile-alternate  

注意,该指令不能跟 image 同时使用,否则 Compose 将不知道根据哪个指令来生成最终的服务镜像。

4.4 env_file

从文件中获取环境变量,可以为单独的文件路径或列表。

如果通过 docker-compose -f FILE 方式来指定 Compose 模板文件,则 env_file 中变量的路径会基于模板文件路径。

如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准。

env_file: .env

env_file:  
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env

环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

# common.env: Set development environment
PROG_ENV=development  

4.5 environment

设置环境变量。你可以使用数组或字典两种格式。

只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。例如:

environment:  
  RACK_ENV: development
  SESSION_SECRET:

或者

environment:  
  - RACK_ENV=development
  - SESSION_SECRET

注意,如果变量名称或者值中用到 true|false,yes|no 等表达布尔含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。

http://yaml.org/type/bool.html 中给出了这些特定词汇,包括

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

4.6 expose

暴露端口,但不映射到宿主机,只被连接的服务访问。仅可以指定内部端口为参数

expose:  
 - "3000"
 - "8000"

4.7 image

指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。例如:

image: ubuntu  
image: orchardup/postgresql  
image: a4bc65fd  

4.8 links

链接到其它服务中的容器。使用服务名称(同时作为别名)或服务名称:服务别名 (SERVICE:ALIAS) 格式都可以。

links:  
 - db
 - db:database
 - redis

使用的别名将会自动在服务容器中的 /etc/hosts 里创建。例如:

172.17.2.186  db  
172.17.2.186  database  
172.17.2.187  redis  

被链接容器中相应的环境变量也将被创建。

4.9 volumes

数据卷所挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。该指令中路径支持相对路径。例如

volumes:  
 - /var/lib/mysql
 - cache/:/tmp/cache
 - ~/configs:/etc/configs/:ro

4.10 volumes_from

从另一个服务或容器挂载它的数据卷。

volumes_from:  
 - service_name
 - container_name

从零开始使用 Docker 打包 Django 开发环境 (2) Dockerfile

1. 基本概念

Dockerfile 是一些列构建 Docker 镜像的指令集合。Docker 通过读取 Dockerfile 指令自动构建镜像。Dockerfile 类似于 Makefile,都是一种文本文件,按照构建镜像顺序组织所有的指令。

Docker 镜像的构建命令:

$ docker build .

这条命令中,Docker CLI 的处理流程如下:

  • 把当前目录及子目录当做上下文传递给 Docker Daemon
  • 从当前目录(不包括子目录)中找到 Dockerfile 文件
  • 检查 Dockerfile 的语法
  • 依次执行 Dockerfile 中的指令,根据指令生成中间过渡镜像(存储在本地,为之后的指令或构建作缓存)

2. Docker 文件组成

Dockerfile 一般包含下面几个部分:

  • 基础镜像,以哪个镜像作为基础进行制作,用法是 FROM 基础镜像名称
  • 维护者信息,需要写下该 Dockerfile 编写人的姓名或邮箱,用法是MANITAINER 名字/邮箱
  • 镜像操作命令,对基础镜像要进行的改造命令,比如安装新的软件,进行哪些特殊配置等,常见的是 RUN 命令
  • 容器启动命令,当基于该镜像的容器启动时需要执行哪些命令,常见的是 CMD 命令或 ENTRYPOINT 命令

3. Dockerfile 命令

3.1 FROM

语法:FROM image[:tag]

解释:设置要制作的镜像基于哪个镜像,FROM 指令必须是整个 Dockerfile 的第一个指令,如果指定的镜像不存在默认会自动从 Docker Hub 上下载。如果不指定 tag,默认是 latast。

3.2 MAINTAINER

语法:MAINTAINER name

解释:MAINTAINER 指令允许你给将要制作的镜像设置作者信息

3.3 RUN

语法:

  • RUN command #将会调用/bin/sh -c command
  • RUN [“executable”, “param1”, “param2”] #将会调用exec执行,以避免有些时候shell方式执行时的传递参数问题,而且有些基础镜像可能不包含/bin/sh

解释:RUN指令会在一个新的容器中执行任何命令,然后把执行后的改变提交到当前镜像,提交后的镜像会被用于Dockerfile中定义的下一步操作,RUN中定义的命令会按顺序执行并提交,这正是Docker廉价的提交和可以基于镜像的任何一个历史点创建容器的好处,就像版本控制工具一样。

3.4 CMD

语法:

  • CMD [“executable”, “param1”, “param2”] #将会调用exec执行,首选方式
  • CMD [“param1”, “param2”] #当使用ENTRYPOINT指令时,为该指令传递默认参数
  • CMD command [ param1|param2 ] #将会调用/bin/sh -c执行

解释:CMD 指令中指定的命令会在镜像运行时执行,在 Dockerfile 中只能存在一个,如果使用了多个 CMD指令,则只有最后一个 CMD 指令有效。当出现 ENTRYPOINT 指令时,CMD 中定义的内容会作为 ENTRYPOINT 指令的默认参数,也就是说可以使用 CMD 指令给 ENTRYPOINT 传递参数。

注意:RUN 和 CMD 都是执行命令,他们的差异在于 RUN 中定义的命令会在执行 docker build 命令创建镜像时执行,而 CMD 中定义的命令会在执行docker run命令运行镜像时执行,另外使用第一种语法也就是调用 exec 执行时,命令必须为绝对路径。

3.5 EXPOSE

语法:EXPOSE port [ …]

解释:EXPOSE 指令用来告诉 Docker 这个容器在运行时会监听哪些端口,Docker 在连接不同的容器(使用–link参数)时使用这些信息。

3.6 ENV

语法:ENV key value

解释:ENV 指令用于设置环境变量,在 Dockerfile 中这些设置的环境变量也会影响到 RUN 指令,当运行生成的镜像时这些环境变量依然有效,如果需要在运行时更改这些环境变量可以在运行 docker run 时添加–env key=value参数来修改。

注意:最好不要定义那些可能和系统预定义的环境变量冲突的名字,否则可能会产生意想不到的结果。

3.7 ADD

语法:ADD src dest

解释:ADD 指令用于从指定路径拷贝一个文件或目录到容器的指定路径中,是一个文件或目录的路径,也可以是一个 url,路径是相对于该 Dockerfile 文件所在位置的相对路径, 是目标容器的一个绝对路径,例如 /home/yooke/Docker/Dockerfile 这个文件中定义的,那么 ADD /data.txt /db/指令将会尝试拷贝文件从 /home/yooke/Docker/data.txt 到将要生成的容器的 /db/data.txt,且文件或目录的属组和属主分别为 uid 和 gid 为0的用户和组,如果是通过 url 方式获取的文件,则权限是600。

注意:

  • 如果执行 docker build somefile 即通过标准输入来创建时,ADD 指令只支持 url 方式,另外如果 url 需要认证,则可以通过 RUN wget … 或 RUN curl … 来完成,ADD 指令不支持认证。
  • src 路径必须与 Dockerfile 在同级目录或子目录中,例如不能使用ADD ../somepath,因为在执行docker build时首先做的就是把 Dockerfile 所在目录包含子目录发送给 docker 的守护进程。
  • 如果 src 是一个 url 且 dest 不是以 ‘/’ 结尾,则会下载文件并重命名为 dest 。
  • 如果 src 是一个 url 且 dest 以 ‘/’ 结尾,则会下载文件到 dest filename,url 必须是一个正常的路径形式,’http://example.com’ 像这样的 url 是不能正常工作的。
  • 如果 src 是一个本地的压缩包且 dest 是以 ‘/’ 结尾的目录,则会调用 ‘tar -x’ 命令解压缩,如果 dest 有同名文件则覆盖,但 src 是一个 url 时不会执行解压缩。

3.8 COPY

语法:COPY src dest

解释:用法与 ADD 相同,不过 src 不支持使用url,所以在使用 docker build somefile 时该指令不能使用。

3.9 ENTRYPOINT

语法:

  • ENTRYPOINT [‘executable’, ‘param1’, ‘param2’] #将会调用exec执行,首选方式
  • ENTRYPOINT command param1 param2 #将会调用/bin/sh -c执行

解释:ENTRYPOINT 指令中指定的命令会在镜像运行时执行,在 Dockerfile 中只能存在一个,如果使用了多个 ENTRYPOINT 指令,则只有最后一个指令有效。ENTRYPOINT 指令中指定的命令(exec执行的方式)可以通过 docker run 来传递参数,例如 docker run images -l 启动的容器将会把 -l 参数传递给 ENTRYPOINT 指令定义的命令并会覆盖 CMD 指令中定义的默认参数(如果有的话),但不会覆盖该指令定义的参数,例如 ENTRYPOINT [‘ls’,’-a’],CMD [‘/etc’],当通过 docker run image 启动容器时该容器会运行 ls -a /etc 命令,当使用 docker run image -l 启动时该容器会运行 ls -a -l 命令,-l 参数会覆盖 CMD 指令中定义的/etc参数。

注意:

  • 当使用 ENTRYPOINT 指令时生成的镜像运行时只会执行该指令指定的命令。
  • 当出现 ENTRYPOINT 指令时 CMD 指令只可能(当 ENTRYPOINT 指令使用 exec 方式执行时)被当做 ENTRYPOINT 指令的参数使用,其他情况则会被忽略。

3.10 VOLUME

语法:VOLUME [‘samepath’]

解释:VOLUME 指令用来设置一个挂载点,可以用来让其他容器挂载以实现数据共享或对容器数据的备份、恢复或迁移。

3.11 USER

语法:USER [username|uid]

解释:USER指令用于设置用户或uid来运行生成的镜像和执行 RUN 指令。

3.12 WORKDIR

语法:WORKDIR /path/to/workdir

解释:WORKDIR 指令用于设置 Dockerfile 中的 RUN、CMD 和 ENTRYPOINT 指令执行命令的工作目录(默认为/目录),该指令在 Dockerfile 文件中可以出现多次,如果使用相对路径则为相对于 WORKDIR 上一次的值,例如 WORKDIR /data,WORKDIR logs,RUN pwd 最终输出的当前目录是 /data/logs。

4. 最佳实践

4.1 使用 .dockerignore 文件

在 docker 构建镜像的第一步,docker CLI 会先在上下文目录中寻找 .dockerignore 文件,根据 .dockerignore 文件排除上下文目录中的部分文件和目录,然后把剩下的文件和目录传递给 docker 服务。.dockerignore 语法同 .gitignore。

4.2 避免安装不必要的包

为了减小复杂度、依赖、文件大小和创建的时间,应该避免安装额外或者不必要的包。例如,我们不必在一个数据库镜像中包含一个文本编辑器。

4.3 对于多行参数要做字典序排序

只要有可能,通过对多行参数进行字母数字排序来缓解后续变更。这将帮助你避免重复的包并且更容易更新。在反斜线( )前添加一个空格是个好习惯。

RUN apt-get update && apt-get install -y   
  bzr 
  cvs 

4.4 尽量利用 build 镜像的缓存

未分类

在创建镜像过程中,Docker 将按照 Dockerfile 指定步骤执行每个指令。 一般情况下,对于每条命令,docker 都会生成一层镜像。如果在构建某个镜像层的时候,发现这个镜像层已经存在了,就直接使用,而不是重新构建。

大部分指令是通过与缓存进行对比该指令、执行指令的基础镜像,判断是否使用缓存。除了 ADD 和 COPY,这两个指令会复制文件内容到镜像内,docker 还会检查每个文件内容校验和(不包括最后修改时间和最后访问时间),如果校验和不一致,则不会使用缓存。

4.5 每个镜像只有一个功能

不要在容器里运行多个不同功能的进程,每个镜像中只安装一个应用的软件包和文件,需要交互的程序通过 pod(kubernetes 提供的特性) 或者容器之间的网络进行通信。这样可以保证模块化,不同的应用可以分开维护和升级,也能减小单个镜像的大小。

4.6 不要在构建中升级版本

更新将发生在基础镜像里,不要在你的容器内来apt-get upgrade更新。因为在隔离情况下,如果更新时试图修改 init 或改变容器内的设备,更新可能会经常失败。它还可能会产生不一致的镜像,因为你不再有你的应用程序该如何运行以及包含在镜像中依赖的哪种版本的正确源文件。

从零开始使用 Docker 打包 Django 开发环境 (1) 环境搭建

Vagrant 适合用来管理虚拟机,而 Docker 适合用来管理应用环境。为了更好地模拟真实运行环境,本系列文章借助 Docker 和 Docker Compose 搭建 Nginx + uWSGI+ Django + MySQL + Redis + Rabbit 的开发环境。

1. 基本概念

Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。Docker 可以让开发者打包应用以及依赖包到一个轻量级、可移植的容器中,然后发布到机器上。

Docker Compose 是一个用来定义和运行复杂应用的 Docker 工具。

2. Docker 安装和使用

2.1 安装

在 Linux 上 安装 Docker。

# curl -sSL https://get.daocloud.io/docker | sh

在 Linux 上 安装 Docker Compose。

# curl -L https://get.daocloud.io/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose

2.2 查看安装信息

安装完毕后,可以通过如下命令检测是否安装成功。

查看 Docker 版本

# docker --version 
Docker version 1.12.6, build 88a4867/1.12.6  

查看 docker-compose 版本

# docker-compose --version
docker-compose version 1.16.1, build 6d1ac21  

拉起 Docker Deamon。Docker Client 需要将 Console 输入的命令,发送给 Daemon 执行。

# service docker start

通过,docker info 命令可以看到,本地使用的是 docker 原生的镜像源。

# docker info // 查看 docker 信息
Docker Root Dir: /var/lib/docker  
Debug Mode (client): false  
Debug Mode (server): false  
Registry: https://index.docker.io/v1/  
Insecure Registries:  
 127.0.0.0/8
Registries: docker.io (secure)  

2.3 拉取、启动镜像

拉取 hello-world 镜像。

# docker pull hello-world
Using default tag: latest  
Trying to pull repository docker.io/library/hello-world ...  
latest: Pulling from docker.io/library/hello-world

Digest: sha256:f3b3b28a45160805bb16542c9531888519430e9e6d6ffc09d72261b0d26ff74f  

启动 hello-world 镜像。

# docker run hello-world

Hello from Docker!  
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:  
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:  
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:  
 https://cloud.docker.com/

For more examples and ideas, visit:  
 https://docs.docker.com/engine/userguide/

查看本地镜像列表。

# docker images 
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE  
docker.io/hello-world   latest              1815c82652c0        12 weeks ago        1.84 kB  

2.4 配置加速器(可选)

使用 Docker 的时候,经常需要从官方获取镜像,但是由于网络原因,拉取镜像的过程非常耗时,严重影响使用 Docker 的体验。

这里使用的 DaoDlcoud 提供的加速器,通过智能路由和缓存机制,提升了访问 Docker Hub 的速度。

curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://6c5fbccc.m.daocloud.io

Docker的save和export命令的区别

Docker是如何工作的(简单说明)

Docker是基于镜像的。镜像类似于已经包含了文件、配置和安装好的程序的虚拟机镜像。同样的,你可以像启动虚拟机一样启动多个镜像实例。运行中的镜像称为容器。你可以修改容器(比如删除一个文件),但这些修改不会影响到镜像。不过,你使用 docker commit 命令可以把一个正在运行的容器变成一个新的镜像。

举个例子:

# 像Docker官方的hello world例子一样,拉取一个叫busybox的镜像

sudo docker pull busybox



# 查看本地已经有哪些镜像

# 我们可以看到busybox

sudo docker images



# 现在让我们来修改下busybox镜像的容器

# 这次,我们创建一个文件夹

sudo docker run busybox mkdir /home/test



# 让我们再看看我们有哪些镜像了。

# 注意每条命令执行后容器都会停止

# 可以看到有一个busybox容器

sudo docker ps -a



# 现在,可以提交修改了。

# 提交后会看到一个新的镜像busybox-1

#  <CONTAINER ID> 是刚刚修改容器后得到的ID

sudo docker commit <CONTAINER ID> busybox-1



# 再看看我们有哪些镜像。

# 我们现在同时有busybox和busybox-1镜像了。

sudo docker images



# 我们执行以下命令,看看这两个镜像有什么不同

sudo docker run busybox [ -d /home/test ] && echo 'Directory found' || echo 'Directory not found'



sudo docker run busybox-1 [ -d /home/test ] && echo 'Directory found' || echo 'Directory not found'

现在,我们有两个不同的镜像了(busybox和busybox-1),还有一个通过修改busybox容器得来的容器(多了一个/home/test文件夹)。下面来看看,是如何持久化这些修改的。

导出(Export)

Export命令用于持久化容器(不是镜像)。所以,我们就需要通过以下方法得到容器ID:

sudo docker ps -a

接着执行导出:

sudo docker export <CONTAINER ID> > /home/export.tar

最后的结果是一个2.7MB大小的Tar文件(比使用save命令稍微小些)。

保存(Save)

Save命令用于持久化镜像(不是容器)。所以,我们就需要通过以下方法得到镜像名称:

sudo docker images

接着执行保存:

sudo docker save busybox-1 > /home/save.tar

最后的结果是一个2.8MB大小的Tar文件(比使用export命令稍微大些)。

它们之间的不同

现在我们创建了两个Tar文件,让我们来看看它们是什么。首先做一下小清理——把所有的容器和镜像都删除:

# 查看所有的容器

sudo docker ps -a


# 删除它们

sudo docker rm <CONTAINER ID>


# 查看所有的镜像

sudo docker images


# 删除它们

sudo docker rmi busybox-1

sudo docker rmi busybox

译注:可以使用 docker rm $(docker ps -q -a) 一次性删除所有的容器,docker rmi $(docker images -q) 一次性删除所有的镜像。

现在开始导入刚刚导出的容器:

# 导入export.tar文件

cat /home/export.tar | sudo docker import - busybox-1-export:latest


# 查看镜像

sudo docker images


# 检查是否导入成功,就是启动一个新容器,检查里面是否存在/home/test目录(是存在的)

sudo docker run busybox-1-export [ -d /home/test ] && echo 'Directory found' || echo 'Directory not found'

使用类似的步骤导入镜像:

# 导入save.tar文件

docker load < /home/save.tar


# 查看镜像

sudo docker images


# 检查是否导入成功,就是启动一个新容器,检查里面是否存在/home/test目录(是存在的)

sudo docker run busybox-1 [ -d /home/test ] && echo 'Directory found' || echo 'Directory not found'

那,它们之间到底存在什么不同呢?我们发现导出后的版本会比原来的版本稍微小一些。那是因为导出后,会丢失历史和元数据。执行下面的命令就知道了:

# 显示镜像的所有层(layer)

sudo docker images --tree

执行命令,显示下面的内容。正你看到的,导出后再导入(exported-imported)的镜像会丢失所有的历史,而保存后再加载(saveed-loaded)的镜像没有丢失历史和层(layer)。这意味着使用导出后再导入的方式,你将无法回滚到之前的层(layer),同时,使用保存后再加载的方式持久化整个镜像,就可以做到层回滚(可以执行 docker tag 来回滚之前的层)。

vagrant@ubuntu-13:~$ sudo docker images --tree

 ├─f502877df6a1 Virtual Size: 2.489 MB Tags: busybox-1-export:latest

 └─511136ea3c5a Virtual Size: 0 B

  └─bf747efa0e2f Virtual Size: 0 B

 └─48e5f45168b9 Virtual Size: 2.489 MB

  └─769b9341d937 Virtual Size: 2.489 MB

 └─227516d93162 Virtual Size: 2.489 MB Tags: busybox-1:latest

以下部分内容来自于:大桥下的蜗牛的Docker 问答录(100 问),更多有关Docker相关常见问题可参考:https://blog.lab99.org/post/docker-2016-07-14-faq.html

随着镜像分层平面化后,docker images –tree 这个命令就取消了。幸运的是,Nate Jones 写了一个工具,用于可视化镜像分层依赖,叫做 dockviz:https://github.com/justone/dockviz

对于 Mac 平台的用户,可以很方便的使用 brew 来进行安装:

brew install dockviz

对于其它平台的用户,可以直接去发布页面下载。

安装好后,直接执行 dockviz images –tree 即可:

├─<missing> Virtual Size: 1.1 MB

│ └─<missing> Virtual Size: 1.1 MB

│   └─176825169704 Virtual Size: 1.1 MB Tags: busybox-1:latest

├─616f38fe120d Virtual Size: 1.1 MB Tags: busybox-1-export:latest

在新版本Docker中也可以使用 docker history 命令来查看镜像历史。

未分类

Docker在PHP中的实践过程

最近微服务很火,很多人都在尝试,我们公司也在这段时间尝试着来时间微服务化,其中就涉及到Docker。
在实践docker中踩了很多坑,也对Docker有了更多的认识,下面记录一下。

Docker在打包Spring boot项目时候,因为Spring boot内部集成了tomcat并且提供了直接打包成jar包的方式,Spring boot如何打包: https://docs.spring.io/spring-boot/docs/current/reference/html/build-tool-plugins-maven-plugin.html

Dockerfile文件如下:

FROM java:8

COPY target/your-project-name.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]

因为公司中还有一个之前的PHP项目,所以也需要对PHP进行打包,查了很多资料,最后在实践中发现,PHP项目需要一个nginx来管理网络,用如何的Dockerfile文件打包的话就会有问题。

FROM php:7.0-cli
RUN apt-get update && apt-get install -y 
        libfreetype6-dev 
        libjpeg62-turbo-dev 
        libmcrypt-dev 
        libpng12-dev 
    && docker-php-ext-install -j$(nproc) iconv mcrypt 
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ 
    && docker-php-ext-install -j$(nproc) gd
FROM php:7.1-fpm
RUN pecl install -o -f redis 
    &&  rm -rf /tmp/pear 
    &&  docker-php-ext-enable redis
RUN docker-php-ext-install  mysqli pdo pdo_mysql

EXPOSE 80

COPY . /php
WORKDIR /php

经过再三查找资料,发现这么一篇文章:

Dockerise your PHP application with Nginx and PHP7-FPM

在里面找到了解决办法,使用docker-compose就好了,docker-compose.yml文件如下:

version: '2'

services:
    web:
        image: nginx:latest
        ports:
            - "8080:80"
        volumes:
            - ./:/php
            - ./site.conf:/etc/nginx/conf.d/default.conf
        networks:
            - code-network
    php:
        image: php:fpm
        volumes:
            - ./:/php
        networks:
            - code-network

networks:
    code-network:
        driver: bridge

上面提到的site.conf文件如下:

server {
    listen 80;
    index index.php index.html;
    server_name localhost;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /php;

    location ~ .php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

直接在命令行执行:

$sudo docker-compose up

就可以看看到运行的结果了,可是因为我们是直接部署到阿里云容器服务上面的,docker-compose这种方式就有了缺陷,因为阿里云容器服务不支持server这种方式,不得已最终经过查询,找到了:
https://hub.docker.com/r/richarvey/nginx-php-fpm/

直接使用如下Dockerfile就可以了:

FROM richarvey/nginx-php-fpm:latest

MAINTAINER Ric Harvey <[email protected]>

ENV PHPREDIS_VERSION 3.0.0
RUN mkdir -p /usr/src/php/ext/redis 
    && curl -L https://github.com/phpredis/phpredis/archive/$PHPREDIS_VERSION.tar.gz | tar xvz -C /usr/src/php/ext/redis --strip 1 
    && echo 'redis' >> /usr/src/php-available-exts 
    && docker-php-ext-install redis

EXPOSE 80

ADD ./ /var/www/html/

既然当初选择了Docker这种方式,就要想办法吃透这门技术,否则在生产环境中的话出了问题就麻烦了。以上对与PHP的Docker化的实践过程,盯着一行行的日志信息查找原因,因为对PHP不是特别熟,还向PHP的同事请教了很多问题。

记录下来面的日后再遇到,虽然以上内容没有针对每个Docker的技术点进行介绍,找时间把每个技术点都记录下来。简单来说就是,PHP必须要搭配nginx使用。

Docker 日志的5个最佳实践

过去的几年中,容器已经成为IT业界的一大热门话题,尤其是在DevOps方面。简单来说,容器提供了简单可扩展的可靠方案,来解决在不同环境中运行软件的问题。

容器将整个运行时环境打包,其中包括应用程序、依赖、代码库、二进制文件以及配置文件。

微服务与容器密切相关,它提供了更为灵活的应用开发方式。微服务构架下,一个应用程序被分为许多个松散的耦合服务,这些服务通过功能性的API相连接,而这些API分别处理不同的业务功能。没有庞大的代码库,微服务提供了一个“分而治之”的开发思路。

容器化应用软件部署平台Docker,是容器基础设施业界的领头羊。容器的真正价值在于,团队可以在进行任务的同时开启一个运行时环境。在将微服务向企业的推广中,Docker或许是当今最有影响力的平台。

通过向终端用户提供一个操作系统的多个实例,虚拟机简化了软件开发和测试。相似的,容器在应用和主机操作系统之间添加了一个虚拟层。两者之间的差别在于,容器仅运行一个操作系统实例,不需要系统管理程序。总之,容器所需要的内存和运行时间就大大减少了。

对所有的应用开发过程来说,日志记录都是一个核心部分,尤其是出错的时候特别有用。但容器化应用与传统应用的日志记录是不同的。对Docker来说,好的日志不仅要记录下应用和主机操作系统,还要记录Docker服务。目前,有很多种针对Docker化应用的日志技术和方法,以下是其中最佳的5个实践。

基于应用的日志记录

此种日志记录方式中,容器内的应用程序采用日志记录框架来处理日志记录过程。例如,一个Java应用程序可能采用Log4j2格式,将日志文件发送到远程服务器上,而绕开了Docker环境和操作系统。

尽管基于应用的日志记录方式将记录事件的最大控制权交给了开发者,但也对应用带来许多开销。这种方式对传统的应用环境来说更为适用,应为它允许开发者继续使用日志记录框架(比如Log4j2),无需再向主机添加日志功能。

使用数据卷

容器在属性上说,是短暂的,即容器中的所有文件都会随着容器的关闭而丢失。而在日志记录的问题上,容器要么将事件记录在一个集中的日志服务(比如Loggly),要么将时间记录到数据卷中。数据卷的定义是“容器内的标记目录,用于保存持久数据或共享数据”。

使用数据卷的优点在于,由于它跟主机目录关联,日志数据是持久的,且可以与其他容器共享。在容器发生故障或者关闭的时候,这种日志记录方式降低了丢失数据的可能性。点击这里(http://t.cn/RbDRTeP)可以查看在Ubuntu设置Docker数据卷的说明。

Docker日志驱动

第三种方式就是使用Docker平台的日志驱动,将日志事件记录在主机的系统日志实例上。Docker日志驱动直接从容器的标准输出和标准错误输出中读取日志事件;这种方式避免了对日志文件的读写,进而转化为性能增益。

但是,使用Docker日志驱动有以下几个缺点:

  • 不允许对日志进行解析,只能转发。

  • Docker日志命令只对日志驱动JSON文件有效。

  • 链接不到TCP服务器时,容器终止。

日志专用容器

这种方式最大的优点在于,允许日志事件完全在Docker环境中管理。日志专用容器可以收集其他容器的日志事件,将它们聚在一起,进行存储或转发到第三方服务。这种做法降低了对主机的依赖。

日志专用容器还有以下优点:

  • 自动采集、监控和分析日志事件。

  • 无需配置,自动对日志事件进行评分。

  • 可以通过日志事件、数据和Docker API数据等多方面数据进行日志检索。

Sidecar方法

Sidecar是目前较为流行的管理微服务架构的方法。它的概念来自挎斗摩托车的挎斗。 “Sidecar和服务并行,通过REST这种API连接到HTTP提供‘平台基础设施功能’”(http://t.cn/Rp80Mj8)。

从日志记录的角度看,sidecar方法的优点在于每个容器都与自己的日志记录容器相连接(应用容器保存了日志事件,日志记录容器对其进行分类并转发到像Loggly这种日志记录管理系统中)。

未分类

sidecar方法尤其适用于较大规模的部署,记录信息更加专业化,允许自定义分类。不过sidecar的设置也相当复杂。

docker用户过度到kubectl命令行指南

对于没有使用过 kubernetes 的 docker 用户,如何快速掌握 kubectl 命令?kubectl 跟 docker 命令之间有什么区别和联系?

在本文中,我们将向 docker-cli 用户介绍 Kubernetes 命令行如何与 api 进行交互。该命令行工具——kubectl,被设计成 docker-cli 用户所熟悉的样子,但是它们之间又存在一些必要的差异。该文档将向您展示每个 docker 子命令和 kubectl 与其等效的命令。

在使用 kubernetes 集群的时候,docker 命令通常情况是不需要用到的,只有在调试程序或者容器的时候用到,我们基本上使用 kubectl 命令即可,所以在操作 kubernetes 的时候我们抛弃原先使用 docker 时的一些观念。

docker run

如何运行一个 nginx Deployment 并将其暴露出来? 查看: https://kubernetes.io/docs/user-guide/kubectl/#run

使用 docker 命令:

$ docker run -d --restart=always -e DOMAIN=cluster --name nginx-app -p 80:80 nginx
a9ec34d9878748d2f33dc20cb25c714ff21da8d40558b45bfaec9955859075d0
$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                         NAMES
a9ec34d98787        nginx               "nginx -g 'daemon of   2 seconds ago       Up 2 seconds        0.0.0.0:80->80/tcp, 443/tcp   nginx-app 

使用 kubectl 命令:

# start the pod running nginx
$ kubectl run --image=nginx nginx-app --port=80 --env="DOMAIN=cluster"
deployment "nginx-app" created

在大于等于 1.2 版本 Kubernetes 集群中,使用kubectl run 命令将创建一个名为 “nginx-app” 的 Deployment。如果您运行的是老版本,将会创建一个 replication controller。 如果您想沿用旧的行为,使用 –generation=run/v1 参数,这样就会创建 replication controller。查看: https://kubernetes.io/docs/user-guide/kubectl/#run 获取更多详细信息。

# expose a port through with a service
$ kubectl expose deployment nginx-app --port=80 --name=nginx-http
service "nginx-http" exposed

在 kubectl 命令中,我们创建了一个 Deployment,这将保证有 N 个运行 nginx 的 pod(N 代表 spec 中声明的 replica 数,默认为 1)。我们还创建了一个 service,使用 selector 匹配具有相应的 selector 的 Deployment。查看: https://kubernetes.io/docs/user-guide/quick-start 获取更多信息。

默认情况下镜像会在后台运行,与docker run -d … 类似,如果您想在前台运行,使用:

kubectl run [-i] [--tty] --attach <name> --image=<image>

与 docker run … 不同的是,如果指定了 –attach ,我们将连接到 stdin,stdout 和 stderr,而不能控制具体连接到哪个输出流(docker -a …)。

因为我们使用 Deployment 启动了容器,如果您终止了连接到的进程(例如 ctrl-c),容器将会重启,这跟 docker run -it不同。 如果想销毁该 Deployment(和它的 pod),您需要运行 kubeclt delete deployment 。

docker ps

如何列出哪些正在运行?查看: https://kubernetes.io/docs/user-guide/kubectl/#get

使用 docker 命令:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                         NAMES
a9ec34d98787        nginx               "nginx -g 'daemon of   About an hour ago   Up About an hour    0.0.0.0:80->80/tcp, 443/tcp   nginx-app

使用 kubectl 命令:

$ kubectl get po
NAME              READY     STATUS    RESTARTS   AGE
nginx-app-5jyvm   1/1       Running   0          1h

docker attach

如何连接到已经运行在容器中的进程?查看: https://kubernetes.io/docs/user-guide/kubectl/#attach

使用 docker 命令:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                         NAMES
a9ec34d98787        nginx               "nginx -g 'daemon of   8 minutes ago       Up 8 minutes        0.0.0.0:80->80/tcp, 443/tcp   nginx-app
$ docker attach a9ec34d98787
...

使用 kubectl 命令:

$ kubectl get pods
NAME              READY     STATUS    RESTARTS   AGE
nginx-app-5jyvm   1/1       Running   0          10m
$ kubectl attach -it nginx-app-5jyvm
...

docker exec

如何在容器中执行命令?查看: https://kubernetes.io/docs/user-guide/kubectl/#exec

使用 docker 命令:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                         NAMES
a9ec34d98787        nginx               "nginx -g 'daemon of   8 minutes ago       Up 8 minutes        0.0.0.0:80->80/tcp, 443/tcp   nginx-app
$ docker exec a9ec34d98787 cat /etc/hostname
a9ec34d98787

使用 kubectl 命令:

$ kubectl get po
NAME              READY     STATUS    RESTARTS   AGE
nginx-app-5jyvm   1/1       Running   0          10m
$ kubectl exec nginx-app-5jyvm -- cat /etc/hostname
nginx-app-5jyvm

执行交互式命令怎么办?

使用 docker 命令:

$ docker exec -ti a9ec34d98787 /bin/sh
# exit

使用 kubectl 命令:

$ kubectl exec -ti nginx-app-5jyvm -- /bin/sh      
# exit

更多信息请查看: https://kubernetes.io/docs/tasks/kubectl/get-shell-running-container

docker logs

如何查看运行中进程的 stdout/stderr?查看: https://kubernetes.io/docs/user-guide/kubectl/#logs

使用 docker 命令:

$ docker logs -f a9e
192.168.9.1 - - [14/Jul/2015:01:04:02 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"
192.168.9.1 - - [14/Jul/2015:01:04:03 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0" "-"

使用 kubectl 命令:

$ kubectl logs -f nginx-app-zibvs
10.240.63.110 - - [14/Jul/2015:01:09:01 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.26.0" "-"
10.240.63.110 - - [14/Jul/2015:01:09:02 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.26.0" "-"

现在是时候提一下 pod 和容器之间的细微差别了;默认情况下如果 pod 中的进程退出 pod 也不会终止,相反它将会重启该进程。这类似于 docker run 时的 –restart=always 选项, 这是主要差别。在 docker 中,进程的每个调用的输出都是被连接起来的,但是对于 kubernetes,每个调用都是分开的。要查看以前在 kubernetes 中执行的输出,请执行以下操作:

$ kubectl logs --previous nginx-app-zibvs
10.240.63.110 - - [14/Jul/2015:01:09:01 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.26.0" "-"
10.240.63.110 - - [14/Jul/2015:01:09:02 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.26.0" "-"

查看: https://kubernetes.io/docs/concepts/cluster-administration/logging 获取更多信息。

docker stop 和 docker rm

如何停止和删除运行中的进程?查看: https://kubernetes.io/docs/user-guide/kubectl/#delete

使用 docker 命令:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                         NAMES
a9ec34d98787        nginx               "nginx -g 'daemon of   22 hours ago        Up 22 hours         0.0.0.0:80->80/tcp, 443/tcp   nginx-app
$ docker stop a9ec34d98787
a9ec34d98787
$ docker rm a9ec34d98787
a9ec34d98787

使用 kubectl 命令:

$ kubectl get deployment nginx-app
NAME        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-app   1         1         1            1           2m
$ kubectl get po -l run=nginx-app
NAME                         READY     STATUS    RESTARTS   AGE
nginx-app-2883164633-aklf7   1/1       Running   0          2m
$ kubectl delete deployment nginx-app
deployment "nginx-app" deleted
$ kubectl get po -l run=nginx-app
# Return nothing

请注意,我们不直接删除 pod。使用 kubectl 命令,我们要删除拥有该 pod 的 Deployment。如果我们直接删除pod,Deployment 将会重新创建该 pod。

docker login

在 kubectl 中没有对 docker login 的直接模拟。如果您有兴趣在私有镜像仓库中使用 Kubernetes,请参阅: https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry

docker version

如何查看客户端和服务端的版本?查看: https://kubernetes.io/docs/user-guide/kubectl/#version

使用 docker 命令:

$ docker version
Client version: 1.7.0
Client API version: 1.19
Go version (client): go1.4.2
Git commit (client): 0baf609
OS/Arch (client): linux/amd64
Server version: 1.7.0
Server API version: 1.19
Go version (server): go1.4.2
Git commit (server): 0baf609
OS/Arch (server): linux/amd64

使用 kubectl 命令:

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.9+a3d1dfa6f4335", GitCommit:"9b77fed11a9843ce3780f70dd251e92901c43072", GitTreeState:"dirty", BuildDate:"2017-08-29T20:32:58Z", OpenPaasKubernetesVersion:"v1.03.02", GoVersion:"go1.7.5", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.9+a3d1dfa6f4335", GitCommit:"9b77fed11a9843ce3780f70dd251e92901c43072", GitTreeState:"dirty", BuildDate:"2017-08-29T20:32:58Z", OpenPaasKubernetesVersion:"v1.03.02", GoVersion:"go1.7.5", Compiler:"gc", Platform:"linux/amd64"}

docker info

如何获取有关环境和配置的各种信息?查看: https://kubernetes.io/docs/user-guide/kubectl/#cluster-info

使用 docker 命令:

$ docker info
Containers: 40
Images: 168
Storage Driver: aufs
 Root Dir: /usr/local/google/docker/aufs
 Backing Filesystem: extfs
 Dirs: 248
 Dirperm1 Supported: false
Execution Driver: native-0.2
Logging Driver: json-file
Kernel Version: 3.13.0-53-generic
Operating System: Ubuntu 14.04.2 LTS
CPUs: 12
Total Memory: 31.32 GiB
Name: k8s-is-fun.mtv.corp.google.com
ID: ADUV:GCYR:B3VJ:HMPO:LNPQ:KD5S:YKFQ:76VN:IANZ:7TFV:ZBF4:BYJO
WARNING: No swap limit support

使用 kubectl 命令:

$ kubectl cluster-info
Kubernetes master is running at https://108.59.85.141
KubeDNS is running at https://108.59.85.141/api/v1/namespaces/kube-system/services/kube-dns/proxy
KubeUI is running at https://108.59.85.141/api/v1/namespaces/kube-system/services/kube-ui/proxy
Grafana is running at https://108.59.85.141/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy
Heapster is running at https://108.59.85.141/api/v1/namespaces/kube-system/services/monitoring-heapster/proxy
InfluxDB is running at https://108.59.85.141/api/v1/namespaces/kube-system/services/monitoring-influxdb/proxy