给自己的服务器搬个家 – Docker化迁移全纪录

非专业运维,不会复杂配置,踩了很多坑,不断实验调整,并非最佳实践,最终目的是实现项目(vue&node)的打包部署或迁移,涉及技术:

  • Docker 容器
  • Jenkins 项目管理及自动化
  • Nginx 代理
  • Mysql 数据库
  • Nodejs 服务端
  • yarn/npm 前端包管理
  • git 附赠,无需安装
  • pm2 静态/动态服务 + 进程守护

Docker是一个在称为“容器”的孤立环境中可运行应用程序的平台

容器分为三个:

  • 一个是放Mysql的,单独迁移数据库。
  • 一个则集成Node环境的容器,其中由Jenkins统一管理项目。
  • 独立一个Nginx容器,只用于代理。

原本计划是在服务器A做好容器后直接将容器迁移到服务器B,实际操作后发现只有nginx成功迁移了,其它两个容器启动失败。不过好在docker能帮我省去很多环境安装的步骤,mysqlnginx在服务器B手动安装也不算费力,所以实际迁移则只迁移了sql文件和nginx配置文件,而Jenkins我则简单粗暴地将整个jenkins_home打包。

首先安装Docker

系统centos7.6

curl -fsSL https://get.docker.com | bash -s docker --mirror aliyun

其它可参考这里

安装完运行

sudo systemctl start docker

常用操作

docker ps 查看正在运行的容器
docker ps -a 列出所有容器
docker stop <name> | docker start <name> | docker restart <name> 字面意思
docker rm <id> 删除容器
docker rmi <id> 删除镜像

常见参数

-d 进程守护
-uroot 使用 root 身份进入容器
-p xx:xx 主机 xx 端口映射容器的 xx 端口
-v 目录映射

安装Jenkins

update: 这一步主要将Jenkins_home打包,后续迁移往服务器的Jenkins映射目录解压

docker pull jenkinsci/blueocean

实际运行时我这里加了 --net=host,因为我的服务都将在这个容器中运行

docker run -d --name back-place -u root -p 9090:8080  -v /var/jenkins_home:/var/jenkins_home jenkinsci/blueocean

这样一个Jenkins就安装好了,少了一堆工作,Jenkins的一些配置可以参考我这篇或者上谷歌百度一下,这里不多展开,解释下上面的命令,--name后面带的是容器名,创建后都可随意修改,-p这行代表Jenkins将运行在9090端口,而且占用了Docker中的8080端口。

查看Docker目前的进程,可以看到 back-place 这个容器已经跑起来:

docker ps

此时的back-place就可以看做一个虚拟机,我们可以进入到这个容器的bash环境中:

docker exec -it back-place /bin/bash

经过一阵折腾,初始化Jenkins、Github生成新令牌、SSH、配置Git(似乎docker实例Jenkins的时候自带了git,查找路径命令为 which git )、一个个项目的构建配置复制等。

(注:如果嫌github太慢,可以使用generic-webhook-trigge插件hook码云,不过通常来讲github只要不用https连接就行,这算是一个坑,https时灵时不灵的,要用ssh项目连接)

离开bash环境:

exit

安装Mysql,迁移数据库

注:这一步实际为备份旧服务器上的mysql文件。

登录mysql,输入密码

mysql -u root -p 

输入 status 可以看到版本号:

给自己的服务器搬个家 - Docker化迁移全纪录

或者在linuxmysql -V也可以直接看到版本,注意大写V。
我是将版本号复制出来直接安装,不过还是建议官网查找相关版本的镜像来安装稳一点:

docker pull mysql:5.7.34

给自己的服务器搬个家 - Docker化迁移全纪录

登录旧的mysql,导完自己的sql文件,linux的mysql关掉,释放3306端口

service mysqld stop 看不同的版本操作不同

-- 分割线: 以下在目标服务器再操作,mysql容器导入运行没成功 --

运行 docker images 看下刚才安装是否成功

给自己的服务器搬个家 - Docker化迁移全纪录

修改密码并启动运行:

docker run -itd --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7.34

123456为root密码,5.7.34改成自己的版本

在bash中或者外部工具连接导入数据库文件,搞定。

安装Node (Alpine)

docker exec -it back-place /bin/bash

进容器,一顿操作(这里看看就好,踩坑了,前面这些步骤是错的):

wget https://nodejs.org/dist/v14.17.1/node-v14.17.1.tar.gz
......

出师未捷身先死,nodejs.org在容器内部能ping通,但却怎么也下载不了,查了一些资料似乎说是容器网络模式设置的问题,默认是桥接模式,要让容器直接使用宿主的网络命名空间就得重新安装容器了,算了,采取折中方法,先退出bash,回到宿主linux中:

docker inspect -f '{{.Id}}' back-place

给自己的服务器搬个家 - Docker化迁移全纪录
然后通过局域网拷贝文件:docker cp 本地文件路径 ID全称:容器路径

docker cp node-v14.17.1.tar.gz afe6f75d24f72b48826a5396c62e8efeb35c2cba30b5460bf20378817a8881f1:/usr/local/src/node.tar.gz

重新回到docker容器back-place中一顿操作:

docker exec -it back-place /bin/bash
cd /usr/local/src
tar zxvf node.tar.gz
cd node-v14.17.1
./configure --prefix=/usr/local/node/14.17.1

