Dash is not bash

在一些 docker 官方 Image 中,执行一些 .sh 文件的时候遇到了一些奇怪现象,比如:

1
2
3
# Run something like:
[[ $ABC =~ regexp ]]
#> [[: not found

其实是因为此 Image 中 sh 并非 bash,而是 dash:

1
2
ls -l /bin/sh
#> /bin/sh -> dash

此时,只需重置 sh 就好了:

1
2
rm /bin/sh
ln -s /bin/bash /bin/sh

不过,在 docker image 里,还是不建议执行 .sh 文件。所有事情都在 host 上完成,之后 -v 共享目录给 docker container 为好。

参考

Use SSH agent forwarding in your private network

大量云服务开始提供可供定制的私有网络功能来组织你所购买的 VPS,如果此时还通过直接链接每台机器的方式来管理,就太浪费资源和极为的不安全了。

以 qingcloud 产品为例,可以通过 GRE 隧道或者 VPN 等手段来实现安全登录和管理。但在一些条件不具备的情况下,采用内控堡垒主机(运维堡垒主机,Bastion Host)方案不失为一个好的方法。

将私有网络(内网)中的一台机器作为堡垒主机,将其 SSH 端口映射向公网某端口,打开该端口的防火墙 TCP 设置。此时,公网只能访问该堡垒主机,登录后,再通过该机登录其他服务器,从而即实现对了公网对其他服务器的隔离,有能够方便管理所有服务器。

方案容易,部署也不麻烦,但这里引发一个问题—— SSH Key 的问题。如果在堡垒主机上使用任何 Key 一旦堡垒主机被攻破,那么其他服务器就是“白给”状态,所以这里可以采用 SSH agent 来“中转” Key。

其实只要比常规方法多 -A 参数就可以实现 Key 中转了:

1
ssh -A [email protected]

使用 SSH config 的话,增加 ForwardAgent 属性:

1
2
3
Host any_name_want
HostName your.server.ip
ForwardAgent yes

此时,访问内网的其他服务器时,形如:

local —- ssh -A —-> Bastion Host —- ssh —-> Server 1 / 2 / 3 /…

当然,在所有服务器上使用的都是你本地 Key 的公钥

但还没有结束,由于 Bastion Host 并不运行正在的服务,在其他服务器上需要部署服务,尤其是通过 git 这类方式部署的时候,又有 Key 的问题了,所以改进一下:

local —- ssh -A —-> Bastion Host —- ssh -A —-> Server 1 / 2 / 3 /…

是的,就是 SSH agent forwarding 两次。

OK,done!

主要实现摘自:
SSH agent forwarding 的應用

No more sudo for using docker

操作 docker 的时候每次都需要 sudo 来提升权限,比较麻烦,可以有一个简单的方法来解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 如果 docker 用户组不存在,那么先添加docker用户组。
# 事实上例如在 CentOS 上使用 `yum` 安装的 docker,已经存在 docker 组
sudo groupadd docker
# 把当前使用的账号添加到 docker 组
sudo gpasswd -a username docker
# > Adding user username to group docker
# 然后 restart docker
sudo service docker restart
# 完成,但此时还是旧的环境状态,先注销
exit
# 再次登录
ssh you.server.ip
# 试试
docker images
# > REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
# Done!

cgconfig service error when the docker starts

当启动 Docker 服务的时候,遇到如下错误:

1
2
3
4
Starting cgconfig service: Error: cannot mount cpuset to /cgroup/cpuset: Device or resource busy
/sbin/cgconfigparser; error loading /etc/cgconfig.conf: Cgroup mounting failed
Failed to parse /etc/cgconfig.conf [FAILED]
Starting docker: [ OK ]

可以使用 cgclear 命令,清理一下。

之后记得先停止 Docker ,再启动。

1
2
3
cgclear
service docker stop
service docker start

PS. OS = CentOS

Make forever for node.js as a Linux service

由于个人比较喜欢在 Linux 上使用 service ,所以才有了以下的折腾。

forever 与 supervisor 的选择

其实,在本地开发还是比较喜欢用 supervisor 的,因为命令简单。考虑到服务器上可能会运行多个 node server 实例,因此选择 forever 来做 node 服务的管理。

了解 forever ,戳 https://github.com/nodejitsu/forever
了解 supervisor,戳https://github.com/isaacs/node-supervisor

Step 1

安装 forever

1
sudo npm install -g forever

(这货依赖不是一般多……)

安装成功后可以看到安装路径,记下待用。以下是我的服务器环境的返回:

1
/usr/local/lib/node_modules

注意:在 CentOS 上安装 nodejs / npm 的方式不同,会得到不同的安装路径:make install 一般在 /usr/local/lib/node_modules;yum install 一般在 /usr/lib/node_modules 。

Step 2

创建启动文件

1
sudo vim /etc/init.d/the_name_you_want

启动文件内容

1
2
3
#!/bin/bash
# chkconfig: 345 99 01
# description: forever service

345 是启动级别,99 是启动优先级(序号),01 是关闭优先级(序号)。如果有其他服务依赖于 forever 服务,可以提高优先级,以确保其他服务启动时其可用。

继续:

