最近有一个云服务器和数据库的迁移任务,踩坑爬坑无数次,觉得必须要记录一下。大家瓜子花生准备好,听我慢慢讲故事#手动笑哭#。
故事背景
公司是做电商业务的,在天猫有几家旗舰店数据量也很大。阿里有一个称为聚石塔的平台,专门给这些ISV提供各种云资源,强制绑定了一些业务,原本我们在聚石塔中有一台ECS和一台RDS部署在华东杭州节点,本月初突然收到阿里的邮件说是要整体迁移到张北节点,华东节点将会在9月底全部停止服务,并附带发了一份迁移文档,要我们尽快迁移。好在我们用到的资源不多,最初觉得迁移过程并不会太复杂,实际还是太天真了。像我这样只有一台服务器和一台数据库的用户迁移过程都谓之艰辛,对于那些有几十甚至上百实例的ISV,那真是欲哭无泪了。每天看着迁移群里大家的各种吐槽、抱怨、焦急、无可奈何,还有那几位一整天都在被艾特的阿里技术支持,说起来都是泪。
于是接下来制定好迁移计划,发邮件购买要用到的资源,等过了两天东西到位,就撸起袖子开干了。
忘了说了,这些东西原先是由另外一位同事负责,然而年后他就开溜了,上级指示我扛过大旗(guo)。
开胃菜
我们的RDS是SQL Server 08 R2版本,阿里在迁移通知中专门提到了这个产品,而且用到了重要提示字样,大意是说微软已经对这个版本的数据库停止了安全更新,所以张北节点已经不再售卖这个版本的实例,要先在当前节点完成版本升级后再迁移。看了下他的迁移手册,觉得异常复杂和危险重重,于是果断放弃官方方案,决定在张北节点买好2016版本数据库,直接切换数据推送,后来找阿里的技术支持咨询了这个方案,也表示可以执行。当然了,我能这样做是有一个前提的,我们的这个库是只读库,用来接收阿里的数据推送然后给业务系统查询,可以理解为只是一个过渡不存储实际的业务数据,对安全性要求不高,就算丢失也能通过淘宝开放平台的API去查询。如果是业务库,那就只能老老实实的按官方文档摸着石头过河了,看群里的反馈,这道开胃菜不好吃,我也算是幸运跳过了第一个坑。
初进坑
RDS处理完毕,那就着手开始折腾服务器,这是一台Linux的机器,系统是Centos7,主要跑了3个服务:上文提到的RDS数据查询API(一个dotnetcore2.1的程序)、Rabbitmq实例和它的管理工具、Portainer,由Docker统一管理,而Docker又由Portainer来管理。按照官方文档,先在原服务器上创建镜像,经过漫长的等待(大概40分钟吧,有的人反映等了大半天最后生成失败的,心态崩…),然后把镜像复制到张北节点,然后通过镜像生成实例,按理说新机器和原机器是完全一样的,各项服务都应该运行正常,并且专门找技术支持确认了,可实际真的不是这样。
聚石塔的服务器只开放30001-30005这几个端口,于是尝试访问一下Portainer所在的30003端口。浏览器输入地址再回车,等了几十秒后显示超时无法访问,一脸懵逼。Ping了一下服务器IP,没毛病,又登录服务器查看docker和container的运行状态以及端口映射,都没问题,又查看端口监听和防火墙,还是正常,二脸懵逼。
查一下container的日志,提示运行正常,三脸懵逼。
我的招已经用完了,没办法转向群里咨询技术支持,回复说这几个端口要走工单申请开通,WTF……老实写工单提交再到群里艾特帮忙快点处理,又陷入漫长的等待中,当时大概2点钟的样子。下午5点多工单状态更新了说正在转给技术处理请耐心等待,然后,就没有然后了接着等,到7点还是没消息决定先下班。
第二天上班发现还是没有消息,又去群里艾特技术支持,几分钟后回复叫我去给ECS绑定一个安全组,照做后再次访问30003端口依然不行,长叹一口气。又尝试访问了一下webapi所在的30001端口,神奇般的成功了#手动黑人问号脸#。咨询了公司运维,教我几个命令简单排查了下,后来因为太忙没回复我了,后来又一顿百度谷歌无果,陷入僵局。心理暗自把这个锅丢给了阿里,觉得是他们哪里配置有问题。
事情不能就这样僵着啊,Portainer起不来程序不能更新,于是打算直接在宿主机上跑一下修改后的dotnetcore程序看数据库访问是否正常。按照微软文档安装对应版本的SDK:
安装好后把发布文件上传到服务器,然后用dotnet命令启动了程序,一切正常。访问我的测试入口:
Curl http://locahost:5000/api/values/testdb/123
看到返回了数据库的测试数据,信心重拾。回过头重新折腾docker,发现docker死活起不来了,囧:
拿着错误信息又是一顿百度谷歌,不断的照网上改配置重启系统,几个小时过去依然不行,决定卸载docker重装。于是依次执行:
yum remove docker*
reboot
yum install docker
docker version
systemctl start docker
然而启动的时候问题依旧,又是长叹一口气。仔细回想了一下,只有yum update对系统做了大的改动,难道是这个问题么?不知不觉又到了晚上7点多,脑子懵的很决定先下班第二天接着搞。
真所谓一波未平一波又起。
再进坑
早上到公司和微信群的小伙伴吐糟着遭遇,大家劝我重装系统,我一边发着捂脸笑哭的表情,一边默默地上聚石塔后台点了磁盘初始化,docker启动不了的问题就算翻篇了,一切从头再来。
依然还是端口的问题,实在没辙了只有给阿里提工单问为什么端口不通,阿里工程师先后叫我排查了iptables、端口监听情况、清除iptables等等还是不行,最后要了我的服务器账号上去排查,在工单中看到阿里的工程师晚上11点多还在帮我排查问题,也真不容易。
终于,在阿里后面的回复中事情迎来了转机,给了我非常大的提示:
从中我捕捉到了2个重要信息,一个是容器的IP,一个是路由解析问题。我马上百度如何查容器的IP地址,然后试着去ping容器的IP,发现30001端口绑定的容器(172.22.0网段)正常,30003端口绑定的容器(192.168.0网段)无法访问,那么这就说明是宿主机和容器网络不通导致的问题。又查看了系统的路由表:
这个路由表有个奇怪的现象,就是192.168.0这个网段指向了2个不同的网卡,分别是eth0和docker0。我知道,eth0是宿主机默认的网关,docker0是docker启动时自动创建的虚拟网关,但是还不清楚这样的配置会有什么影响,于是百度了一下Linux路由的详细介绍,得知相同的配置会有优先级的问题,又尝试着删除eth0的配置:
route del -net 192.168.0.0 netmask 255.255.255.0
再次用公网访问30003端口,成功了!!!终于看到了熟悉的页面:
没那么简单
以为事情就此告一段落后面都是平坦大道,想不到问题又来了。通过docker run我新镜像后发现容器总是自动退出,于是寻找各种让容器持续运行的办法,一阵折腾没有效果,去微信群问小伙伴,问我是不是程序抛异常了,我顿时一种柳暗花明的感觉,立马查看容器日志:
docker logs topapi
果然是报错了:
很显然,是说我的framework版本不对,但是我的dockerfile中确实引入的2.1版本运行时:
FROM microsoft/dotnet:2.1-runtime COPY . /app WORKDIR /app EXPOSE 5000 80 ENTRYPOINT ["dotnet", "DRP.API.dll"]
退一万步说,宿主机我也已经安装过SDK,而且直接在宿主机上运行都是可以的,为什么通过docker来运行就挂了,百思不得解。只能按照提示中的信息排查是不是少装了什么组件,一阵yum install下来还是失败:
去广州微软.net俱乐部的微信群请教别人,两位大佬给我分析解答了一下,一位说是我的dockerfile在copy文件时漏了一些引用文件,要我重新修改dockerfile,不过经过多次调整测试依然无效,不得不采用第二位的办法,就是把运行时改为2.2版本:
修改dockerfile为如下内容:
# 添加基础镜像 FROM microsoft/dotnet:2.2-aspnetcore-runtime #容器中系统的工作空间 WORKDIR /app #拷贝当前文件夹下的文件到容器中系统的工作空间 COPY . /app #设置Docker容器对外暴露的端口 EXPOSE 5000 80 #运行应用程序 ENTRYPOINT ["dotnet", "DRP.API.dll"]
重新打包镜像,然后run起来,这次一切都是那么的自然,docker ps查看容器已经状态是up了。欣喜若狂,以为即将看到胜利的曙光,接着用浏览器打开我的测试入口:
http://xxx.xxx.xxx.xxx:30001/api/values/testdb/123
尴尬的报了500,心中万马奔腾….
这次学机灵了,第一时间docker logs,发现是数据库报错了:
fail: Microsoft.AspNetCore.Server.Kestrel[13] => ConnectionId:0HLM4DDINAGJC => RequestId:0HLM4DDINAGJC:00000001 RequestPath:/api/values/testdb/123 Connection id "0HLM4DDINAGJC", Request id "0HLM4DDINAGJC:00000001": An unhandled exception was thrown by the application. SqlSugar.UtilExceptions: English Message : Connection open error . A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server) Chinese Message : 连接数据库过程中发生错误,检查服务器是否正常连接字符串是否正确,实在找不到原因请先Google错误信息:A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server). at SqlSugar.AdoProvider.GetDataReader(String sql, SugarParameter[] parameters) at SqlSugar.QueryableProvider`1.GetData[TResult](KeyValuePair`2 sqlObj) at SqlSugar.QueryableProvider`1._ToList[TResult]()
很明显是数据库连接不上,检查连接字符串,没毛病,再次进入僵局。
正在苦恼时,突然想起前面删掉的那条路由,尝试重启网络恢复路由:
service network restart
再次访问测试地址,确实成功了。可问题又进入了死循环,容器内的应用无法访问。
终见天日
经过以上的种种分析后,最终把问题定在了路由这儿。既然是因为同一网段有2个网关,那么我修改一下docker的默认网段不就可以了吗?再次面向百度编程,得到两种方案:
第一种方案,创建新的的网关和路由,然后分配给docker:
service docker stop ip addr add 192.168.1.1/24 dev bridge0 ip link set dev bridge0 up vim /etc/docker/daemon.json
加上"bridge": "bridge0"节点并保存退出,再重启docker:
service docker start
第二种方案,直接修改docker0的默认网段:
service docker stop
vim /etc/docker/daemon.json
加上"bip": "192.168.1.1/24"节点并保存退出,再重启docker即可。
我这里采用第二种方式,修改后的路由表为:
重新访问各种服务,全部都正常运行,到此总算是拨开云雾见青天。
有个小细节不知大家是否发现,也是我当时存在的一个疑惑,就是前面有提过两个容器的网段不一样,按理说通过docker run来的容器应该都是相同的网段,为什么会这样呢?后来在折腾Portainer的时候找到了这个问题。
Portainer是一款docker管理工具,简而言之的说就是把用命令操作的东西可视化,当然功能远不止这些。Portainer中有一个Stack功能,我并不清楚这是干什么用的,只是看到旧的Portainer中的容器绑定了一个stack所以想依葫芦画瓢也搞一个:
于是拿stack的配置文件新创建一个,没想到居然报错,提示已存在相同名称的容器。我马上意识到这个特殊的容器应该是通过stack创建,我删掉已存在的容器再次创建stack,这次成功了。出于好奇,仔细分析了stack的配置文件:
发现里面主要是定义了镜像名、容器名、网络模式、端口映射这些,而其中vhnet这个网络配置让我很感兴趣,转而查看docker已经配置好的网关:
看到这里,一种恍然大悟的感觉,你懂的。
除此之外,从前任留下的文档里可以知道,stack有一种类似热更新的功能,修改配置文件中的镜像名后update stack就能实现对应的容器更新,不用起新的容器,这点确实很不错。更多强大的功能日后也会慢慢学习。
我的收获
经过前面几天的折腾,我更加熟悉了docker的各种基本操作和配置,也学会了使用新的命令,像docker inspect查看容器信息、docker attach进入容器内部,也加深了在Linux上排查问题的思路理解,学到了新的操作命令。也实际使用docker在Linux上部署了一次dotnetcore的生产环境,收获颇丰。
遗留的问题
1、 yum update后到底经历了什么让docker跪地不起,报错原因至今没搞明白。
2、 为什么2.1的dotnetcore程序在2.1运行时跑不起来,换成2.2版本就可以。
3、stack是怎么实现修改镜像后容器就能生效的呢?
有知道的大佬还请多多指导。
总结
表面上全篇都在讲才踩坑的事,但追根究底还是因为自己在Linux方面的知识欠缺和经验不足。还是那句话,多踩坑,会让你记忆深刻,会让你学到意想不到的东西,会让你的身体变得足够大,下次碰到坑能一脚踏过去。
故事讲完了,大家周末愉快~