知乎专栏 |
docker run -d -p 5000:5000 --name registry registry:latest
iMac:registry neo$ mkdir etc iMac:registry neo$ htpasswd -Bbn neo chen > etc/htpasswd or docker run --entrypoint htpasswd registry:2 -Bbn neo passw0rd > etc/htpasswd
docker run -d \ --restart=always \ --name registry \ -v `pwd`/etc:/usr/local/etc \ -e "REGISTRY_AUTH=htpasswd" \ -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ -e REGISTRY_AUTH_HTPASSWD_PATH=/usr/local/etc/htpasswd \ -e REGISTRY_HTTP_ADDR=0.0.0.0:443 \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/usr/local/etc/domain.cer \ -e REGISTRY_HTTP_TLS_KEY=/usr/local/etc/domaon.key \ -p 443:443 \ registry:2
docker pull centos:7
# docker pull centos:7 7: Pulling from library/centos 343b09361036: Pull complete Digest: sha256:bba1de7c9d900a898e3cadbae040dfe8a633c06bc104a0df76ae24483e03c077 Status: Downloaded newer image for centos:7
基于 CentOS 7 运行一个容器
docker run -it --name mycentos docker.io/centos:7 /bin/bash
# docker run -it --name mycentos docker.io/centos:7 /bin/bash
运行后直接进入了容器的shell控制台默认是bash
# yum install -y java-1.8.0-openjdk # cat >> /etc/profile.d/java.sh <<'EOF' export JAVA_HOME=/usr/java/default export JAVA_OPTS="-server -Xms2048m -Xmx4096m -Djava.io.tmpdir=/tmp -Djava.security.egd=file:/dev/./urandom -Dfile.encoding=UTF8 -Duser.timezone=GMT+08" export CLASSPATH=$JAVA_HOME/lib:$JAVA_HOME/jre/lib:. export PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin: EOF # source /etc/profile.d/java.sh
检查Java是否安装成功
# whereis java java: /usr/bin/java /usr/lib/java /etc/java /usr/share/java /usr/share/man/man1/java.1.gz # java -version openjdk version "1.8.0_131" OpenJDK Runtime Environment (build 1.8.0_131-b11) OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)
创建应用程序目录
# mkdir -p /www/netkiller.cn/www.netkiller.cn/
推出当前容器
# exit
复制 jar 文件到Docker容器
docker cp /www/netkiller.cn/www.netkiller.cn/www.netkiller.cn-0.0.1.war mycentos:/usr/local/libexec
启动容器
# docker start mycentos mycentos
进入容器
# docker exec -it mycentos /bin/bash
如果仅仅是测试可以手动启动 Srping boot 项目
# cat >> /root/run.sh <<EOF java -server -Xms2048m -Xmx8192m -jar /usr/local/libexec/www.netkiller.cn-0.0.1.war EOF chmod u+x /root/run.sh
生产环境请使用启动脚本
# curl -s https://raw.githubusercontent.com/oscm/build/master/Application/Spring/service/springbootd -o /etc/init.d/springbootd # chmod +x /etc/init.d/springbootd
编辑启动脚本 /etc/init.d/springbootd 修改下面配置项
############################################## BASEDIR="/www/netkiller.cn/api.netkiller.cn" JAVA_HOME=/srv/java JAVA_OPTS="-server -Xms2048m -Xmx8192m -Djava.security.egd=file:/dev/./urandom" PACKAGE="api.netkiller.cn-0.0.2-release.jar" CONFIG="--spring.config.location=$BASEDIR/application.properties -Dspring.profiles.active=production -Dserver.port=8080 -Dlog.level=info" USER=www ############################################## NAME=springbootd PROG="$JAVA_HOME/bin/java $JAVA_OPTS -jar $BASEDIR/$PACKAGE $CONFIG" LOGFILE=/var/tmp/$NAME.log PIDFILE=/var/tmp/$NAME.pid ACCESS_LOG=/var/tmp/$NAME.access.log ##############################################
你也可以使用 systemd 启动脚本,详见《Netkiller Java 手札》
docker commit mycentos springboot:1
# docker commit mycentos springboot:1 sha256:757d92d642d1b5a7b244f6ddf89f24a8d463d154438651c83ba51a644b401782
启动 spring boot 容器
# docker run -d --name springboot -p 80:8080 springboot:1 /root/run.sh
-d: 以守护进程方式启动 --name:指定容器的名称 -p:映射容器8080端口到宿主机的80端口 springboot:1 :上一步制作好的springboot镜像,版本号为1
启动容器
# docker start springboot
停止容器
# docker stop springboot
http://download.redis.io/redis-stable/redis.conf
http://download.redis.io/redis-stable/sentinel.conf
docker pull redis
# docker pull redis Using default tag: latest latest: Pulling from library/redis 10a267c67f42: Pull complete 5b690bc4eaa6: Pull complete 4cdd94354d2a: Pull complete 71c1f30d820f: Pull complete c54584150374: Pull complete d1f9221193a6: Pull complete d45bc46b48e4: Pull complete Digest: sha256:548a75066f3f280eb017a6ccda34c561ccf4f25459ef8e36d6ea582b6af1decf Status: Downloaded newer image for redis:latest
# docker run --name my-redis -d redis 10207174e18f61290f9c869e6437fa787e459e07b076b82cedf800a8c37c515d
查看启动情况
# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 10207174e18f redis "docker-entrypoint..." 8 minutes ago Up 8 minutes 6379/tcp my-redis
# docker run -it --link my-redis:redis --rm redis redis-cli -h redis -p 6379 redis:6379> set name neo OK redis:6379> get name "neo" redis:6379> exit
# docker stop my-redis my-redis # docker rm my-redis my-redis # docker run --name my-redis -d -p 6379:6379 redis 10207174e18f61290f9c869e6437fa787e459e07b076b82cedf800a8c37c515d # docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1c4540d8617f redis "docker-entrypoint..." 2 seconds ago Up 1 second 0.0.0.0:6379->6379/tcp my-redis
检查端口
# ss -lnt | grep 6379 LISTEN 0 128 :::6379 :::*
version: "3.7" services: redis: image: redis:latest container_name: redis ports: - "6379:6379" volumes: - redis_data:/var/lib/redis restart: always networks: - dev networks: dev: driver: bridge volumes: redis_data:
version: '3.9' services: redis: image: redis:alpine container_name: redis restart: always hostname: redis.netkiller.cn user: redis:redis privileged: true environment: - TZ=Asia/Shanghai - LANG=en_US.UTF-8 ports: - 6379:6379 volumes: - ./conf/redis.conf:/etc/redis.conf - redis:/var/lib/redis - ./logs:/var/log/redis entrypoint: redis-server /etc/redis.conf command: --requirepass passw0rd volumes: redis:
确认配置生效
neo@MacBook-Pro-Neo ~ % docker exec -it redis redis-cli -a passw0rd Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe. 127.0.0.1:6379> config get dir 1) "dir" 2) "/var/lib/redis" 127.0.0.1:6379>
version: '3.8' services: redis: image: redis:latest environment: - TZ=Asia/Shanghai hostname: redis ports: - 6379:6379 networks: - test volumes: - data:/var/lib/redis configs: - source: config target: /usr/local/etc/redis.conf mode: 0440 deploy: replicas: 1 restart_policy: condition: on-failure resources: limits: cpus: "1" memory: 512M update_config: parallelism: 1 delay: 5s monitor: 10s max_failure_ratio: 0.1 order: start-first configs: config: file: ./redis.conf volumes: data: networks: test: driver: overlay
下载 配置文件 https://redis.io/topics/config
iMac:redis neo$ curl -sO https://raw.githubusercontent.com/redis/redis/6.0/redis.conf iMac:redis neo$ egrep -v "^#|^$" redis.conf
修改配置文件
bind 0.0.0.0 logfile "/var/log/redis/redis.log" dir /var/lib/redis appendonly yes
创建 Docker 网络
iMac:redis neo$ docker network create \ > --driver=overlay \ > --subnet=172.12.0.0/16 \ > --ip-range=172.12.0.0/16 \ > --gateway=172.12.0.1 \ > --attachable \ > test gvcz5y66ovrlqfaxb02zx026t iMac:redis neo$ docker network ls NETWORK ID NAME DRIVER SCOPE 786efe30f42d bridge bridge local 51e2b21d7daa docker_gwbridge bridge local 96ba0de26cd2 host host local 7r7k9robn0uu ingress overlay swarm cbf078a5f121 none null local d851mrlkludv redis_default overlay swarm q0h9awx86ef4 registry_default overlay swarm cf585ea9ceb4 registry_default bridge local gvcz5y66ovrl test overlay swarm iMac:redis neo$ docker stack deploy -c redis.yml redis Creating network redis_default Creating service redis_redis
查看服务
iMac:redis neo$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS 1ti2ndlpdhm8 redis_redis replicated 0/1 redis:latest *:6379->6379/tcp 1w6xjrl0sn88 registry_registry replicated 1/1 registry:latest *:5000->5000/tcp
查看容器运行状态
iMac:redis neo$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8407fd8fe66b redis:latest "docker-entrypoint.s…" 29 seconds ago Up 29 seconds 6379/tcp redis_redis.1.6fpqt3pdti03j9swn3x04ob9n
redis 日志
1:C 09 Aug 2021 15:13:20.270 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 1:C 09 Aug 2021 15:13:20.270 # Redis version=6.2.5, bits=64, commit=00000000, modified=0, pid=1, just started 1:C 09 Aug 2021 15:13:20.270 # Configuration loaded 1:M 09 Aug 2021 15:13:20.270 * monotonic clock: POSIX clock_gettime 1:M 09 Aug 2021 15:13:20.270 * Running mode=standalone, port=6379. 1:M 09 Aug 2021 15:13:20.270 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 1:M 09 Aug 2021 15:13:20.270 # Server initialized 1:M 09 Aug 2021 15:13:20.270 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. 1:M 09 Aug 2021 15:13:20.271 * Ready to accept connections
宿主主机上配置如下
[root@localhost ~]# cat >> /etc/sysctl.conf <<EOF # Redis net.core.somaxconn = 1024 vm.overcommit_memory=1 EOF
docker-compose.yml 中设置 net.core.somaxconn
[root@localhost redis]# cat docker-compose.yml version: '3.9' services: redis: image: redis:alpine container_name: redis restart: always hostname: redis.netkiller.cn user: redis:redis environment: - TZ=Asia/Shanghai - LANG=en_US.UTF-8 ports: - 6379:6379 volumes: - redis:/data sysctls: - net.core.somaxconn=511 command: --logfile /data/redis.log --requirepass passw0rd --appendonly yes volumes: redis:
本例子使用 alpine 版本
过程 2.1.
[root@iZj6ciilv2rcpgauqg2uuwZ]~# docker pull nginx Using default tag: latest latest: Pulling from library/nginx Digest: sha256:41ad9967ea448d7c2b203c699b429abe1ed5af331cd92533900c6d77490e0268 Status: Image is up to date for nginx:latest
启动容器
docker run --name my-nginx-container -p 80:80 -d nginx
上面不能满足生产环境的需求,通常不会将数据放在容器中,我的做法如下。
docker rm my-nginx-container -f docker run --name my-nginx-container \ -v /srv/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /srv/nginx/conf.d:/etc/nginx/conf.d:ro \ -v /var/log/nginx:/var/log/nginx:rw \ -v /www:/www:ro \ -p 80:80 -d nginx docker ps
首先观察一个现象,打开 linux 终端窗口,查看 nginx 进程。
[root@localhost ~]# ps ax | grep nginx 6670 ? Ss 0:00 nginx: master process /usr/sbin/nginx 6671 ? S 0:00 nginx: worker process 6672 ? S 0:00 nginx: worker process 6673 ? S 0:00 nginx: worker process 6674 ? S 0:00 nginx: worker process 9396 pts/0 S+ 0:00 grep --color=auto nginx
6670 ~ 6674 都是 nginx 的进程,其中 6670 nginx: master process /usr/sbin/nginx 是父进程,用于监听 80/443 端口。6671 ~ 6674 nginx: worker process 是子进程,每个进程中又产生多线程,每个线程对应一次用户TCP请求。
6671 ~ 6674 子进程的进程ID会变化,而 6670 是不变的。 6670 父进程可以接收操作系统传递过来的信号(不懂信号的同学请恶补,信号,共享内存,管道,Socket 可以实现进程间通信),也就是我们可以告诉正在运行的进程,现在要干什么。
给 6670 进程发送 HUP 信号,nginx 就会重新读取配置文件,刷新缓存,此时 6671~6674不受影响,会继续为用户体统TCP链接服务,直到都安全Close为止。此时 6670 父进程已经完成配置的更新,6671~6674 也完成了它的使命,下一次新用户过来 nginx 就会创建新的进程,这个过程是无缝的,用户感知不到,80/443 端口始终提供服务,不会有任何用户出现中断链接的情况。
现在来演示一下,执行 reload 就会刷新配置文件,清空缓存,同时会将闲置的 nginx: worker process 关闭,并开启新的子进程。
[root@localhost ~]# systemctl reload nginx [root@localhost ~]# ps ax | grep nginx 6670 ? Ss 0:00 nginx: master process /usr/sbin/nginx 6671 ? S 0:01 nginx: worker process is shutting down 9403 ? S 0:00 nginx: worker process 9404 ? S 0:00 nginx: worker process 9405 ? S 0:00 nginx: worker process 9406 ? S 0:00 nginx: worker process 9408 pts/0 S+ 0:00 grep --color=auto nginx
现在我们可以看到子进程ID的变化,9403 ~ 9406。父进程 nginx: master process /usr/sbin/nginx 的ID仍然是 6670
现在是容器中实现上面的 reload 操作。
[root@localhost ~]# cat docker-compose.yml version: '3.9' services: nginx: container_name: nginx restart: always image: nginx:latest ports: - 192.168.30.11:80:80 - 192.168.30.11:443:443
[root@localhost ~]# docker-compose up Starting nginx ... done Attaching to nginx nginx | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration nginx | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ nginx | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh nginx | 10-listen-on-ipv6-by-default.sh: info: IPv6 listen already enabled nginx | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh nginx | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh nginx | /docker-entrypoint.sh: Configuration complete; ready for start up nginx | 2021/07/12 20:55:41 [notice] 1#1: using the "epoll" event method nginx | 2021/07/12 20:55:41 [notice] 1#1: nginx/1.21.1 nginx | 2021/07/12 20:55:41 [notice] 1#1: built by gcc 8.3.0 (Debian 8.3.0-6) nginx | 2021/07/12 20:55:41 [notice] 1#1: OS: Linux 4.18.0-315.el8.x86_64 nginx | 2021/07/12 20:55:41 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576 nginx | 2021/07/12 20:55:41 [notice] 1#1: start worker processes nginx | 2021/07/12 20:55:41 [notice] 1#1: start worker process 24 nginx | 2021/07/12 20:55:41 [notice] 1#1: start worker process 25 nginx | 2021/07/12 20:55:41 [notice] 1#1: start worker process 26 nginx | 2021/07/12 20:55:41 [notice] 1#1: start worker process 27
[root@localhost ~]# docker exec -it nginx bash root@2d2637a6ac4d:/# ps ax PID TTY STAT TIME COMMAND 1 ? Ss 0:00 nginx: master process nginx -g daemon off; 24 ? S 0:00 nginx: worker process 25 ? S 0:00 nginx: worker process 26 ? S 0:00 nginx: worker process 27 ? S 0:00 nginx: worker process 623 pts/0 Ss 0:00 bash 629 pts/0 R+ 0:00 ps ax root@2d2637a6ac4d:/#
reload nginx
[root@localhost ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2d2637a6ac4d nginx:latest "/docker-entrypoint.…" 25 minutes ago Up 5 minutes 192.168.30.11:80->80/tcp, 192.168.30.11:443->443/tcp nginx [root@localhost ~]# docker container exec nginx nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful [root@localhost ~]# docker container exec nginx nginx -s reload 2021/07/12 21:01:41 [notice] 636#636: signal process started
再次查看进程
[root@localhost ~]# docker exec -it nginx bash root@2d2637a6ac4d:/# ps ax PID TTY STAT TIME COMMAND 1 ? Ss 0:00 nginx: master process nginx -g daemon off; 24 ? S 0:00 nginx: worker process 25 ? S 0:00 nginx: worker process 26 ? S 0:00 nginx: worker process 27 ? S 0:00 nginx: worker process 623 pts/0 Ss 0:00 bash 629 pts/0 R+ 0:00 ps ax root@2d2637a6ac4d:/# ps ax PID TTY STAT TIME COMMAND 1 ? Ss 0:00 nginx: master process nginx -g daemon off; 623 pts/0 Ss 0:00 bash 642 ? S 0:00 nginx: worker process 643 ? S 0:00 nginx: worker process 644 ? S 0:00 nginx: worker process 645 ? S 0:00 nginx: worker process 646 pts/0 R+ 0:00 ps ax
sudo mkdir -p /opt/mysql/{data,mysql.d,docker-entrypoint-initdb.d}
docker-compose.yaml
version: '3' services: mysql: # 镜像名 image: mysql:latest # 容器名 container_name: mysql # 重启策略 restart: always hostname: db.netkiller.cn environment: # 时区上海 TZ: Asia/Shanghai # root 密码 MYSQL_ROOT_PASSWORD: test # 初始化数据库 MYSQL_DATABASE: test # 初始普通化用户 MYSQL_USER: test # 用户密码 MYSQL_PASSWORD: test # 映射端口 ports: - 3306:3306 volumes: # 挂载数据 - ./mysql/data/:/var/lib/mysql/ # 挂载配置 - ./mysql/conf.d/:/etc/mysql/conf.d/ # 挂载初始化目录 - ./mysql/docker-entrypoint-initdb.d/:/docker-entrypoint-initdb.d/ command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --explicit_defaults_for_timestamp=true --lower_case_table_names=1
登陆测试
neo@MacBook-Pro-Neo ~ % docker exec -it mysql mysql -uroot -ptest mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 14 Server version: 8.0.25 MySQL Community Server - GPL Copyright (c) 2000, 2021, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
$ docker run -d --network some-network --name mongo \ -e MONGO_INITDB_DATABASE=test \ -e MONGO_INITDB_ROOT_USERNAME=admin \ -e MONGO_INITDB_ROOT_PASSWORD=secret \ mongo $ docker run -it --rm --network some-network mongo \ mongo --host mongo \ -u admin \ -p secret \ --authenticationDatabase admin \ test > db.getName(); test
version: '3.9' services: mongodb: image: mongo:latest container_name: mongo hostname: mongo.netkiller.cn restart: always user: mongodb:mongodb privileged: false volumes: - ./data:/data ports: - 27017:27017 environment: TZ: Asia/Shanghai LANG: en_US.UTF-8 MONGO_INITDB_DATABASE: admin MONGO_INITDB_ROOT_USERNAME: admin MONGO_INITDB_ROOT_PASSWORD: admin entrypoint: docker-entrypoint.sh mongod command: --logpath /data/mongod.log
[www@testing ~]$ sudo cat /var/log/mongodb/mongod.log | grep '"W"' {"t":{"$date":"2021-08-13T19:54:20.219+08:00"},"s":"W", "c":"ASIO", "id":22601, "ctx":"main","msg":"No TransportLayer configured during NetworkInterface startup"} {"t":{"$date":"2021-08-13T19:54:20.227+08:00"},"s":"W", "c":"ASIO", "id":22601, "ctx":"main","msg":"No TransportLayer configured during NetworkInterface startup"} {"t":{"$date":"2021-08-13T19:54:20.851+08:00"},"s":"W", "c":"CONTROL", "id":22178, "ctx":"initandlisten","msg":"/sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'","tags":["startupWarnings"]} {"t":{"$date":"2021-08-13T20:01:12.470+08:00"},"s":"W", "c":"ASIO", "id":22601, "ctx":"main","msg":"No TransportLayer configured during NetworkInterface startup"} {"t":{"$date":"2021-08-13T20:01:12.478+08:00"},"s":"W", "c":"ASIO", "id":22601, "ctx":"main","msg":"No TransportLayer configured during NetworkInterface startup"} {"t":{"$date":"2021-08-13T20:01:13.085+08:00"},"s":"W", "c":"CONTROL", "id":22178, "ctx":"initandlisten","msg":"/sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'","tags":["startupWarnings"]}
[root@testing ~]# docker exec -it mongo bash root@mongo:/# cat /sys/kernel/mm/transparent_hugepage/enabled [always] madvise never root@mongo:/# cat /sys/kernel/mm/transparent_hugepage/defrag always defer defer+madvise [madvise] never
root@mongo:/# echo never > /sys/kernel/mm/transparent_hugepage/defrag bash: /sys/kernel/mm/transparent_hugepage/defrag: Read-only file system
[root@testing ~]# if test -f /sys/kernel/mm/transparent_hugepage/enabled; then > echo never > /sys/kernel/mm/transparent_hugepage/enabled > fi [root@testing ~]# cat /sys/kernel/mm/transparent_hugepage/enabled always madvise [never] [root@testing ~]# docker exec -it mongo bash root@mongo:/# cat /sys/kernel/mm/transparent_hugepage/defrag always defer defer+madvise [madvise] never root@mongo:/# cat /sys/kernel/mm/transparent_hugepage/enabled always madvise [never] root@mongo:/# exit exit
解决方案 /etc/rc.local 中加入下面脚本,CentOS 8 Stream 开启 rc.local 请参考《Netkiller Linux 手札》
cat <<'EOF'>> /etc/rc.local if test -f /sys/kernel/mm/transparent_hugepage/enabled; then echo never > /sys/kernel/mm/transparent_hugepage/enabled fi if test -f /sys/kernel/mm/transparent_hugepage/defrag; then echo never > /sys/kernel/mm/transparent_hugepage/defrag fi EOF
[root@testing ~]# systemctl restart rc-local
version: '3.9' services: node: image: node:latest container_name: node restart: always hostname: node.netkiller.cn extra_hosts: - db.netkiller.cn:192.168.10.5 - redis.netkiller.cn:192.168.10.12 environment: TZ: Asia/Shanghai ports: - 7777:7777 volumes: - /opt/netkiller.cn/www.netkiller.cn:/opt/netkiller.cn/www.netkiller.cn working_dir: /opt/netkiller.cn/www.netkiller.cn entrypoint: node /opt/netkiller.cn/www.netkiller.cn/main.js