User Mode Linux – 一个用户态的 Linux 内核

User Mode Linux 可以在用户态启动一个 Linux。这使我们能在类似 OpenVZ 虚拟化技术的系统上,使用最新的 Linux 内核;甚至可以在非 root 用户下启动。

但有些 OpenVZ VPS 的 TOS 可能不允许你这么做;另外,这样可能会有一定的性能损失。

准备 rootfs 镜像

我们找一台 Ubuntu Server 来准备 rootfs。这个时候需要有 root 权限,因为你需要 mount 一个 loop 文件系统,chroot 改点东西之类的。

# 下载镜像
wget http://uk.images.linuxcontainers.org/images/alpine/3.1/amd64/default/20170305_17:50/rootfs.tar.xz
# 安装解压软件
apt install xz-utils
# 创建镜像,32 M 对于 alpine 是够用的
dd if=/dev/zero of=rootfs.img bs=1M count=32
mkfs.ext4 rootfs.img
# 把镜像挂到 loop 上
mkdir rootfs
sudo mount -o loop rootfs.img rootfs
sudo tar xvf rootfs.tar.xz -C rootfs
# 给 root 设个密码
sudo chroot rootfs /bin/sh
  passwd
  exit
# 清理
sudo umount rootfs
rmdir rootfs

此时 rootfs.img 已经做好了。

编译内核

我们继续用 ubuntu server 来编译 UML 内核。

sudo apt install build-essential kernel-package fakeroot libncurses5-dev libssl-dev ccache
# 抓内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.10.1.tar.xz
tar xvf linux-4.10.1.tar.xz
cd linux-4.10.1
make defconfig ARCH=um
make menuconfig ARCH=um
# 这里会出来一个界面让你配置内核,有茫茫多的参数可以选
# 大概找一些 linux 内核编译的文章可以参考吧
# 主要是 User Mode Linux 有一些特殊选项可以注意下,比如强制静态编译什么的
# 然后就开始正式编译啦
make ARCH=um
strip linux # 移除调试符号

编译过程可能比较慢。有时候会有一些库找不到啥的,apt 装一下就好。

获取 slirp

slirp 是用来在没有 root 的情况下联网的。

apt source slirp

这里获取到的 slirp 放到目标机器上编译,不需要在 ubuntu server 上搞了。

cd slirp-1.0.17/src
./configure
make CFLAGS="-I. -DUSE_PPP -DUSE_MS_DNS -DFULL_BOLT -fno-strict-aliasing -Wno-unused -std=gnu89" clean all

启动 UML

现在万事俱备,只欠东风了。

# 在 UML Host 上
# 把一些二进制文件放好
slirp=/home/oott123/uml/slirp/slirp-1.0.17/bin/slirp
uml=/home/oott123/uml/kernel/linux-4.10.1/linux
rootfs=/home/oott123/uml/alpine/rootfs.img
cow=/home/oott123/uml/machine/demo/data.cow # 放 copy-on-write 数据的地方
export TMPDIR=/tmp
$uml rw ubda=$cow,$rootfs mem=128M eth0=slirp,,$slirp

配置网络

启动之后你可能会发现没有网络。不用担心,改改网络配置:

# 在 UML 里
vi /etc/network/interfaces
#####
auto eth0
iface eth0 inet static
address 10.0.2.15
gateway 10.0.2.2
netmask 255.255.255.0
dns-nameservers 10.0.2.3
hostname $(hostname)
#####
/etc/init.d/networking restart
echo 'nameserver 114.114.114.114' > /etc/resolv.conf
# 测试网络
wget -O- myip.ipip.net
# 安装软件
apk update
apk add curl
curl myip.ipip.net
# 关闭 UML
halt

与主机交互

或许你需要将 UML 内的端口转发到主机上。

# 在主机上
vi ~/.slirprc
redir 2222 22

或许你需要访问主机的文件系统。

# 在 UML 上,把 UML 内的 /mnt/home 映射到 /home
# 注意权限问题、注意两边不要同时读写
mkdir /mnt/home
mount none /mnt/home -t hostfs -o /home

记 VestaCP 的 Let’s Encrypt 自助签发配置

主要原料是 https://github.com/interbrite/letsencrypt-vesta 这个脚本。

然后写一个 python 脚本用来给用户自助签发

#!/bin/env python
import sys
import re
import os
import subprocess

args = ["/usr/local/bin/letsencrypt-vesta", "-u", os.environ['SUDO_USER']]
for i in sys.argv[1:]:
    if re.match('^[a-z0-9][a-z0-9.-]+$', i):
        args.append(i)
