标签 centos 下的文章

本文原来拟定的标题为:《墙,墙,又是墙!》,以显示我的愤怒。

故事背景是由于 Google Workspace 放弃了白嫖用户,我转付费了其中若干套,放弃了若干套。放弃的订阅可以导出全部数据到 GCP 存储桶。

大概是这样的:

在 gsutil 工具主页上看到安装方式的文档链接,文档首选通过自定义 repo 的方式安装:

sudo tee -a /etc/yum.repos.d/google-cloud-sdk.repo << EOM
[google-cloud-cli]
name=Google Cloud CLI
baseurl=https://packages.cloud.google.com/yum/repos/cloud-sdk-el8-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=0
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
       https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOM

sudo dnf install google-cloud-cli

经测试,由于众所周知的原因,这个方案在国内主机上是行不通的。

知天朝者,Google 也。他们还提供了通过归档包的方式进行安装的方案。目前可以下载到的最新的版本归档是:google-cloud-sdk-377.0.0-linux-x86_64.tar.gz。

这里要感谢仁慈的 TG,没有赶尽杀绝,留了 dl.google.com 活口:

# wget -c https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-377.0.0-linux-x86_64.tar.gz
# tar xzf google-cloud-sdk-377.0.0-linux-x86_64.tar.gz
# cd google-cloud-sdk
# ./install.sh

安装完成后提示:

To update your SDK installation to the latest version [381.0.0], run:
  $ gcloud components update

作为精神强迫症患者,必须来一波:

# gcloud components update

到这里身心还较为愉悦,尝试列出 GCP 存储桶的文件:

# gsutil ls gs://takeout-export-xxxx

我就猜到会报错:

ServiceException: 401 Anonymous caller does not have storage.objects.list access to the Google Cloud Storage bucket.

- 阅读剩余部分 -

不知道有多少同学遇到过类似反人类的需求:在 Web 上对 MongoDB 进行可视化管理。之前在服务器上用 node 跑了一套 adminMongo,甚是不稳定。用户端一些随机操作容易导致这个服务宕掉,查日志是这个样子的:

/adminMongo/node_modules/nedb/lib/datastore.js:77
    if (err) { throw err; }
               ^

Error: "toString()" failed
    at Buffer.toString (buffer.js:495:11)
    at tryToString (fs.js:453:15)
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:444:12)

Google 半天资料也较少,遂欲弃之。

期间也折腾了一下 phpmoadmin,说实话距离我美好的想象相去甚远。

后来重新折腾了一遍 admin-mongo,感觉是我以前的打开方式不对。记录之,用于自我马克。

操作系统 CentOS 7,以 yum 方式安装 node:

yum install nodejs -y

理论上会为您安装好 node 与 npm。确认一下:

node --version
npm --version

我这里的情况是:

node --version
v6.17.1

npm --version
3.10.10

继续安装 pm2:

npm install -g pm2

确认一下:

pm2 --version

我这边是:

pm2 --version
3.3.1

然后就可以直接安装 admin-mongo:

npm install -g admin-mongo

安装目录是 /usr/lib/node_modules/admin-mongo,因此:

cd /usr/lib/node_modules/admin-mongo
pm2 start app.js --name admin-mongo

看到类似这样的信息:

[PM2] Starting /usr/lib/node_modules/admin-mongo/app.js in fork_mode (1 instance)
[PM2] Done.
┌─────────────┬────┬─────────┬──────┬───────┬────────┬─────────┬────────┬─────┬───────────┬──────┬──────────┐
│ App name    │ id │ version │ mode │ pid   │ status │ restart │ uptime │ cpu │ mem       │ user │ watching │
├─────────────┼────┼─────────┼──────┼───────┼────────┼─────────┼────────┼─────┼───────────┼──────┼──────────┤
│ admin-mongo │ 0  │ 0.0.23  │ fork │ 18021 │ online │ 0       │ 0s     │ 0%  │ 13.3 MB   │ root │ disabled │
└─────────────┴────┴─────────┴──────┴───────┴────────┴─────────┴────────┴─────┴───────────┴──────┴──────────┘

到这里 admin-mongo 就开始提供服务了,监听端口 TCP 1234。验证一下:

netstat -antlp | grep 1234
tcp        0      0 0.0.0.0:1234            0.0.0.0:*               LISTEN      18021/node /usr/lib