很好,又崩了,python找不到,原来我之前一直安装node的方法是基于CentOS的,已经集成了python环境,突然发觉事情不对劲了,查了一下目前docker容器的linux版本:

给自己的服务器搬个家 - Docker化迁移全纪录
好家伙,又赶紧查了一下各个系统版本对应的包管理工具:

  • centos:yum
  • ubuntu:apt-get
  • alpine:apk

我是一条无奈的分割线

以下需要按照顺序操作,否则npm与node可能对不上先设置apk为国内镜像源:

推荐使用中科大镜像
sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
apk update && apk upgrade
-----------
以下为清华镜像,node版本有问题
echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.8/main/" > /etc/apk/repositories
apk update && apk upgrade

安装node执行:

apk add nodejs

需要手动安装npm

apk add --update npm
npm config set registry https://registry.npm.taobao.org

node 安装完毕


检查node版本,如果是8(中科大镜像无问题),手动升级一下node最新稳定版

npm install -g n
n stable

上一步如果遇到报错,尝试运行以下命令(坑真的多)

npm config set unsafe-perm true

根据提示运行以下命令,不然全局 node 命令还是旧版

PATH="$PATH"

给自己的服务器搬个家 - Docker化迁移全纪录

安装PM2

用来在Jenkins容器中守护各个服务的进程。

npm install pm2 -g

常用命令

pm2 list
pm2 start ./xxx
pm2 restart all
pm2 flush          # 清空所有日志文件
pm2 delete 0       # 删除指定应用 id 0

跑静态服务

pm2 serve <path> <port> --name  <name>

如果你的Vue项目是history路由,需要这么跑

pm2 serve --spa <path> <port> --name  <name>

跑动态服务

pm2 start <path>

eg:
pm2 start /home/service.js

安装 Nginx (Alpine 失败)

就在我安装到一半的时候,linux突然崩溃,尝试了很久,依然是不行,最终决定把Nginx独立出来一个容器。

给自己的服务器搬个家 - Docker化迁移全纪录
就这样搞废了几个容器,虽然很不服气,但最后还是选择放弃 TvT

给自己的服务器搬个家 - Docker化迁移全纪录

安装 Nginx(Docker)

先停掉宿主的Nginx服务,Docker拉一份镜像

docker pull nginx

实例化镜像,绑定80端口(桥接模式)

docker run --name nginx -p 80:80 -d nginx

桥接模式需要指定端口,考虑到以后可能会配置更多的端口,所以实例还是直接使用net模式方便些

docker run --name nginx -d --net=host nginx

进入Nginx容器,查看配置目录

docker exec -it nginx /bin/bash
nginx -t

可以看到配置文件在 /etc/nginx 目录下,按需要进行修改,或利用docker cp将原Nginx的配置迁移过来。

docker inspect -f '{{.Id}}' nginx
docker cp <本地文件路径> <ID全称>:<容器路径>
docker cp /conf/nginx.conf xxxxxxxxxxxx:/etc/nginx

导入导出(放弃)

导出前需要查看复制出容器的COMMAND,这在导入时需要用到

docker ps -a --no-trunc

给自己的服务器搬个家 - Docker化迁移全纪录

导出镜像 docker export <id> > <path>

docker export 7691a814370e > /home/test.tar

这样就有了镜像文件:test.tar,将镜像文件放到静态服务中,从服务器B远程拉取服务器A的镜像安装(理想很美好,实际上最终只有nginx容器移花接木成功,故放弃)。

导入镜像 docker import <url> <name>

docker import http://xxx.com/nginx.tar test/nginx

给自己的服务器搬个家 - Docker化迁移全纪录

由于该镜像为容器实例,所以导入运行时需要最后面加上容器对应的COMMAND命令:

docker run --name=xxx -d xxx /docker-entrypoint.sh nginx -g 'daemon off;'

给自己的服务器搬个家 - Docker化迁移全纪录

大文件传输

第一次打包后的文件大概是这样的:

给自己的服务器搬个家 - Docker化迁移全纪录

然后放到静态服务上,根本下载不了:
给自己的服务器搬个家 - Docker化迁移全纪录

这里比较占空间的是Jenkins容器,后面因为容器迁移跑不起来,实际我只迁移了jenkins_home目录(可以看做是Jenkins的所有配置,我压缩完大概200M),记得把工作空间清空一下,这块是比较占用容量的,然后用gzip压缩,效果是非常的Amazing啊:

给自己的服务器搬个家 - Docker化迁移全纪录
压缩

gzip -c mysql.tar > mysql.tar.gz

解压

gunzip mysql.tar.gz

服务器之间的大文件传输我们可以使用scp来实现:

scp mysql.tar.gz root@服务器B的IP:/data/

gzip只能压缩文件,打包目录要用 tar :

tar -zcvf jk.tar.gz /var/jenkins_home

上面这个-zcvf的缩写就代表了打包完顺便使用gzip压缩,解压:

tar -xzvf

结尾

一开始想的是把所有线上的服务,统统放到docker独立容器中,通过克隆镜像来一次性迁移,目的是为了减少再次部署安装以及各种配置的时间,最后虽然不如理想中美好,但也算达成了目的:以最低成本搬家,并且在最低改动下保持我原有的生态——项目统一管理、自动化部署等等,在新服务器中使用docker安装环境并不复杂,只有少量配置需要重新弄(如SSH-key这种),这样下次服务器需要变更的时候也轻松许多了。