subprocess.call(args)

给用户 sudo 权限

%users ALL=NOPASSWD:/usr/local/bin/sign.py

然后让用户执行 sudo /usr/local/bin/sign.py 域名 即可。

我是如何搭建一个低成本的 Minecraft 小型服务器的

注目:本人不接受任何的免费的技术咨询指导,提问请先发红包!

自己有个小型的 Minecraft 服务器,为了节约成本以及管理方便,做了一整套自动化的管理方案。整套方案代码量近万行,同时很多东西都写死在了代码里,想开源但是很多东西目前还不完善,根本没法拿出手……所以写了个思路整理,若有需要的朋友可以参考着搭一个。

一、 服务端的选用

没啥好说的,由于需要 mod ,用的 KCauldron-1.7.10, mod 都是自己打的, Forge + LiteModLoader ,大概十几个 mod 。

插件用了一个非正版验证插件,配合宏与按键绑定(Marcos and key bind)进行自动登录。

二、 ISP 的选择

ISP 使用青云。由于服务器在线人数常年在 5 人以下,故选用青云可以随时开关实例,节约运行成本。

青云关机的话,公网 IP 和硬盘也收费,故使用 API 将 ip 释放,仅保留硬盘扣费。

三、 如何开启服务器

由于公网 IP 变动和服务器常年关闭,在另外一台服务器上搭建了一个 HTTP 服务,并自己写了个启动器。

当启动器启动的时候调用那个服务去开启服务器、下发公网 IP 并写入 servers.dat,避免动态域名解析被缓存的问题。

开启服务器后,利用 Telegram bot API ,在群里进行通知服务器的新 IP。

利用 mc 协议检查十分钟没人登录后,服务器自行关闭。

四、 如何管理 mc 服务

使用 supervisord 进行自启动。

需要控制台时,利用 supervisorctl fg minecraft 来调用控制台。

在 Windows 下使用 boot2docker 搭建 docker 开发环境

很久以前研究过一会儿 docker ,当时觉得只是一个轻量级的虚拟机包装而已,没觉得有多大用。

最近发现这玩意还蛮好玩的,但是又懒得翻以前的虚拟机了,于是发现一个叫 boot2docker国内下载(via DaoCloud) 的东西。官方宣称是“It runs completely from RAM, weighs ~27MB and boots in ~5s (YMMV).”。

下载回来的安装包有 100 多 MB,当时在想说好的 27MB 呢,结果发现包里有个 VirtualBox 和一个 msysgit ……简直坑。

如果你的机器上没有这俩玩意,那就放心大胆的直接安装吧。它会帮你把各种琐事配置好,官方的指导文档里也写的十分详细。

如果你的系统里有 VirtualBox 了,那也就直接安装,它会自动识别系统上的 vbox 的,不过你最好先测试一下你的 Host-only 网卡是否正常。

那么重点来了:如果你的系统里有 msysgit,并且还有其它的 GNU 工具链,比如我这种装了 Gow 的人,那么其实这个玩意是有坑的。

要说这个坑,首先要谈一谈 boot2docker 的工作原理。 boot2docker 由 VirualBox 里的一个叫 boot2docker 的虚拟机、一个用来管理 Virual Box 里的虚拟机的工具 boot2docker.exe 和 docker 本体 docker.exe 组成。

当你执行 sh start.sh 的时候,它首先调用 boot2docker init 来创建这个虚拟机(如果已经存在则跳过);其次将该虚拟机启动;最后使用 boot2docker ip 来获取虚拟机的 ip 地址并设置环境变量。

boot2docker ip 这个命令,是调用系统中的 ssh 来获取 ip 地址的。它在 init 的时候,将虚拟机的 22 端口转发到宿主机的 2022 端口,并运行 ssh -p 2022 docker@localhost ip addr show dev eth1 来获取 ip 地址(详细的命令可运行 boot2doker -v ip 来显示)。而 Gow 里的 ssh 指令,则是调用了 PuTTY 的 plink.exe

那么问题来了:plink.exe 根本不支持 -p 语法,也不支持它所使用的 -o 等参数。

所以,如果你装了 Gow,请一定把 msys git 的路径加到 PATH 的最前方……起码是在 Gow 的前面,这样就不会被坑了。

处理完这些乱七八糟的事情之后,运行 boot2docker status 即可看到你的虚拟机的当前状态。下次使用时,只需执行 sh start.sh 即可自动配置好环境变量并且开启 git bash。在这个 git bash 里,你就可以执行你的 docker 指令了~

如果不想用 git bash,那么执行 boot2docker shellinit ,也可以看到应设置的环境变量,如:

