这篇主要对最近公司做的一个运维平台项目进行总结。主要是碰到的一些技术难点和项目中遇到的奇怪的问题。虽然项目中每个功能点都知道要怎么做,但是做的过程中也会遇到很多细节问题,需要一个个去解决,下面罗列出来。
使用 awk
提取某一列,使用 sed
命令提取某一行。
awk
能很方便的处理每行格式都相同的文本,比如 CSV,一些终端命令的输出。几乎所有的 linux 系统都支持,mac 也支持。
基本用法是:
# 打印 docker ps 命令的第一列
docker ps | awk -F " " '{print $1}'
-F
参数指定了分隔符,这里以空格为分隔符, $1
表示分隔出来的第一个元素。
awk
还有一些内置的函数和控制语句,不过目前用不到,先不做了解。
# 打印 docker ps 命令的第一行
docker ps | sed -n '1p'
-n
表示不输出所有内容,根据后面的 ‘’ 里的条件输出,引号里面的 1 和 p ,1
表示第一行,p
表示输出。
那么结合一下 awk 和 sed 命令,就能获取到某一行某一列的值,例如获取第2行第3列的值:
docker ps | awk -F " " '{print $3}' | sed -n '2p'
比如用 kill 命令删除 3000 端口的进程:
kill -9 $(lsof -i:3000 | awk -F " " '{print $2}' | sed -n '2p')
在 docker 中,很多命令的输出很多都不是格式化的,很多列里面的值也带有空格,导致使用 $ 取某列值的时候就不准,但是还好 docker 的 –format 参数提供了基于 go 模版功能,可以定制输出的格式,如:
docker ps --format '##' | awk -F "##" '{print $2}'
使用 `` 获取当前对象,只取 name 和 status 的信息,输出的格式用 ## 隔开,然后用 awk 的 -F 参数就可以顺利的取到值。
执行 yarn add pkg 来安装 pkg 包。有一个需要注意的是使用 pkg 打包会把 node_modules 里面的文件也一起打包进去,如果项目中使用了 child_process 的话,而且在命令中使用了 node_modules 的一些模块的话,会报模块找不到的错误,这时除了打包后的 node 应用,还需要把 node_modules 也打包到镜像中。
项目前后端分离,后端项目在 CI 中打包需要把前端项目也打包进来,前端项目可以是单独的 docker 镜像,也可以直接让后端去拉取代码,这里使用的是后者。
以下是 drone CI 中的例子:
- name: frontend pull
image: xxx/git
commands:
- git submodule update --init --recursive --remote
depends_on:
- clone
- name: frontend build
image: xxx/node
commands:
- cd client
- yarn
- yarn build
depends_on:
- frontend pull
如果真的遇到服务器磁盘满的情况,已经是救不回来了,只能实现预防,比如在上传文件和解压大文件的时候,先去判断一下磁盘空间大小,如果空间不够就返回一个错。上传文件时前端可以获取文件大小,后端先校验。解压文件时判断解压文件大小 * 4 倍的容量,只能预估一下大概的大小,不是很准确。
写在 docker-compose.yml 中的密码不用转义,连接的时候要根据客户端来定要不要转义,比如我使用的是 typeorm,在配置文件 ormconfig.json 文件中的密码是要转义后的。如果直接命令行连接上去,输入的密码是不需要转义的。
这个项目中涉及到 dind(docker in docker),在 docker 容器中需要管理宿主机的容器。
我们用的是 node 做服务端。解决方式是自己做一个镜像,镜像内包含了 node,npm,pm2,docker,docker-compose 等必要的命令,然后把 /var/run/docker.sock 文件挂载到容器中,/var/run/docker.sock 文件是 docker 客户端和 docker 服务端通信的中间文件,客户端往这个文件中写入数据就能被服务端读取到。
dind 有一个坑,就是 docker 容器如果使用了 volume 挂载,比如我们在容器内使用 docker-compose up -d 启动容器,如果某些容器指定了 volume 字段,则 volume 左边的挂载路径要写宿主机的路径,不能是容器内的文件路径。参考[这篇文章](https://stackoverflow.com/questions/31381322/docker-in-docker-cannot-mount-volume)。
多个 docker 共享文件夹的写法
# docker-compose.yml
version: '3.4'
services:
app:
image: server:v1.0
ports:
- 3000:3000
volumes:
- client:/app/client
- .env:/app/.env
nginx:
image: nginx:latest
ports:
- 80:80
volumes:
- client:/usr/share/nginx/html
volumes:
client:
这个是磁盘空间不够的报错,解决方式有两种,一种是删除没有使用的镜像和容器,还有一种是加磁盘。 目前解决方案是删除不使用的镜像和容器,一行命令就可以了:docker system prune 如果解决方式是扩展硬盘容量的话,可能可以参考这篇文章
遇到的一个怪问题就是,我在容器内 ping 宿主机是通的,但是使用 wget 宿主机的内网ip发现不通,但是 wget 内网中的其他服务器却是可以的。
后来发现是防火墙的问题,容器网络如果是 bridge 模式的话,容器和宿主机之间的通信是通过 docker0 虚拟网桥实现的。容器内的报文通过 docker0 转发。当容器和宿主机通信时,docker0 网桥直接将报文转发到宿主机。报文的源地址是 docker0 网段的地址,而如果容器访问宿主机以外的机器,docker 的 SNAT 网桥会将报文的源地址转换微宿主机的地址,通过宿主机的网卡向外发送。
因此,当容器访问宿主机时,如果宿主机服务端口被防火墙拦截,那就无法联通宿主机,出现 Host is unreachable
的错误,而访问宿主机外的其他机器,由于报文地址是宿主机的地址,因此,不会被防火墙拦截。
解决方式有两种:
再重启防火墙
service firewalld restart
2. 或者使用命令
firewall-cmd –permanent –zone=trusted –change-interface=docker0
把 docker0 添加到信任的网桥里面。
注意:这里的 `docker0` 只是一个例子,到时候如果创建了自己的网段,则要把网段的那个名称写进去,比如 `br-u123giu21i`。
## 重启node容器的时候,内存和CPU飙涨,CPU涨到100%以上,内存使用到达1~2个G,这时pm2又会重启容器里面的node进程,经过多次重启后会稳定下来。
这是因为node中使用了 `staticCache` 模块,导致启动服务时需要把 `staticCache` 指定的目录下的所有文件都加载到内存里。
## 貌似是一个循环引用的问题,deploy.ts 引用了一个常量,只要使用了这个常量,emit出去的消息,监听者都收不到
确定是循环引用的问题,两个相互引用的模块A和B,如果先引入B,则在执行B中的require(A)时阻塞,进到A模块,再执行到A模块中的require(B)时,node运行时判断了是相互引用,则会返回一个空对象给A,此时A继续往下执行,但此时A中拿到的B是一个空对象。
## volume 文件夹太大导致启动容器时CPU和内容使用量暴涨
经查询,是不当使用 `staticCache` 模块导致的问题。
## docker-compose 很多命令的输出都是在stderr上面的,导致不好排查真正的错误
查了一圈,没有什么好的解决办法,docker-compose 就是把很多命令的输出放在 stderr 里的,要么就是使用原生的 docker 命令去操作。
## 缓存 index.html 页面
nginx 对 index.html 单独加上配置:
location =/ { add_header Cache-Control ‘no-cache’; … } ```