好,完美。赶紧做个反代用起来,不想被直接访问到的话,我用了比较猥琐的办法:

echo 用户名:密码 > /usr/local/nginx/conf/vhost/xxx.passwd
vim /usr/local/nginx/conf/vhost/xxx.conf

在 location / 中增加:

auth_basic "请替换成您的登录提示";
auth_basic_user_file /usr/local/nginx/conf/vhost/xxx.passwd;

最后 server nginx reload 一下完事儿。

故事背景:

  • 暂未启用 CI/CD 机制;
  • JVM 以 Jar 包方式启动;
  • 存在开发、测试、生产三套环境。

解决的痛点:

  • 解放劳动力,使 Jar 包从开发、测试到生产环境的部署自动化;
  • 友好且优雅地管理 JVM;
  • 我要看到更详细的日志。嗯,配合 ELK 简直真香。

实现前提:

  • Jar 包中配置文件(数据池、配置项等)剥离,每套环境独立,Jar 包重复利用;
  • 各环境中已配置了 FTP 服务,且可访问到 Jar 包所在目录。

好,开始表演:

#!/bin/sh
# author: xuchao_dot_org

DATE_TIME=$(date "+%Y-%m-%d %H:%M:%S");

BASE_DIR=/data/java/jar/myproject
TMP_DIR=/data/java/tmp/myproject
PROJECT_NAME=myproject_name # 同样也是 Jar 包的文件名
CONFIG_FILE=/data/java/conf/myproject/application.yml
LOG_FILE=/data/java/logs/myproject/$(date +%Y_%m_%d_%H_%M_%S).log

DEV_IP=
DEV_PORT=
DEV_USERNAME=
DEV_PASSWD=
DEV_DIR=/jar/myproject
TEST_IP=
TEST_PORT=
TEST_USERNAME=
TEST_PASSWD=
TEST_DIR=/jar/myproject

export JAVA_HOME=/data/java/jdk/jdk1.8.0_191
export PATH=$JAVA_HOME/bin:$PATH
export JAVA=$JAVA_HOME/bin/java
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

function running() {
    count=`ps aux | grep java | grep ${PROJECT_NAME}.jar | grep -v grep | wc -l`
    procedure=`ps -ef | grep -w "${PROJECT_NAME}.jar" | grep -w "java" | grep -v "grep" | awk '{print $2}'`
    if [ ${count} -eq 1 -o "${procedure}" != "" ]; then
        return 0
    else
        return 1
    fi
}

function start() {
    if running; then
        echo "${PROJECT_NAME} is running."
        echo "${DATE_TIME} failed to start, ${PROJECT_NAME} is running." 2>&1 >> $LOG_FILE
        exit 1
    fi
    echo "starting ${PROJECT_NAME}..."
    echo "${DATE_TIME} starting ${PROJECT_NAME}..." >> $LOG_FILE
    echo ${CLASSPATH} >> $LOG_FILE
    exec nohup $JAVA -server -Xms128m -Xmx256m -Xss256k -Dspring.config.location=${CONFIG_FILE} -Djava.io.tmpdir=${TMP_DIR} -jar ${BASE_DIR}/${PROJECT_NAME}\.jar >> $LOG_FILE 2>&1 & # 需要调整 JVM 启动参数的选手请看这里
    sleep 15
    if running; then
                echo "${PROJECT_NAME} is successfully started."
        echo "${DATE_TIME} ${PROJECT_NAME} is successfully started." 2>&1 >> ${LOG_FILE}
        else
                echo "${PROJECT_NAME} is failed to start."
        echo "${DATE_TIME} ${PROJECT_NAME} is failed to start." 2>&1 >> ${LOG_FILE}
    fi
}