1
2
3
DEAMON="/your/server/config/file/path"
LOG="/your/logs/xxx.log"
PID="/root/.forever/pids/xxx.pid"
  • DEAMON 处给出配置文件地址,其中文件内容每行为一项任务,每项形如:
    name app.js path/of/app/js

  • 两处“xxx”是占位符,稍后替换用;

注意右边是值在引号内!!不然 forever 可能会喊它要 string,尤其是 LOG 地址。在 yum 安装环境中遇到过,make 安装环境未重现。

继续

1
2
3
export PATH=$PATH:/usr/local/bin
export NODE_PATH=$NODE_PATH:/usr/local/lib/node_modules
export NODE_ENV=production

注入环境变量,不然找不到 node 或者 -g 安装的包。上面已经提过,地址根据安装方法不同不一致,区别在于有没有 local 这一级,自己根据实际情况定夺。

继续,start 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case "$1" in
start)
while read Name Main Path
do
if [ $2 ]
then
if [ "$2" != "$Name" ]
then
continue
fi
fi
if [ -f ${PID/xxx/$Name} ]
then
echo -e "\033[0;33mWarn:\033[0m \033[0;32m$Name\033[0m is already running well."
continue
fi
forever start -wal ${LOG/xxx/$Name} -m 0 --watchDirectory $Path --pidFile ${PID/xxx/$Name} $Path/$Main
done < $DEAMON
;;

详细说明一下:

  • “Name Main Path” 对应配置文件中的内容格式,
  • start 后不跟参数,则启动所有配置项;跟上配置项 Name 做参数则仅启动该项
  • 已经启动的返回友好提示信息

关于 forever 的说明:

  • -w 监视文件的变更,一旦有变更,forever 会重启关联 server
  • -a 声明追加内容到已有日志文件 (没有 -a 重启时会直接报错并中断…………谁会每次重新启动来关心 log 日志?!为什么 -a 不是默认值!!!)
  • -l 指定日志文件地址
  • -m 最大运行次数
  • —watchDirectory 监视文件的目录 path
  • —pidFile 字面意思

list 命令用来查看正在运行的进程,其他命令顾名思义了。

继续,stop 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
stop)
if [[ `forever list` =~ 'No' ]]
then
forever list
else
echo '======================================'
forever list
echo '======================================'
echo -e "Enter the number of uid[\033[0;32mX\033[0m] to stop."
printf 'uid: '
read answer
echo -e "\033[0;32mStopping\033[0m $answer..."
echo '======================================'
forever stop $answer || echo 'Wrong entry!\rExit!\rRetry.'
echo '======================================'
echo -e "\033[0;32mDone!\033[0m"
fi
;;

说明

  • 不需要第二参数
  • 显示正在运行的任务列表,输入 uid 号以停止相应任务

继续,stopall、restartall 命令:

1
2
3
4
5
6
stopall)
forever stopall
;;
restartall)
forever restartall
;;

很简单,不错解释了。

继续,reload|restart 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
reload|restart)
if [ $2 ]
then
while read Name Main Path
do
if [ "$Name" = "$2" ]
then
forever restart -wal ${LOG/xxx/$Name} -m 0 --watchDirectory $Path --pidFile ${PID/xxx/$Name} $Path/$Main
fi
done < $DEAMON
else
echo -e "\033[0;31merror\033[0m: need second param like this: $1 name_of_the_app"
fi
;;

用法说明

  • 必须使用第二参数:任务项的 Name
  • reload、restart 互为别名

继续,获取运行任务列表:

1
2
3
list)
forever list
;;

没有命令时:

1
2
3
4
5
6
  *)
echo "Usage: /etc/init.d/noded {start|stop|restart|reload|stopall|restartall|list}"
exit 1
;;
esac
exit 0

到这里,启动文件创建完成了,可以 :wq 了。

Step 3

确认启动文件的一些属性

1
2
sudo chown root:root /etc/init.d/the_name_you_want
sudo chmod 755 /etc/init.d/the_name_you_want

启动服务

1
2
sudo chkconfig --add the_name_you_want
chkconfig the_name_you_want on

基本完成

可以通过如下命令来控制服务了

1
2
3
4
sudo service the_name_you_want start
sudo service the_name_you_want restart
sudo service the_name_you_want stop
sudo service the_name_you_want list

其他问题

添加忽略监控文件的配置文件

1
touch /your/watch/path/.foreverignore

如果没有 .foreverignore 会报一个 error,如下:

1
2
error: Could not read .foreverignore file.
error: ENOENT, open '/your/watch/path/.foreverignore'

但是你的 app.js 是会正常执行的,应该说一切正常。

而,如果没有 —watchDirectory 参数配置,你仍然会得到一个 error,如下:

1
2
3
4
5
6
7
error: Could not read .foreverignore file.
error: ENOENT, open '/.foreverignore'

/usr/local/lib/node_modules/forever/node_modules/forever-monitor/node_modules/watch/main.js:63
if (err) throw err;
^
Error: ENOTDIR, readdir '/proc/1/fd/5'

此时,你的 app.js 依然欢快运行,但是你执行 list 命令去看它的时候,它……

1
2
sudo /etc/init.d/noded list
info: No forever processes running

-w 和 —watchDirectory 要一起出现,就够了,-w 不能直接接收值也忍了,不设置后者直接出现这种奇异现象,真是……(此处省略10000字!!)


End