export DOCKER_HOST=tcp://192.168.111.222:2376
export DOCKER_CERT_PATH='C:\Users\oott123\.boot2docker\certs\boot2docker-vm'
export DOCKER_TLS_VERIFY=1

此时,用 set 将这些环境变量设置好,即可在 cmd 中使用 docker 来管理 docker 容器了,如:

set DOCKER_HOST=tcp://192.168.111.222:2376
set DOCKER_CERT_PATH=C:\Users\oott123\.boot2docker\certs\boot2docker-vm
set DOCKER_TLS_VERIFY=1

试试 docker info 能不能显示出虚拟机的信息来吧。

supervisord 管理多进程程序如 pyspider 时的合适配置

现在的工程中,有一部分是用 pyspider 来抓取数据的,使用 supervisord 来管理它的进程。在使用过程中,遇到一个奇怪的问题:用supervisorctl stop pyspider之后,pyspider 并未全部停止,而是留下了三四个进程在系统中。

查阅官方手册,在 [program:x] Section Settings 中找到了名为 stopasgroup 的选项,解释如下:

If true, the flag causes supervisor to send the stop signal to the whole process group and implies killasgroup is true. This is useful for programs, such as Flask in debug mode, that do not propagate stop signals to their children, leaving them orphaned.

在 [program:pyspider] 小节,开启这个选项。如:

[program:pyspider]
command=/home/oott123/pyspider/run.py
autorestart=true
startsecs=2
user=oott123
directory=/home/oott123/pyspider/
redirect_stderr=true
stopasgroup=true
environment=WEBUI_HOST="127.0.0.1",WEBUI_PORT="5033"

然后重启 supervisord ,再测试停止 pyspider 的服务,它就不会残留那几个进程了。

CentOS 7 下 docker 安装拾穗

如何安装 docker

docker 在 CentOS 7 下已经收录到了 extras 包中,因此我们只需要执行:yum install docker 即可安装 docker。

安装后,使用systemctl start docker来启动 docker 的服务,再用systemctl enable docker来使其开机启动。顺带一提,service docker startchkconfig docker on也能达到同样的效果;但由于 CentOS 7 采用了 systemd 来管理服务和开启启动项,因此我们尽量使用 systemctl 来操作服务。

安装后输入 docker info 来查看 docker 信息。

[root@localhost data]# docker info
Containers: 0
Images: 0
Storage Driver: devicemapper
 Pool Name: docker-253:0-68161190-pool
 Data file: /var/lib/docker/devicemapper/devicemapper/data
 Metadata file: /var/lib/docker/devicemapper/devicemapper/metadata
 Data Space Used: 293.0 Mb
 Data Space Total: 102400.0 Mb
 Metadata Space Used: 0.7 Mb
 Metadata Space Total: 2048.0 Mb
Execution Driver: native-0.2
Kernel Version: 3.10.0-123.el7.x86_64

如何修改 Data file 路径

遇到的第一个问题,则是:docker 的数据都放到哪里了?显然,上面的 docker info 给了我们答案: 在/var/lib/docker/ 下。由于种种神奇的原因,我们希望尝试 修改 docker 的数据存放目录

编辑这个文件:vim /etc/sysconfig/docker,找到OPTIONS=--selinux-enabled -H fd://,修改为:OPTIONS=-g /mnt/docker0/data --selinux-enabled -H fd://。这样我们就成功的将 docker 目录修改成功了。

执行 systemctl restart docker ,再 docker info ,可以看到目录已经修改成功了。

如何使用国内的 docker 镜像源

考虑 DockerPool 这个 docker poll。

例如,我要下载 CentOS 7 的 docker 镜像,那么执行:

docker pull dl.dockerpool.com:5000/centos:centos7

镜像文件略微有点大,下得我心醉。在 DockerPoll Downloads 可以找到其它的镜像列表。

下载完成后,要把第三方 registry 的下载的文件打上 tag

docker tag dl.dockerpool.com:5000/centos:centos7 centos:centos7

如何清理 docker 容器

执行了一些简单的命令,比如docker run -t -i centos:centos7 ping baidu.com之后,再看 docker info ,发现 Containers 居然到了 4 。大惊,用 docker ps -a 查看后才知道,每次我用 docker run 的时候他都会新建一个 Container。试了下 docker rm ,一次只能删除一个。改用 docker rm $(docker ps -aq) ,成功删除它们。

参考:Docker——从入门到实践

还算比较完善的 Linux 服务器自动备份、上传脚本

脚本托管于gist,点击查看

