4

起因

对于各种凌乱的电脑问题,其它行业的人以为程序员们什么都会;程序员中,女程序以为男程序员什么都会;男程序员中,一般程序员以为技术好的程序员什么都会;技术好的程序员,每次都在网上苦苦搜寻答案……

公司的系统管理员辞职了——当然那是很久以前的事情了,然后我就会做一点公司服务器运维方面的工作,虽然不专业,但是日常维护还是问题不大。不过有一天,代码服务器被我搞坏了,我只是简单地想装个 Git 服务(以前一直使用 Subversion),然后被要求升级,然后升级过程中出现失败,然后服务器就不能用了。然后百度、Bing,甚至翻墙 Google 也没找到解决办法——不过还好,我偶然发现从启动菜单中不带 “Server” 的那一项启动系统,除了 SSH 不能用之外,其它服务还能照常运行。又经过一翻尝试之后决定放弃,免得把事情搞得更糟——现在最多就是需要操作的时候搬台显示器去机房,麻烦一点。

又过了一段时间,公司停电,UPS 也耗尽了最后一点能量——当然这一切都发生在晚上,所以早晨到公司的时候,服务器已经歇菜了。重启的时候发现 Raid 卡报警,硬盘坏了。拆下来一测试,三块硬盘坏了两块,差点全洗白。这事很严重,所以马上买新硬盘顺便重做系统,毕竟没有 SSH 是件很不方便的事情。

目的

老实说我是真心想装 Windows Server,但是授权问题真的很难解决。如果说用 Linux,有两件事情让我很踌躇,一件事就是上面提到的为了装 Git 更新各种库把服务器系统搞坏;另一件事是在这之后,我专门找了一台旧机器准备玩玩 Linux,结果装好的第二天,对系统进行自动升级之后它就再也启动不起来了。所以我担心以后系统升级和扩展服务内容又会大费周章。

为了解决这个问题,立刻能想到的就是使用虚拟化技术,虚拟机当然是玩过的,除了本身慢得要死之外,把宿主也拉来慢得要死——在遍网都在说 Docker 的年代,我当然会去了解一下,顺便做做尝试。当然这个尝试并不是事到临头才做的,早些时间我已经在 Windows 下简单的尝试过了,搭了个试验环境用的 Gogs 服务,用的 Docker Toolbox,它有一个图形化管理工具叫 Kitematic,所以操作基本上是傻瓜式的。用 Kitematic 的时候压根不需要去了解 Docker 命令行,只是根据网上的说明,在图形化界面下做好配置就行,So Easy!所以严格的说我并不会用 Docker,只是知道用它搭建服务跑起来比虚拟机轻量多了。

基本上就这么决定了,安装 Linux,配置 Raid 卡,再装个 Docker 环境,上 Docker Hub 搜点现成的映像做成服务,似乎就已经解决了我们目前的问题……然后就开始了为期两周的服务搭建之旅

这两周都在干啥

两周!!!对于专业运维来说,这个时间都可以进行星际旅行了,但是考虑下我的背景,16 年前使用过 Linux,那时候只是在 Linux 下用 Emacs 和 javac 进行开发;没有运维知识栈,所知到关于运维的一切都来自 Internet 上的技术新闻,当然在软件开发和设计过程中会有一些涉及,但涉及不深。在目标确定之后接下来要做的一切事情,都是一边搜索一边试验的情况下完成的,所以两周,似乎可以理解了。

第一周

说起来第一周算是浪费掉了,因为我想继续使用 Raid 卡,但又不想延用原来那个古老版本的 Linux 系统。所以先尝试了 Ubuntu Server 16。各种搜索也没找到驱动,本来想向 Raid 供应商提个服务单,看到一大堆要填写的英文信息,还是放弃了。

然后换 CentOS。驱动倒是很快找到了,就是各种装不上……

最后在朋友的朋友的提示下,决定放弃 Raid 卡,在 Linux 里做软 Raid,不过这时候一周已经过去了

第二周

做软 Raid 配置的过程还算顺序,基本上都是按网上的贴子一步步来。就是配置完之后一直不能用,错误信息现在已经不记得了,也是各种找原因,最后在 /proc/mdstat 里发现了一个百分比,而且还是变化着的,英文看得似懂非懂,大概是在同步两步硬盘吧。所以耐心等着这个百分比走到头。有惊无险,可以装 Docker 了。

