如果想要动手试试 Docker ,那么就行动吧!
本文会讲解Docker如何工作,会遇到什么问题,以及如何助力基本的开发任务 - 构建微服务。
本文使用带有MySQL后台的Node.js服务作为示例,介绍如何在本地运行代码,以及运行微服务和数据库的容器。
Docker本质上是一种软件,让用户创建镜像(很像虚拟机的模板),并且随后在容器里运行该镜像的实例。
Docker维护着有很多镜像的存储库,称为 Docker Hub ,用户可以将其作为尝试镜像的起点,或者作为镜像的免费存储。用户可以安装Docker,选择想要使用的镜像,然后在容器里运行其实例。
本文会尝试构建镜像,从镜像创建容器等等。
要跟上本文的实验,需要安装Docker。
在docs.docker.com/engine/installation处查看平台适用的安装向导。
如果你使用的是Mac或者Windows,考虑使用一个虚拟机。我在Mac OS X上使用Parallels运行Ubuntu虚拟机来完成大多数开发任务。因为可以做快照,当实验的时候把环境破坏了的话,可以恢复快照回去。
键入如下命令:
docker run -it ubuntu
一段时间之后,你会看到如下提示:
root@719059da250d:/#
试试如下命令,然后退出容器:
root@719059da250d:/# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.4 LTS
Release: 14.04
Codename: trusty
root@719059da250d:/# exit
这看上去似乎没干什么,其实底层发生了很多事情!
你看到的是在你的机器上运行着的Ubuntu的隔离容器环境里的bash shell。这个环境完全归你所有——可以在上面安装东西,运行软件,可以做任何你想做的事情。
下图表明了刚刚发生了什么(图来自于《 理解Docker文档架构 》一文):
docker
: 运行docker客户端 run
: 运行新容器的命令 -it
: 让容器带有交互型终端的参数 ubuntu
: 容器所依赖的基础镜像 ubuntu
的镜像——这里发现有。 ubuntu
镜像创建新容器。 尝试下面这些命令:
docker run -it haskell
docker run -it java
docker run -it python
这里我们不打算使用 Haskell ,但是你可以看到,运行一个环境非常非常容易。
构建自己的镜像也很简单,可以在其中包含应用程序或者服务,可以是数据库等等。随后就可以在任意安装了Docker的机器上运行它们——镜像可以保证以相同的,可预测的方式在每台机器上运行。我们可以将软件及其运行所需的环境整体构建成代码,并且轻松部署。
让我们以一个简单微服务为例。
这里将构建一个管理邮件地址到电话号码目录的微服务,使用Node.js和MySQL。
要完成本地开发,需要安装MySQL,并且创建一个测试数据库...
创建本地数据库,并且在之上运行脚本,这很容易,但是可能会带来一些问题。很多不受控制的事情开始了。它可能能工作,我们甚至可以使用一些提交进代码库的shell脚本来控制这些步骤,但是如果其他开发人员已经安装了MySQL了呢?如果他们的数据库已经使用了我们想要创建的名称'users'了呢?
这是很好的Docker用户场景。我们可能不想在Docker里运行生产环境数据库(比如可能会使用Amazon RDS),但是可以瞬间使用Docker容器创建一个干净的MySQL数据库做开发——保持干净的开发机器,并且保证所有东西都在控制中,并且可重复。
运行如下命令:
docker run --name db -e MYSQL_ROOT_PASSWORD=123 -p 3306:3306 mysql:latest
该命令启动一个运行着的MySQL实例,通过使用3306端口,root密码123可以访问该实例。
1. docker run
告诉引擎,用户想要运行一个镜像(在最后传入的是镜像, mysql:latest )
2. --name db
将这个容器命名为 db
.
3. -d
解绑-比如,在后台运行容器。
4. -e MYSQL_ROOT_PASSWORD=123
里 -e
参数告诉docker所提供的环境变量。这之后跟着的变量正是MySQL镜像检查且用来设置的默认root密码。
5. -p 3306:3306
告诉引擎用户想要将容器内的3306端口映射到外部的3306端口上。
最后一部分很重要——即使这是MySQL的默认端口,如果用户不显式告诉docker想要映射的端口,docker就会阻塞该端口的访问(因为容器默认是隔离的,直到用户告诉docker想要访问它们)。
该命令返回值是容器id,这是容器的指针,用户可以用它来停止容器,向容器发送命令等等。让我们看看正在运行的是哪些容器:
$ docker ps
CONTAINER ID IMAGE ... NAMES
36e68b966fd0 mysql:latest ... db
最关键的信息是容器ID,镜像和名称。连接到这个镜像看看里面有什么:
$ docker exec -it db /bin/bash
root@36e68b966fd0:/# mysql -uroot -p123
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
+--------------------+
1 rows in set (0.01 sec)
mysql> exit
Bye
root@36e68b966fd0:/# exit
下面这么做也很有意思:
1. docker exec -it db
告诉docker用户想要在名为 db
的容器里执行一个命令(我们也可以使用id,或者id的前几个字母)。 -it
确保用户有交互型终端。
2. mysql -uroot -p123
我们实际在容器里作为进程运行的命令,这里只是mysql客户端。
我们可以创建数据库,表,用户,等等。
在容器内运行MySQL需要一些Docker技巧,但是让我们先打住,看看服务。现在,使用脚本创建一个 test-database
目录来启动数据库,停止数据库以及搭建测试数据:
test-database/setup.sql
test-database/start.sh
test-database/stop.sh
启动脚本很简单:
#!/bin/sh
# Run the MySQL container, with a database named 'users' and credentials
# for a users-service user which can access it.
echo "Starting DB..."
docker run --name db -d /
-e MYSQL_ROOT_PASSWORD=123 /
-e MYSQL_DATABASE=users -e MYSQL_USER=users_service -e MYSQL_PASSWORD=123 /
-p 3306:3306 /
mysql:latest
# Wait for the database service to start up.
echo "Waiting for DB to start up..."
docker exec db mysqladmin --silent --wait=30 -uusers_service -p123 ping || exit 1
# Run the setup script.
echo "Setting up initial data..."
docker exec -i db mysql -uusers_service -p123 users < setup.sql
该脚本在一个detached容器里运行数据库镜像(比如,在后台运行),创建了一个用户来访问 users
数据库,然后等待数据库服务器启动,随后运行 setup.sql
脚本来设置初始数据。
setup.sql
是:
create table directory (user_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, email TEXT, phone_number TEXT);
insert into directory (email, phone_number) values ('homer@thesimpsons.com', '+1 888 123 1111');
insert into directory (email, phone_number) values ('marge@thesimpsons.com', '+1 888 123 1112');
insert into directory (email, phone_number) values ('maggie@thesimpsons.com', '+1 888 123 1113');
insert into directory (email, phone_number) values ('lisa@thesimpsons.com', '+1 888 123 1114');
insert into directory (email, phone_number) values ('bart@thesimpsons.com', '+1 888 123 1115');
stop.sh
脚本会停止容器并且删除容器(docker默认会保留容器,这样能够快速重启,本示例中并不需要这样):
#!/bin/sh
# Stop the db and remove the container.
docker stop db && docker rm db
之后会进一步简化这个过程,让它更加顺畅。在repo里的 step1 分支里查看这一阶段的代码。