由于内嵌会导致一定程度的网页加载阻塞,故此处内嵌并非实时版本。

#!/bin/bash
Err() { echo -ne "\e[1;31m"; Log "$@" 1>&2; echo -ne "\e[0m";}
Log() { echo $(date +"[%Y-%m-%d %H:%M:%S]") $@; }
Log ====每日备份脚本开始运行====
UPLOADER_BIN="/root/auto-scripts/bpcs_uploader/bpcs" #bpcs 上传脚本路径(见下方bpcs文件)
REMOTE_DIR=$(date +"%Y%m")/$(date +"%Y%m%d") # 远端上传路径
WORKING_DIR="/root/auto-scripts/backup_daily" # 工作路径
CONFIG_DIR=$WORKING_DIR/list.d #配置文件路径
BACKUP_DIR=$WORKING_DIR/backups #本地备份路径
CURRENT_DIR=$BACKUP_DIR/$(date +"%Y%m%d")
OLD_DIR=$BACKUP_DIR/$(date -d -3day +"%Y%m%d")
zipPassword=ChangeThePasswordToYourOwn #备份密码
#准备删除老文件
Log 删除旧备份文件夹:$OLD_DIR
rm -rf $OLD_DIR
#创建新目录
mkdir -p $CURRENT_DIR
#遍历配置目录,进行备份
for i in $(ls $CONFIG_DIR)
do
zipExclude="*/cache/* */tmp/* */temp/* */.npm/* */data/threadcache/*"
. $CONFIG_DIR/$i
Log 备份$backupDir为$i……
backupFile=$i.zip
zip -r -P $zipPassword $CURRENT_DIR/$backupFile $backupDir -x $zipExclude
Log 上传备份……
$UPLOADER_BIN upload $CURRENT_DIR/$backupFile $REMOTE_DIR/$backupFile
done
Log 备份完成!

用法懒的写,自己研究!

依赖:bpcs_uploader,以及 zip 。yum install zip

CentOS 6 + Apache 环境下,PHP 5.2 与 5.4 共存

别问我为什么还要用 5.2 ,我会告诉你是因为 ZendOPT 么。

言归正传。在 Linux 下,要装两个不同版本的 php 可不是下载两个 exe 那么简单。这里采用的方法,是手工编译 php 5.2 并使用 epel 源中的 php 5.4 。前者采用 fastcgi 模式运行,后者采用传统的 mod_php 来运行。

现在我们假设,环境中已经装好了 Apache 2.2 + MySQL 5.5 + PHP 5.4 ,并且希望加装一个 PHP 5.2.17 ,并且能够直接使用 MySQL 5.5。

首先需要安装 epel 。

rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm

然后安装依赖:

yum install httpd-devel libxml-delvel openssl-devel bzip2-devel libcurl-devel libjpeg-devel libpng-devel freetype-devel libmcrypt-devel libmhash-devel libtool-ltdl-devel

然后需要自己编译安装 libiconv,如下。

wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz
tar -xvf libiconv-1.14.tar.gz
cd libiconv-1.14
./configure
make
make install

然后编译安装 PHP ,记得打开 cgi 选项。PHP 源码包请到官网下载。

./configure --prefix=/usr/local/php52 --with-config-file-path=/usr/local/php52/etc --with-config-file-scan-dir=/usr/local/php52/etc/php.d --with-openssl --with-zlib --with-bz2 --with-gettext --with-mhash --with-mcrypt --with-iconv=/usr/local/libiconv --with-curl --with-gd --with-jpeg-dir --with-png-dir --with-freetype-dir --with-mysql=/usr/bin/mysql --with-pdo-mysql=/usr/bin/mysql --enable-gd-native-ttf --enable-bcmath --enable-mbstring --enable-zip --enable-soap --enable-sockets --enable-ftp --without-pear --enable-fastcgi --enable-force-cgi-redirect
make
make install

这样 PHP 5.2.17 就被安装到了 /usr/local/php52 里了。

/usr/loca/php52/bin/php -v

然后配置 fastcgi 。 找到 ~/public_html/cgi-bin/ 目录,创建 php52.fcgi 文件如下:

#!/bin/sh
export PHP_FCGI_CHILDREN=4
export PHP_FCGI_MAX_REQUESTS=200
export PHPRC="/home/oott123/conf/php.ini"
exec /usr/local/php52/bin/php-cgi

其中 PHPRC 为用户定义的 php.ini 文件。再创建 .htaccess 文件(此处需要 AllowOverrideAll )如下:

<FilesMatch \.php$>
    SetHandler application/x-httpd-php-52