function stop() {
    if ! running; then
        echo "${PROJECT_NAME} is not running." | tee ${LOG_FILE}
        echo "${DATE_TIME} failed to stop, ${PROJECT_NAME} is not running." 2>&1 >> ${LOG_FILE}
        exit 1
    fi
    echo "stopping ${PROJECT_NAME}..."
    echo "${DATE_TIME} stopping ${PROJECT_NAME}..." >> $LOG_FILE
    pid=`ps -ef | grep -w "${PROJECT_NAME}.jar" | grep -w "java" | grep -v "grep" | awk '{print $2}'`
    kill -15 ${pid}
    sleep 5
    if ! running; then
        echo "${PROJECT_NAME} is successfully stoped."
        echo "${DATE_TIME} ${PROJECT_NAME} is successfully stoped." 2>&1 >> ${LOG_FILE}
    else
        echo "${PROJECT_NAME} is failed to stop. trying again..."
        echo "${DATE_TIME} ${PROJECT_NAME} is failed to stop. trying again..." 2>&1 >> ${LOG_FILE}
        kill -15 ${pid}
        sleep 5
        if ! running; then
            echo "${PROJECT_NAME} is successfully stoped. [2nd attempt]"
            echo "${DATE_TIME} ${PROJECT_NAME} is successfully stoped. [2nd attempt]" 2>&1 >> ${LOG_FILE}
        else
            echo "${PROJECT_NAME} is failed to stop. [2nd attempt]"
            echo "${DATE_TIME} ${PROJECT_NAME} is failed to stop. [2nd attempt]" 2>&1 >> ${LOG_FILE}
        fi
    fi
}

function status() {
    if running; then
        echo "${PROJECT_NAME} is running."
    else
        echo "${PROJECT_NAME} is stopped."
    fi
}

function help() {
    echo "script for jvm managerment. author: xuchao_dot_org"
    echo "usage: $0 [start|stop|restart|status|get|help]"
    echo "    start:        start the ${PROJECT_NAME} server"
    echo "    stop:        stop the ${PROJECT_NAME} server"
    echo "    restart:    restart the ${PROJECT_NAME} server"
    echo "    status:        get ${PROJECT_NAME} current status, running or stopped"
    echo "    get:        [dev|test] [version] get file from dev or test"
    echo "    help:        show this message"
}

function get_dev() {
    [ -z $1 ] && echo "params error. please see [ $0 help ] for more information." && exit 1
    echo "geting file [${PROJECT_NAME}-$1.jar] from dev..."
    echo "${DATE_TIME} geting file [${PROJECT_NAME}-$1.jar] from dev..." 2>&1 >> ${LOG_FILE}
    ftp -inp 2>&1 >> ${LOG_FILE} <<EOF
open ${DEV_IP} ${DEV_PORT}
user ${DEV_USERNAME} ${DEV_PASSWD}
cd ${DEV_DIR}
lcd ${BASE_DIR}
get ${PROJECT_NAME}-$1.jar
close
bye
EOF
    if [ -f "${BASE_DIR}/${PROJECT_NAME}-$1.jar" ]; then
        echo "get file [${PROJECT_NAME}-$1.jar] from dev successfully."
        echo "${DATE_TIME} get file [${PROJECT_NAME}-$1.jar] from dev successfully." 2>&1 >> ${LOG_FILE}
        \cp -rf ${BASE_DIR}/${PROJECT_NAME}-$1.jar ${BASE_DIR}/${PROJECT_NAME}.jar
        echo "copy file [${PROJECT_NAME}-$1.jar] into [${PROJECT_NAME}.jar] successfully."
        echo "${DATE_TIME} copy file [${PROJECT_NAME}-$1.jar] into [${PROJECT_NAME}.jar] successfully." 2>&1 >> ${LOG_FILE}
    else
        echo "failed to get file [${PROJECT_NAME}-$1.jar] from dev."
        echo "${DATE_TIME} failed to get file [${PROJECT_NAME}-$1.jar] from dev." 2>&1 >> ${LOG_FILE}
    fi
}

