最近有一个云服务器和数据库的迁移任务,踩坑爬坑无数次,觉得必须要记录一下。大家瓜子花生准备好,听我慢慢讲故事#手动笑哭#。

 

故事背景


公司是做电商业务的,在天猫有几家旗舰店数据量也很大。阿里有一个称为聚石塔的平台,专门给这些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
<http://xxx.xxx.xxx.xxxx: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 nameis 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 occurredwhile establishing a connection to SQL Server.
The server was not found or was not accessible. Verify that the instance nameis
correct and that SQL Serveris 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方面的知识欠缺和经验不足。还是那句话,多踩坑,会让你记忆深刻,会让你学到意想不到的东西,会让你的身体变得足够大,下次碰到坑能一脚踏过去。

故事讲完了,大家周末愉快~



友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信