经验的成本


最近想对博客的源码做自动化的CI,原本想着使用github的workflow配置,提交博客源码后让github自动编译发布博客。不过由于还要部署一些其他的内容,所以索性就想着自己写个github的webhook,当提交博客源码时,服务器运行CI工具,顺便发布下自己部署的其他服务。

做这个事情本身并不难,其实只要在github上把博客源码的仓库,增加一个webhook,然后服务器后台部署自己的CI工具就行了。在做这个事情之前,突然想到,自己一直从事的是客户端的开发。服务器部署的经验比较少,做这个事情大概要花多久时间哪?然后就尝试从网上看一些教程和文章,从网上一些描述来讲,做这个事情可能只需要花费1到2个小时的时间。然后在做这个事情的过程中,我就记录了解决整个问题的过程和时间。对比下花费的时间和精力。下面我就逐个描述下当时解决问题的过程。

Docker环境

部署服务自然少不了使用Docker。然后自己就根据博客使用的环境开始写DockerFile了。因为博客用的jekyll框架,自然需要ruby的环境。凭借自己之前仅记的一些Docker知识,自然的写了下面这行代码FROM ruby:2.7.0,然后又想着还需要node环境,然后DockerFile代码就变成这样子了。


FROM ruby:2.7.0
FROM node:latest

如果了解Docker工作原理的自然就懂了,跟在FROM后面所有的指令,都是在FROM这层镜像环境上做的。所以当DockerFile执行到 RUN bundle install时候,就报错说没有找到bundle命令。最开始自己还没有意识到时这个错误,然后就开始查找Docker下ruby环境的相关错误,然后本地写了DockerFile增加如下代码:


FROM ruby:2.7.0
RUN mkdir -p /home/blogsource
WORKDIR /home/blogsource
COPY . /home/blogsource
RUN bundle install

发现都是正确的,然后对比源码才发现可能是FROM指令的问题,然后仔细查询了FROM指令用法,发现Docker17.05老的版本只支持一条FROM指令,新的Docker版本支持多条。但是Docker只会部署最后一条FROM指令的环境。下面就引用下网上的Docker学习的一些资料.

多个 FROM 指令并不是为了生成多根的层关系,最后生成的镜像,仍以最后一条 FROM 为准,之前的 FROM 会被抛弃,那么之前的FROM 又有什么意义呢?每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。

这是我犯的第一个错误,等我解决完这个问题,大概消耗了1个多小时的时间。(PS 由于自己服务器带宽比较小,每次运行Docker拉取镜像时间很久大概要10分钟,也是浪费时间的一个原因)。

CMD命令的使用

开心的解决完第一个问题,然后又开始了编译Docker镜像。其实我心里早有准备不可能有这么顺利的,果然控制台又开始报错了,看下我写的DockerFile.


FROM ruby:2.7.0
RUN mkdir -p /home/blogsource
WORKDIR /home/blogsource
COPY . /home/blogsource
RUN bundle install
CMD [ "cd", "_nodejs/" ]

RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -\
  && apt-get install -qq --no-install-recommends nodejs \
  && npm install \

这是我犯的第二个错误,想当然的以为CMD命令就可以把工作目录转到nodejs的文件夹中,然后安装node环境。环境安装完后运行npm install 就报错说找不到package文件。然后当时就想,是不是CMD命令的问题就用了下面的方法重新试了下。


RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -\
  && apt-get install -qq --no-install-recommends nodejs \
  && cd ./_nodejs/ \
  && npm install \

发现这样子安装就没有问题了,然后就查询了下CMD和RUN命令的用法,下面的差异就是关键点。

CMD 指令:类似于 RUN 指令,用于运行程序,但二者运行的时间点不同;CMD 在docker run 时运行,而非docker build;

看到这里就明白了,在安装环境的时候用RUN指令,而真正运行容器的时候采用CMD,这两点的差别就造成了这个问题。还好这个问题也就浪费了半小时的时间。接下来看下个问题吧。

Docker运行问题

终于编译Docker文件没有问题了,但是发现Docker容器只要运行就退出一直显示 exit(0)。当时着实没有了思路,感觉编译的过程都正确为啥运行不了那。就通过 docker exec进入到容器中,执行nmp start发现没有问题啊。于是开始查找下了docker退出的原因,关键字自然是exit(0),然后很快找到了下面的答案.

