Docker:基本知识

我们都在说未来要“云”化,那么我们应该要将服务打包放到“云”上。那么我们的应用需要容器化,那么我们要安装Docker

Docker是一个开源的PaaS产品,2013年开源发布Docker引擎后,迅速崛起。

本文介绍Docker的基本概念跟入门使用。

Docker容器是什么?

Docker跟虚拟机有些像,但Docker不是虚拟机。虚拟机是对硬件进行虚拟化,Docker则使用了操作系统级别的虚拟化技术。Docker容器运行在宿主机中,但容器之间是隔离的,并且将各自的依赖库、配置打包进了容器。

镜像(Image)

操作系统分为内核与用户空间。对于Linux而言,内核启动后会挂载文件系统。Docker镜像是一个特殊的文件系统,除了提供容器运行时需要的程序、库、资源和配置外,还包含了一些配置参数,以支持运行时的一些功能。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

容器(Container)

镜像(Image)和容器(Container)的关系,就像是类与实例一样:镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、暂停、停止、删除等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 文件系统、网络配置、进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。

前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为 容器存储层。

仓库(Repository)

镜像的分发到其他服务器上使用,需要一个分发中心服务。Docker Registry是这样的服务,而仓库则是镜像的仓储。一个Registry中可以包含多个仓库(Repository),每个仓库可以包含多个标签(Tag),每个标签对应一个镜像。

通常,一个仓库会包含同一软件的不同版本的镜像,我们可以通过 <用户名>/<仓库名>:<标签> 的格式来指定具体使用什么版本。latest是默认标签。比如nginx:latest。

Docker的基本命令

Docker运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker会从镜像仓库下载该镜像。

获取镜像

从公开的仓库(或私有仓库)可以获取镜像,如Docker Hub上:

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

列举镜像

docker image ls

1
2
3
4
5
6
qilin:~ ryan$docker image list
REPOSITORY TAG IMAGE ID CREATED SIZE
gitlab/gitlab-ce latest e6b464b98aff 2 days ago 1.92GB
postgres latest f30ff596f17b 3 days ago 314MB
jenkins/jenkins lts 7e250da768ed 8 days ago 619MB
sonarqube latest e7cc715e8756 5 weeks ago 504MB

镜像包失去镜像名后(比如docker pull更新),旧的镜像没有名称了,称之为虚悬镜像(dangling)。
通过命令docker image ls -f dangling=true可以查找到。
一般来说,虚悬镜像是可以随意删除的,可以用下面的命令删除:docker image prune

需要留意,可能有些依赖的镜像被下载下来,这些不是dangling。

删除镜像

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

运行容器

基于镜像新建一个容器并启动。下例中启动ubuntu中的bash并进行交互式操作。以Jenkins的操作指令示例:

1
docker run -it -d -p port1:port2 jenkins/jenkins:lts

docker run的参数:
-it:-i是交互式操作,-t终端。在交互式终端运行容器。
-d:守护式启动容器
-p port1:port2 表示宿主机和容器的端口映射
–name 表示给生成的容器起名字
-v /volume/in/host:/var/volume/in/container 数据卷挂载

对于已创建的处于stop状态的容器,可以使用docker start $containerId再次启动容器。

查看容器

1
docker ps -a

查看容器的日志:

1
2
3
4
docker logs [-f] [t] [--tail]     容器名
-f --follows=true | false 是否跟踪输出,默认为 false
-t --timestamps=true | false 是否带时间戳,默认为 false
--tail="all" 显示最后多少条输出,默认为 all

docker -f $containerId

进入容器

如果要修改容器,可以用docker exec命令进入容器,

1
docker exec -it $containerId bash

用-it指定交互式终端进入,最后的bash是指定进入容器后执行的命令。

进入容器后可能会修改到配置,定制后需要将容器存储保存下来,就是在原有镜像的基础上,成为镜像。

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

示例:

1
2
3
4
5
6
7
8
9
docker commit \

--author "Jason Chan <jasonchan@gmail.com>" \

--message "修改了默认网页" \

server \

nginx:v2

停止容器

1
docker stop $containerId

使用docker stop来终止一个运行中的容器。

导出容器

1
docker export 7691a814370e > container.tar

可以将容器做一个快照,导出成为镜像。

以上是关于docker的container的一些入门介绍,进阶内容可以参考官网的介绍。

Docker Compose

Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.

绝大多数情况下,我们的应用都是有外部依赖的,比如数据库,MQ或者外部服务。当我们把服务都容器化的时候,管理和协调这些容器跟应用本身,就变成一件糟心的事儿了。Docker的解决方案是Docker Compose

Docker Compose允许我们通过一个配置文件定义整个应用,包括它的依赖。这样会使开发变得相对incredibly容易。使用Compose可以:

  • 一行命令启动整个应用:docker-compose up -d
  • 简单的DNS解析:可以在service-a中直接使用http://service-b调用service-b
  • 一行配置挂载数据卷

下面是一个docker-compose.yml文件的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
environment:
FLASK_ENV: development
REDIS_HOST: redis
REDIS_PORT: 6379
redis:
image: "redis:alpine"

第一行version定义了版本;services下配置了webredis两个服务(的容器)。ports定义了<HOST_PORT>:<CONTAINER_PORT>的端口映射;volumes定义了宿主机与容器文件系统的映射,例子中是将宿主机的当前目录挂载为容器内的/code路径;environment定义了环境变量。

实际上还可以配置网络,这一点没有谈到:容器间是隔离的,网络不是直接相通的。这是官网的一个示例图:

有兴趣可以从官网文档了解。

反向代理

为了让非宿主的机器能访问容器的服务,需要做反向代理。
创建一个包含web应用的容器,一般会做宿主机到容器的端口映射,如:

1
docker run -p 8080:80 -d webapp

使用了宿主机8080端口映射到容器的80端口。如果使用NGINX做反向代理,需要配置转发,参考配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
# server_name ; # 域名
location / {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 转发
proxy_pass http://localhost:8070;
}
}

如果是uWSGI应用

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
# server_name ; # 域名

# charset
charset utf-8;

location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8080;
uwsgi_read_timeout 120s;
}
}

还有什么想说的?

There is no problem in computer science that can’t be solved by adding another level of indirection.

嗯!

Author: Jason

Permalink: http://blog.knpc21.com/tech/docker-basic/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。

Comments