以Docker和Rocket为代表的容器技术现在正变得越来越流行,它改变着公司和用户创建、发布、运行分布式应用的方式,在未来5年将给云计算行业带来它应有的价值。它的诱人之处在于:
资源独立、隔离
资源隔离是云计算平台的最基本需求。Docker 通过 Linux namespace, cgroup限制了硬件资源与软件运行环境,与宿主机上的其他应用实现了隔离,做到了互不影响。不同应用或服务以“集装箱”(container)为单位装“船”或卸“船”,“集装箱船”(运行container的宿主机或集群 )上,数千数万个“集装箱”排列整齐,不同公司、不同种类的“货物”(运行应用所需的程序、组件、运行环境、依赖)保持独立。
环境的一致性
开发工程师完成应用开发后 build 一个 docker image,基于这个 image 创建的 container 像是一个集装箱,里面打包了各种“散件货物”(运行应用所需的程序、组件、运行环境、依赖)。无论这个集装箱在哪里:开发环境、测试环境、生产环境,都可以确保集装箱里面的“货物”种类与个数完全相同,软件包不会在测试环境缺失,环境变量不会在生产环境忘记配置,开发环境与生产环境不会因为安装了不同版本的依赖导致应用运行异常。这样的一致性得益于“发货”(build docker image)时已经密封到“集装箱”中,而每一个环节都是在运输这个完整的、不需要拆分合并的“集装箱”。
轻量化
相比传统的虚拟化技术(VM),使用 docker 在cpu, memory, disk IO, network IO上的性能损耗都有同样水平甚至更优的表现。Container 的快速创建、启动、销毁受到很多赞誉。
Build Once, Run Everywhere
这个特性着实吸引了很多人,“货物”(应用)在“汽车”、“火车”、“轮船”(私有云、公有云等服务)之间迁移交换时,只需要迁移符合标准规格和装卸方式的“集装箱”(docker container),削减了耗时费力的人工“装卸”(上线、下线应用),带来的是巨大的时间人力成本节约。这使未来仅有少数几个运维人员运维超大规模装载线上应用的容器集群成为可能,如同60年代后少数几个机器操作员即可在几小时内连装带卸完一艘万级集装箱船。
容器技术现在也被广泛应用于数据库领域。它的“Build Once, Run Everywhere”的特性大大减少了花在安装配置数据库环境上的时间,因为即使对于从事数据库多年的DBA而言,安装配置数据库环境依旧是一项看似简单但却经常不顺利的工作。当然,容器技术的其他优势也被很好的应用在数据库的使用中。
SequoiaDB 作为一款优秀的国产分布式关系型数据库,已经得到了越来越多用户的认可。本文以 Docker 为例,着重介绍如何用 Dockerfile 快速构建 SequoiaDB镜像,以及如何利用容器快速搭建和启动 SequoiaDB 集群供应用系统使用。
如何安装 docker 以及配置镜像仓库不是本文的重点,网上有很多相关的技术文章。需要指出的是本文采用阿里云镜像仓库,因为将镜像上传到 Docker 官方仓库的速度实在不敢恭维。如何注册和使用阿里云镜像仓库,可以参考文章(http://www.jb51.net/article/123101.htm)。
STEP 1:创建 Dockerfile,内容如下,只需要几行简单的指令即可
# Sequoiadb DOCKERFILES PROJECT # -------------------------- # This is the Dockerfile for Sequoiadb 2.8.4 # # REQUIRED FILES TO BUILD THIS IMAGE # ---------------------------------- # (1) sequoiadb-2.8.4-linux_x86_64-enterprise-installer.run # (2) installSDB.sh # # HOW TO BUILD THIS IMAGE # ----------------------- # Put all downloaded files in the same directory as this Dockerfile # Run: # $ sudo docker build -t sequoiadb:2.8.4 . # # Pull base image FROM ubuntu # Environment variables required for this build ENV INSTALL_BIN_FILE="sequoiadb-2.8.4-linux_x86_64-enterprise-installer.run" \ INSTALL_SDB_SCRIPT="installSDB.sh" \ INSTALL_DIR="/opt/sequoiadb" # Copy binaries ADD $INSTALL_BIN_FILE $INSTALL_SDB_SCRIPT $INSTALL_DIR/ # Install SDB software binaries RUN chmod 755 $INSTALL_DIR/$INSTALL_SDB_SCRIPT \ && $INSTALL_DIR/$INSTALL_SDB_SCRIPT \ && rm $INSTALL_DIR/$INSTALL_SDB_SCRIPT
其中installSDB.sh脚本内容如下:
chmod 755 $INSTALL_DIR/$INSTALL_BIN_FILE $INSTALL_DIR/$INSTALL_BIN_FILE --mode unattended rm $INSTALL_DIR/$INSTALL_BIN_FILE echo 'service sdbcm start' >> /root/.bashrc
需要注意的是本例采用Sequoiadb企业版2.8.4,您也可以从巨杉官网下载社区版(选择tar包,下载然后解压),替换本例中的介质名称。
巨杉官网下载地址:http://download.sequoiadb.com/cn/
STEP 2:创建镜像
root用户执行:
docker build -t sequoiadb:2.8.4 .
如果是普通用户,需要使用sudo:
sudo docker build -t sequoiadb:2.8.4 .
STEP3:登陆阿里云镜像仓库
docker login --username=xxx registry.cn-hangzhou.aliyuncs.com
其中xxx为您在阿里云注册的账号。
STEP4:查看本地sequoiadb镜像id
docker images
STEP5:标记本地镜像,将其归入阿里云仓库
docker tag 04dc528f2a6f registry.cn-hangzhou.aliyuncs.com/508mars/sequoiadb:latest
其中 04dc528f2a6f 是笔者本地 sequoiadb 镜像 id,新的标记格式有一定要求,registry.cn-hangzhou.aliyuncs.com 为阿里云仓库地址,508mars 是笔者在阿里云的用户名,sequoiadb 是镜像名,latest 是 tag。
STEP6:提交sequoiadb镜像到镜像库
docker push registry.cn-hangzhou.aliyuncs.com/508mars/sequoiadb:latest
Docker 的网络默认采用 bridge 模式,采用 bridge 模式的容器有如下特点:
1)同一宿主机的容器之间可以互相ping通
2)不同宿主机的容器之间互相ping不同
但是 SequoiaDB 集群要求所有节点之间是可以互通的,所以如果运行 SequoiaDB 的容器跑在不同宿主机上,docker 的默认网络模式显然不合适。有很多种方法可以解决不同宿主机容器之间的连通性问题,本文只介绍 weave 虚拟网络这个解决方案,因为 weave 同时提供了一个DNS server的功能,有了该功能,在利用容器部署 SequoiaDB 集群时不再需要修改各个容器内部的 /etc/hosts,大大简化了自动化部署的步骤。
STEP1:安装 weave 网络
curl -s -L git.io/weave -o /usr/local/bin/weave chmod a+x /usr/local/bin/weave
需要在所有宿主机安装,笔者采用了三台虚拟机作为宿主机:sdb1, sdb2和sdb3。
STEP2:启动 weave 网络
weave launch
第一次启动时会下载weave镜像。
STEP3:从阿里云仓库下载 sequoiadb 镜像
docker pull registry.cn-hangzhou.aliyuncs.com/508mars/sequoiadb
STEP4:在所有宿主机创建docker的挂载卷
cd /home/sdbadmin mkdir -p data/disk1 data/disk2 data/disk3 mkdir -p conf/local chmod -R 777 data chmod -R 777 conf
挂载卷的位置可以自定义,但总的来说需要创建两大类挂载卷,一类用来存放集合数据,如本例中的data/disk1, data/disk2, data/disk3,一类用来存放节点配置信息,如本例中的conf/local。这样即使容器被误删了,依旧可以启动一个新容器来扮演被误删的容器的角色。
STEP5:启动容器
sdb1:
weave stop weave launch eval $(weave env) docker run -dit --name sdbserver1 -p 11810:11810 -v /home/sdbadmin/data:/data -v /home/sdbadmin/conf/local:/opt/sequoiadb/conf/local registry.cn-hangzhou.aliyuncs.com/508mars/sequoiadb
sdb2:
weave stop weave launch 192.168.17.132 eval $(weave env) docker run -dit --name sdbserver2 -p 11810:11810 -v /home/sdbadmin/data:/data -v /home/sdbadmin/conf/local:/opt/sequoiadb/conf/local registry.cn-hangzhou.aliyuncs.com/508mars/sequoiadb
sdb3:
weave stop weave launch 192.168.17.132 eval $(weave env) docker run -dit --name sdbserver3 -p 11810:11810 -v /home/sdbadmin/data:/data -v /home/sdbadmin/conf/local:/opt/sequoiadb/conf/local registry.cn-hangzhou.aliyuncs.com/508mars/sequoiadb
其中 192.168.17.132 是sdb1的IP地址,11810是对外暴露的集群访问端口。宿主机存放节点配置信息的卷必须挂到容器的 /opt/sequoiadb/conf/local 目录,存放表数据的卷可以挂载到用户自定义的目录,但是集群一旦创建后,不可更改。启动容器的时候必须指定机器名,因为在构建完集群后,机器名会被保存在SequoiaDB 的系统表中,节点的机器名与系统表中不一致会导致无法加入到集群。在使用 weave 的场景下,建议使用 --name 选项,不要使用 --hostname 设置机器名。后者会阻止 weave 将机器名添加到 DNS 服务器,weave 会自动根据 --name 的值来设置机器名,同时在机器名后增加 weave.local 域名,并添加到的DNS服务器。
STEP6:将创建SequoiaDB集群的脚本拷贝到容器中
docker cp create_cluster.js sdbserver1:/data
create_cluster.js内容如下:
var array_hosts = ["sdbserver1.weave.local", "sdbserver2.weave.local", "sdbserver3.weave.local"]; var array_dbroot = ["/data/disk1/sequoiadb/database","/data/disk2/sequoiadb/database","/data/disk3/sequoiadb/database"]; var port_sdbcm = "11790"; var port_temp_coord = "18888"; var cataloggroup = {gname:"SYSCatalogGroup", gport:"11820", ghosts:["sdbserver1.weave.local", "sdbserver2.weave.local", "sdbserver3.weave.local"]}; var array_coordgroups = [ {gname:"SYSCoord", gport:"11810", ghosts:["sdbserver1.weave.local", "sdbserver2.weave.local", "sdbserver3.weave.local"]} ]; var array_datagroups = [ {gname:"dg1", gport:"11830", ghosts:["sdbserver1.weave.local", "sdbserver2.weave.local", "sdbserver3.weave.local"], goptions:{transactionon:true}} ,{gname:"dg2", gport:"11840", ghosts:["sdbserver1.weave.local", "sdbserver2.weave.local", "sdbserver3.weave.local"], goptions:{transactionon:true}} ,{gname:"dg3", gport:"11850", ghosts:["sdbserver1.weave.local", "sdbserver2.weave.local", "sdbserver3.weave.local"], goptions:{transactionon:true}} ]; var array_domains = [ {dname:"allgroups", dgroups:["dg1", "dg2", "dg3"], doptions:{AutoSplit:true}} ]; println("启动临时协调节点"); var oma = new Oma(array_coordgroups[0].ghosts[0], port_sdbcm); oma.createCoord(port_temp_coord, array_dbroot[0]+"/coord/"+port_temp_coord); oma.startNode(port_temp_coord); println("创建编目节点组:"+cataloggroup.ghosts[0]+" "+cataloggroup.gport+" "+array_dbroot[0]+"/cata/"+cataloggroup.gport); var db = new Sdb(array_coordgroups[0].ghosts[0], port_temp_coord); db.createCataRG(cataloggroup.ghosts[0], cataloggroup.gport, array_dbroot[0]+"/cata/"+cataloggroup.gport); var cataRG = db.getRG("SYSCatalogGroup"); for (var i in cataloggroup.ghosts) { if (i==0) {continue;} println("创建编目节点: "+cataloggroup.ghosts[i]+" "+cataloggroup.gport+" "+array_dbroot[0]+"/cata/"+cataloggroup.gport); var catanode = cataRG.createNode(cataloggroup.ghosts[i], cataloggroup.gport, array_dbroot[0]+"/cata/"+cataloggroup.gport); catanode.start(); } println("创建协调节点组"); var db = new Sdb(array_coordgroups[0].ghosts[0], port_temp_coord); var coordRG = db.createCoordRG(); for (var i in array_coordgroups) { for (var j in array_coordgroups[i].ghosts) { println("创建协调节点组:"+array_coordgroups[i].ghosts[j]+" "+array_coordgroups[i].gport+" "+array_dbroot[0]+"/coord/"+array_coordgroups[i].gport); coordRG.createNode(array_coordgroups[i].ghosts[j], array_coordgroups[i].gport, array_dbroot[0]+"/coord/"+array_coordgroups[i].gport); } } coordRG.start(); println("删除临时协调节点") var oma = new Oma(array_coordgroups[0].ghosts[0], port_sdbcm); oma.removeCoord(port_temp_coord); println("创建数据节点组") var db = new Sdb(array_coordgroups[0].ghosts[0], array_coordgroups[0].gport); var k=0; for (var i in array_datagroups) { var dataRG = db.createRG(array_datagroups[i].gname); for (var j in array_datagroups[i].ghosts) { println("创建数据节点:"+array_datagroups[i].gname+" "+array_datagroups[i].ghosts[j]+" "+array_datagroups[i].gport+" "+array_dbroot[k]+"/data/"+array_datagroups[i].gport+" "+array_datagroups[i].goptions) dataRG.createNode(array_datagroups[i].ghosts[j], array_datagroups[i].gport, array_dbroot[k]+"/data/"+array_datagroups[i].gport, array_datagroups[i].goptions); } dataRG.start(); k++; } println("创建域"); var db = new Sdb(array_coordgroups[0].ghosts[0], array_coordgroups[0].gport); for (var i in array_domains) { println("创建域:"+array_domains[i].dname+" "+array_domains[i].dgroups+" "+array_domains[i].doptions) db.createDomain(array_domains[i].dname, array_domains[i].dgroups, array_domains[i].doptions ); }
STEP7:创建SequoiaDB集群
docker exec sdbserver1 su - sdbadmin -c "sdb -f /data/create_cluster.js
至此 SequoiaDB 集群创建并启动完成,后面再启动容器的时候集群会自动启动。
SequoiaDB 利用容器技术很好的实现了集群快速部署,大大简化了初学者安装部署的难度。后期笔者还会在 SequoiaDB 镜像制作上做一些优化,因为目前做出来的镜像有点大,主要根源是采用 ADD 或 COPY 命令将安装介质拷贝到 Docker 容器中会生成一个新的镜像 image1,最终生成的镜像 image2 中虽然删除了安装介质,但是它在 image1 之上,所以 image2 的大小依旧包含安装介质。最好采用 ADD 拷贝 tar 包(ADD会自动解压)或者采用类似如下的方式:
RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.xz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all