</FilesMatch>
Action application/x-httpd-php-52 /cgi-bin/php52.fcgi

如果需要再加入 php 5.4 的支持,再添加如下代码:

<FilesMatch \.php54$>
    SetHandler application/x-httpd-php
</FilesMatch>

至此已经完成了 PHP 多版本的配置。

在Linux下使用ftp.proxy搭建FTP反向代理/透明代理

由于某些原因,自己的FTP服务器访问起来相当不顺畅。好在手上有速度稍快的资源可以拿来当反代,Google了一大堆,最后终于找到了一个合适的项目,叫 ftp.proxy 。ftp.proxy 是一个提供 FTP 代理的程序,支持透明代理,也支持标准化的 FTP 代理。这里我们把它配置为透明(反向)代理来使用。

首先,到 ftp.proxy 的下载页面 查看最新的 ftp.proxy ,在服务器上下载它。写这篇文章的时候,最新的 ftp.proxy 是1.2.3。由于这个项目已经快10年没有更新了(The current stable version of ftp.proxy is 1.2.3 Released 30. July 2004),为了防止官方出现某些意外的错误,我把它放了一份到了百度网盘上,你可以考虑下载它。

wget http://www.ftpproxy.org/download/ftpproxy-current.tgz

然后就是编译安装。如同官网所说,这是一个“old story”。

tar -xvf ftpproxy-1.2.3.tgz #文件名请自行修改
cd ftpproxy-1.2.3/
make
mkdir /usr/local/man #如果出错是没关系的
sudo make install

安装的时候遇到了一个错误,是因为自己的 CentOS 并没有/usr/local/man/这个目录。修正的方法已经写在上面了。

然后就可以开始使用ftp.proxy了。如下:

ftp.proxy -D 12580 192.168.1.1:10086

其中12580是你要在反代服务器上监听的端口,而10086则是原服务器的端口。

在本地试着连接一下,Enjoy it~

在CentOS 6下安装配置Squid缓存服务器

基于成本考虑,这次把博客折腾换到了东海岸。

东海岸的机房真不是一般的慢啊,已经快慢掉渣了。不争气的服务器和拉车的老牛一样慢,一个页面四五秒钟都加载不起来。

翻了一下现在市面上的CDN,都不满意,不是速度慢就是被墙了,想来想去,还是自己再弄个LA机房的服务器反代过去,充当一个“伪”CDN好了。

于是弄了个LA机房的VPS,还是自己习惯的CentOS6.2。不知道为何对CentOS比较有感情,虽然最近接触Debian觉得真是个好系统。说不定从哪天开始就移情别恋了=-=

好了说正事。在CentOS下装Squid并不难,如下:

yum install squid

好吧如果文章就到这里就没有了你们一定会喷我的。所以文章还没结束——还有最重要的,把Squid作为一个反向代理服务器来配置。

用yum装好之后,squid的主配置文件在/etc/squid/squid.conf,装好之后里面一堆东西,其实我们全部都可以不要。如下把它删掉:

mv /etc/squid/squid.conf /etc/squid/squid.conf.bak
vim /etc/squid/squid.conf

然后敲以下配置:

acl manager proto cache_object
http_access deny manager
http_access allow all
http_port 80  accel vhost vport #反向代理监听端口
cache_peer 192.168.1.1 parent 80 0 no-query originserver round-robin  name=web #源服务器端口和IP地址,以及名字
cache_peer_domain web !localhost #对应域名设定源服务器
forwarded_for truncate #截断X-Forwarded-For,防止IP地址欺骗;也可以不要这一行
cache_mem 32 MB #内存缓存大小
cache_dir ufs /var/spool/squid 100 16 256 #磁盘缓存设置
coredump_dir /var/spool/squid
refresh_pattern -i (/cgi-bin/|\?) 0    0%      0
refresh_pattern -i \.php 0    0%      0
refresh_pattern .              0       20%     4320

如果像我一样只有一台服务器,那就按上面的配置就好了,把192.168.1.1改成你源站的服务器,然后就算设置完成了。

如果你有很多不同的服务器,然后想对应不同的域名,就修改其中的cache_peer如下:

cache_peer 192.168.1.1 parent 80 0 no-query originserver round-robin  name=web1
cache_peer_domain web1 best33.com
cache_peer 192.168.1.2 parent 80 0 no-query originserver round-robin  name=web2
cache_peer 192.168.1.3 parent 80 0 no-query originserver round-robin  name=web3
cache_peer_domain web2 web3 oott123.com

最后重启squid,设置开机自启动

service squid restart
chkconfig squid on

这样就算配置完了。