function get_test() {
    [ -z $1 ] && echo "params error. please see [ $0 help ] for more information." && exit 1
    echo "geting file [${PROJECT_NAME}-$1.jar] from test..."
    echo "${DATE_TIME} geting file [${PROJECT_NAME}-$1.jar] from test..." 2>&1 >> ${LOG_FILE}
    ftp -inp 2>&1 >> ${LOG_FILE} <<EOF
open ${TEST_IP} ${TEST_PORT}
user ${TEST_USERNAME} ${TEST_PASSWD}
cd ${TEST_DIR}
lcd ${BASE_DIR}
get ${PROJECT_NAME}-$1.jar
close
bye
EOF
    if [ -f "${BASE_DIR}/${PROJECT_NAME}-$1.jar" ]; then
        echo "get file [${PROJECT_NAME}-$1.jar] from test successfully."
        echo "${DATE_TIME} get file [${PROJECT_NAME}-$1.jar] from test successfully." 2>&1 >> ${LOG_FILE}
        \cp -rf ${BASE_DIR}/${PROJECT_NAME}-$1.jar ${BASE_DIR}/${PROJECT_NAME}.jar
        echo "copy file [${PROJECT_NAME}-$1.jar] into [${PROJECT_NAME}.jar] successfully."
        echo "${DATE_TIME} copy file [${PROJECT_NAME}-$1.jar] into [${PROJECT_NAME}.jar] successfully." 2>&1 >> ${LOG_FILE}
    else
        echo "failed to get file [${PROJECT_NAME}-$1.jar] from test."
        echo "${DATE_TIME} failed to get file [${PROJECT_NAME}-$1.jar] from test." 2>&1 >> ${LOG_FILE}
    fi
}

function get_file() {
    [[ -z $1 || -z $2 ]] && echo "params error. please see [ $0 help ] for more information." && exit 1
    case "$1" in
        dev)
            get_dev $2
            ;;
        test)
            get_test $2
            ;;
        *)
            help
            exit 1
            ;;
    esac
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        $0 stop
        sleep 5
        $0 start
        ;;
    status)
        status
        ;;
    help)
        help
        ;;
    get)
        get_file $2 $3
        ;;
    *)
        help
        exit 1
        ;;
esac

演出结束,下期再见。

JVM 的日志太多了,tail -f 累觉不爱,于是有搭建 ELK 堆栈的需求。

ELK 堆栈主要是 4 个组件:

  • Elasticsearch:用于存储日志
  • Kibana:用于搜索和可视化日志的Web界面
  • Logstash:用于处理传入的日志
  • Filebeat 代理:充当日志传送代理,利用伐木工具网络协议与 Logstash 进行通信

本次部署条件:ELK 部署在单机,操作系统为 CentOS 7。JVM 通过 Logstash 将日志写入 Elasticsearch,客户端通过 Kibana 实现可视化管理。ELK 组件均使用 7.3.0 版本。未使用 Filebeat 搜集 syslog,此项工作标记为 TODO。

开始。

1、准备工作

1.1、配置国内的 yum 源

增加一个 repo 文件:

vim /etc/yum.repos.d/elasticsearch.repo

添加清华镜像:

[elasticsearch-7.x]
name=Elasticsearch repository for 7.x packages
baseurl=https://mirrors.tuna.tsinghua.edu.cn/elasticstack/yum/elastic-7.x/
gpgcheck=0
enabled=1

强迫症患者来一波:

yum makecache

1.2、安装 Java 运行环境

这里我安装的是 OpenJDK(也可以是 Oracle Java),版本要求 1.8.0 及以上:

yum -y install java

确认 JDK 版本:

java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-b10)
OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)

若已安装 OpenJDK 且版本为 1.7,很有可能无法通过 yum 来升级。可先 yum remove java,再 yum -y install java。

2、安装与配置 ELK 组件

2.1、安装 ELK 组件

yum -y install elasticsearch kibana logstash

为 Elasticsearch 与 Logstash 创建数据存放与日志的目录:

mkdir -p /data/elasticsearch/{data,logs}
mkdir -p /data/logstash/{data,logs}
chown -R elasticsearch:elasticsearch /data/elasticsearch
chown -R logstash:logstash /data/logstash

2.2、配置 Elasticsearch

修改 Elasticsearch 配置文件:

vim /etc/elasticsearch/elasticsearch.yml

内容为:

cluster.name: test
node.name: node-1
path.data: /data/elasticsearch/data
path.logs: /data/elasticsearch/logs
network.host: 0.0.0.0
http.port: 9200
discovery.seed_hosts: ["127.0.0.1"]
cluster.initial_master_nodes: ["node-1"]

因为是单机部署,cluster.initial_master_nodes 中的节点名称要与 node.name 一致。其余都好理解。

启动 Elasticsearch:

service elasticsearch start

验证 Elasticsearch 是否成功启动:

ss -ntlup | grep -E "9200|9300"
tcp    LISTEN     0      32768    :::9200                 :::*                   users:(("java",pid=30466,fd=286))
tcp    LISTEN     0      32768    :::9300                 :::*                   users:(("java",pid=30466,fd=237))