之前介绍容器的时候曾经说过,Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。

看到这里自己就有点明白了,查看了下自己写的DockerFile的脚本代码,CMD ["nohup","npm","start"]发现为了让node在后台运行,自己特意加了nohup,反而是弄巧成拙,造成Docker容器运行完就自动退出了。

其实解决这个问题花了自己蛮久的时间,一直把Docker当成了虚拟机的概念在想这个逻辑,Docker本质上还是一个进程,只是使用了linux用户定义分组的能力,如果知道这个前提就不容易犯错了。这看起来和苹果的沙盒原理有异曲同工之妙,也正是这个错误的认知,自己决定之后一定要看下Docker源码的逻辑,了解下Docker设计的思路。

Git配置问题

前面的三个问题,其实已经花了自己咖啡馆一下午的时间了。然后编译运行Docker,发现没有问题了。就去github,打开了webhook的设置,配置上去后,按照文档把nodejs的代码写好了,逻辑很简单如下:

var result = req.body['commits'];
var message = result[0];
var command = message['message'];
if (command == 'publish') {
    res.status(200).send('receive success start publish...');
    execute('cd .. && git pull origin master && ./publish.sh',function (error) {
        if (error == null) {
        console.log('publish successful');
        } else {
        console.log(error);
        }
    });
}
function execute(command,callback) {
  var process = require('child_process');
  process.exec(command, function(error, stdout, stderr) {
      console.log("stdout:"+stdout);
      console.log("stderr:"+stderr);
      console.log("error:"+error);
      callback(error);
  });
}

然后就提交了下github的源码,看了下Docker运行的日志,发现git没有权限的报错,然后自己想了下,Docker是按照group用户的概念隔离进程的,那自然不能重用服务器上的sshkey了,那在Docker镜像build的时候应该把sshkey文件复制进去,至此完整的DockerFile代码就如下:

FROM ruby:2.7.0
USER root

#在image中创建文件夹
RUN mkdir -p /home/blogsource
# 将工程下所有文件拷贝到文件夹中
COPY . /home/blogsource

#使用RUN命令执行npm install安装工程依赖库
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN bundle install \
  && curl -sL https://deb.nodesource.com/setup_14.x | bash -\
  && apt-get update -qq \
  && apt-get install -qq --no-install-recommends nodejs \
  && apt-get clean \
  && cd ./_nodejs/ \
  && npm install \
  && mkdir /root/.ssh \
  && cp -r ./gitconfig/* /root/.ssh/ \
  && git config --global user.email "[email protected]" \
  && git config --global user.name "mengtnt"
#暴露给主机的端口号
EXPOSE 9999
#执行npm start命令,启动Node工程
WORKDIR /home/blogsource/_nodejs
CMD [ "npm", "start" ]

自此终于把博客自动化ci的工具搭建成功了,自己记录了下时间大概花了8小时左右才完成。我相信如果是一个Docker运维很有经验的人,搞这些源码,可能只需要1到2个小时就能部署和调试完成,我这里很多时间都是浪费在解决问题上了。这本质上就是经验的成本。

总结

所以何为经验,就是在某个领域解决问题的效率。拿上面的例子,如果是一个经验不丰富的人,可能要花费3倍于经验丰富的人,才能解决这个问题,这个就是生产效率的问题。试问企业更愿意雇佣那种人,显而易见如果企业用1个月就可以赚到3个月的钱时,就愿意拿3倍工资招聘一个经验丰富的人,而不会用平均的工资水平招聘一个经验不足的人。

对于企业来讲,生产效率是他们立于不败之地的重要手段。很多工具的发明也正是为了提升生产效率。所以作为打工人的我们要考虑,为什么企业要用你,自己的价值何在?工作的时候,一些重复的劳动,就要想有没有什么办法可以做自动化。这不仅节省了企业的成本,重要的是也让自己学到了经验,那将来其他公司也很乐于雇佣你,因为你帮企业提高了生产力,企业就更乐意拿高于没经验人的工资来聘用你。所以在职场上,要不断的增加自己解决问题的经验,然后树立自己的核心竞争力,这样才能立于不败之地。

如果你喜欢这篇文章,谢谢你的赞赏

图3

如有疑问请联系我