装 Docker 是很容易的,不过拉映像还是踩了些坑,大把的时间都花在找映像去了。

Docker 服务环境

当然我们要搭的服务还是有一些,不过最主要的就是 Subversion 服务、Mantis 和 Gogs,以及后面两个都会用到的 MySQL。

一开始我是直接写命令的,但是很快我就意识到这是个非常不靠谱的方式。Docker 命令参数那么多,直接写命令很容易写错,这都不算,大部分命令都是从网络上来的,总还得尝试,如果不对还需要修改……而且似乎也应该为后期维护留下点什么,所以开个了文件叫 INSTALL.md 来记录每一步操作。

MySQL 服务

首先是从 MySQL 开始,这个有官方映像,所以没什么好选的,直接拉官方映像。

从以前的了解,知道 Docker 容器相对封闭,需要通过卷(volume)来指定数据在宿主中的存储位置。这个从命令行很容易查到是 -v 参数,它会为容器内某个目录和宿主某个目录建议映射关系,这两个目录在参数中用一个冒号来分隔,但问题是谁在左谁在右?

网上的很多文章说得不清不楚,甚至还有相悖的说法——反正是新装的系统也不用怕搞错,连查带试验,终于搞明白了,冒号左边是宿主目录,右边是容器内的目录

-v <宿主中的路径>:<容器内的路径>

创建容器之后,发现用 HeidiSQL 连不上,又查了半天,发现原来是没有做端口映射。在用 -p 参数做端口映射的时候又把参数搞反了,所以又折腾了半天——好吧,其实根据 -v 参数中的顺序都可以猜到这个顺序

-p <映射到宿主的端口>:<容器内的端口>

但是仍然连接不上,用 docker ps 命令一查看,已经跑了好几个 mysql 容器了,以为是这个原因(其实不是),所以又查命令,删除了一些容器,一开始比较笨,docker rm 命令提示运行中的容器不可删除,所以是先运行 docker stop,再运行的 docker rm,后来偶尔发现了 -f 参数,才变得简单一点

docker rm -f <容器ID/容器名>

所有容器都删除之后,新建了个 MySQL 容器,外面仍然连不上……不过看起来好像并不是连不上服务器,而是验证失败。想了一下,最开始创建 MySQL 容器的时候是用的官方示例的密码,也就是 -e MYSQL_ROOT_PASSWORD=my-secret-pw,后来再创建的时候改成了自己的密码,会不会是这个原因呢?果然用 my-secret-pw 登录成功了!又查了半天资料,搞明白,原来第一次创建容器的时候,-v 参数指定的宿主目录下已经初始化创建了 MySQL 的数据。以后再次创建 MySQL 容器的时候,它检查到已经存在数据就不会再初始化,当然密码也就是以前指定的那个密码了。

MySQL 服务似乎没有问题了,不过我很担心重启服务器之后容器不会自动重启,所以决定实验一下。

悲剧,Linux 服务器居然不能重启,报告等待超时,无奈只好手工关闭电源再重新开机。到目前为止这个问题仍然没解决,但是至少我没事不会老重启服务器的,所以先忽略它吧。

我担心的事情出现了,MySQL 容器果然没有重启。于是继续搜,发现了 --restart 参数。所以又重新创建了一次容器,加上了 --restart=always 解决问题。

好吧,现在还有最后一个问题——为什么创建容器之后终端会阻塞在那里,我需要另开一个终端来进行其它操作?继续求助于强大的搜索引擎,最后搞明白,需要在 docker run 的时候加个 -d 参数。

Mantis 服务

Mantis 服务需要 MySQL,还需要 PHP5 以上版本。在 Docker Hub 上并没找到现成的,所以退而求其次,找了个带 PHP 的 Apache:nimmis/apache-php7

有了之前的经验,这个配置起来很简单,Mantis 很快就运行起来了,但是问题也来了。

首先就是怎么访问数据库?之前创建的 mysqld 容器和这次创建的 httpd 容器似乎有着不同的 IP,然而 IP 是什么?如果不知道容器的 IP,直接访问宿主的 IP 似乎也可行,但我并不能保证宿主的 IP 会一直不变,难道每次变化都要去修改配置——痛苦!!

借助搜索引擎的力量,我找到了 --link 参数。当然还有 Docker Compose 技术,不过这个 Compose 看起来有点深奥,以后再研究吧。