2.3、配置 Kibana

修改 Kibana 配置文件:

vim /etc/kibana/kibana.yml

内容为:

server.port: 5601
server.host: "127.0.0.1"
elasticsearch.hosts: ["http://127.0.0.1:9200"]
kibana.index: ".kibana"
i18n.locale: "zh-CN"

这里值得注意的是,7.3.0 以前的版本中,elasticsearch.hosts 表现为 elasticsearch.url,需要注意甄别。

关于 Kibana 汉化:自 6.x 以来,官方自带汉化资源,7.x 的方法是通过修改配置文件 i18n.locale: "zh-CN" 即可。5.x 与 6.x 的汉化可参考这个项目

启动 Kibana:

systemctl start kibana

验证 Kibana 是否成功启动:

ss -ntlup | grep 5601
tcp    LISTEN     0      511       *:5601                  *:*                   users:(("node",pid=31095,fd=18))

2.4、配置 Logstash

修改 Logstash 配置文件:

vim /etc/logstash/logstash.yml

内容为:

path.data: /data/logstash/data
path.logs: /data/logstash/logs

创建一个索引的配置文件:

vim /etc/logstash/conf.d/my-project-1.conf

这里是针对 JVM 的配置:

input {
    tcp {
        port => 5044
        codec => json_lines
    }
}

output {
    elasticsearch {
        hosts  => ["127.0.0.1:9200"]
        index => "test-myproject-1-%{type}"
    }
    stdout {
        codec => rubydebug
    }
}

配置文件的创建根据项目需要以此类推。

启动 Logstash:

systemctl start logstash

若启动失败,可执行:

/usr/share/logstash/bin/system-install /etc/logstash/startup.options systemd

验证 Logstash 是否成功启动:

ss -ntlup | grep 5044
tcp    LISTEN     0      1024     :::5044                 :::*                   users:(("java",pid=8747,fd=134))

2.5、反向代理

我的环境中,“本机”没有公网 80 或 443 端口权限。因此通过 nginx 反代至 ELK 所在主机的 Kibana 端口,即本文的:5601。

3、收尾

到这里为止若无报错,ELK 环境就搭建完毕了。收个尾:

systemctl daemon-reload  # 重新加载所有配置文件
systemctl restart elasticsearch kibana logstash  # 启动 ELK
systemctl enable elasticsearch kibana logstash  # 将 ELK 加入开机启动
systemctl status elasticsearch kibana logstash  # 查看 ELK 启动状态

现在,我们的 Java 项目可以通过 5044 端口直接将日志输出到 Logstash。其它诸如 MySQL、系统日志等的配置,敬请期待。

Oneinstack 有一个备份配置脚本:backup_setting.sh 及备份脚本:backup.sh。目前已经支持本地备份、远端主机备份及诸如阿里云、七牛云等各种云的备份。虽然同时支持多种方式进行备份,但同一种备份方式只能选一次,譬如不能同时向两个远端主机进行备份,且只能通过 ssh 方式。在比较看重异地灾备的情况下,多远端主机备份的需求不能满足;在不方便开启外网 ssh 的情况下,这个脚本也不能满足。

面对前述情况我做了一些调整:

1、Oneinstack 自带备份脚本只进行本地备份和各种云的备份。默认本地备份路径是 /data/backup,通过观察备份文件名的规律可得出:

ls -lah /data/backup/*20190101*

可匹配到当日备份成功的新文件。

2、远端备份机搭建 FTP 服务,一般 VPS 需要开启被动模式支持,防火墙配置好被动模式的端口段。

3、本地提前安装好 FTP 客户端:

yum -y install ftp

需要每向一台远端备份机上传,便增加一个脚本:

#!/bin/bash
ftp -n<<!
open REMOTE_HOST_IP REMOTE_PORT
user FTP_USER FTP_PASSWD
binary
hash
cd /
lcd /data/backup
prompt
mput *$(date "+%Y%m%d")*
close
bye
!

注意自行修改脚本中大写字母部分的配置,也可拎出来做变量配置。若有问题可在 ftp 后面加 -v 或 -d 参数,输出对端返回和打开 debug 模式,以进一步调试。

测试通过后就可以放 crontab 了,需要备份到几个地方就开几个脚本。再也不用担心神马地震、海啸、核武,美滋滋。

- EOF -