--link 似乎是以类似域名解析的方式将指定的容器绑定到指定的名称(域名),所以当前容器内部就可以通过这个名称来访问另一个容器了,而且另一个容器的端口是不需要映射到宿主的。当然,--link 参数的冒号左右分别是什么这个问题也需要搞清楚,根据之前的经验,左边接近宿主,右边接近容器内部,所以

--link <另一个容器名称>:<当前容器内部可用的域名>

现在还有第二个问题,Mantis 提示需要安装 PHP 的插件 mbstring。怎么办?继续搜!最后搜出来有两个办法:

  1. 基于原有映像创建自己的映像,安装 mbstring,然后使用这个新映像创建容器
  2. 直接进入容器安装 mbstring

创建映像又有两种方法,一种是进入容器安装之后再 submit,另一种是通过 Dockerfile。好吗,我现在还不想深入到映像这个层次,所以使用第二种办法,使用 docker exec 进入容器——它居然是通过运行容器的 bash 命令来进入的,别忘了 -i -t 参数

docker exec -it httpd bash

这里 httpd 是创建的 Apache with PHP7 容器的名称,-it 是合并了 -i-tbash 就是执行的命令。

安装 mbstring 的过程很顺利,而且只要容器不重建,就不需要重新安装……如果容器需要重建,那空了研究下 Dockerfile 吧。

最后是恢复 Mantis 的数据库,一开始是直接把 MySQL 数据库文件拷贝过来的,很遗憾,提示找不到表。所以不能偷懒,还是老实的 mysqldump 出来再通过 mysql 命令导入。

Gogs 和 Subversion

安装 Gogs 几乎没遇到什么困难,因为前面已经遇到并解决了所有它可能遇到的问题。

到是 Subversion 装起来比较费事。一开始找了两个 Subversion 的 Docker,Subversion 都是 1.8 版本,而宿主上偏偏又装不了 1.8 版本的 Subversion,只能装 1.9 的(我搞不懂为什么,也懒得去搞懂了)。这么一来,我担心在宿主上通过 svnadmin 创建的库不能被容器内运行的 svn 服务兼容。如果在容器内去创建库又比较麻烦。

原本以为很简单的 Subversion 却在寻找映像这里卡住了。最好尝试了好久,总算找了个 Subversion Edge 的映像,Web 界面管理库,虽然功能并不是很强大,但是简单的管理是没有问题了。说起来,还是很想念 Windows 下的 VirtualSVN Server。

Nginx 反向代理

Docker 服务的端口只要不映射出来,就只能内部访问,这看起来还是增强了安全性的,然并卵,因为不映射出来外面就不能使用这些服务。早就听说过 Nginx 的大名,所以干脆用 Nginx 映像来做个反向代理容器,然后以 HTTPS 协议来代理后面的 HTTP 服务。

配置都在从网上抄的,所以就不贴出来了,不过对于 SVN,后来是遇到 svn copy 总是 502 错误的问题,又在网上去搜索了一番,发现需要在 location 配置里加这么一段

# 解决 svn copy 报 502 错误的问题
set $fixedDescination $http_destination;
if ($http_destination ~* ^https(.*)$) {
    set $fixedDescination http$1;
}
proxy_set_header    Destination $fixedDescination;

然后又遇到提交数据过大的问题,于是又加了一项

client_max_body_size    48m;

最后完整的配置如下,基本上可以代表所有 location 的反向代理配置了

# svn server - svn
location /svn/ {
    proxy_set_header    Host                $host;
    proxy_set_header    X-Forwarded-Host    $host; 
    proxy_set_header    X-Forwarded-Server  $host; 
    proxy_set_header    X-Real-IP           $proxy_protocol_addr;
    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_buffering     on; 

    # 解决 svn copy 报 502 错误的问题
    set $fixedDescination $http_destination;
    if ($http_destination ~* ^https(.*)$) {
        set $fixedDescination http$1;
    }
    proxy_set_header    Destination $fixedDescination;

    proxy_pass              http://svn-server:18080/svn/;
    client_max_body_size    48m;
}

还是假装总结一下吧

  1. 文档和记录很重要,就是前面提到的 INSTALL.md
  2. 搜索引擎很重要,如果中文搜不到就搜英文
  3. Docker 是个好东西
  4. Nginx 也是个好东西
  5. 有没有想在成都或者绵阳找工作的运维?

边城
59.8k 声望29.6k 粉丝

一路从后端走来,终于走在了前端!