运维管理系统的组成

一套运维管理系统需要什么?

资产管理cmdb, 这会是整个系统的核心之一.

权限管理系统, 支持RABC(role-based access control)或者ABAC(Attribute-based access control)

任务管理系统, 支持定义/调用任务流.

可以调用发布系统.

可以执行服务器命令.

可选的审计模块.

可选的故障管理模块.

最近写了一个demo. 时机合适放出来. 地址

说说Elasticsearch内存分配

问题终于解决了, 本来想总结一下, joshua已经总结完了, 还比我自己总结的更好.

索性偷个懒(偷懒也需要找理由啊)

链接: Elasticsearch 内存那点事

那我就说一下ELK中其他的性能提升方式吧.

如果要实现业务系统日志汇总分析, 直接推动业务系统直接输出json格式的日志吧.

如果输出了plain 格式, 还需要耗费CPU去分析正则, 性能耗费不是一般地说.

grek在线debug,需要翻墙

http://grokdebug.herokuapp.com/

grok的解析式

https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns

利用gitlab的ci/cd发布PHP代码

 

公司某个PHP项目发布一直手动执行shell(基本逻辑是备份,指定php文件名并scp, reload php-fpm).用起来相当麻烦而且容易出错, 开发人员提出能不能协助简化操作.

对比了以下方案:

  1. 改写shell为rsync, 实现自动差异对比, 省去输入文件名的麻烦.
  2. 使用国内比较流行的jenkins搭建一套发布系统.
  3. 最新的gitlab(9.x)支持内置的ci/cd功能.

通过详细的功能性和便捷性考察, 最终选择使用gitlab内置的ci功能. 原因有:

  1. 一步到位, 用shell不方便查询发布历史和发布结果.
  2. 现在有一个低版本的源码编译的gitlab, 再引入jenkins还需要引入账号权限设置, 成本有些高.
  3. gitlab ci功能满足, 方便管理. BTW, gitlab的产品理念和公司理念都很不错.

 

实施步骤:

升级gitlab(源码编译改为同版本号的Omnibus版, 然后升级Omnibus版本到最新版)

安装gitlab-runner, 并考察几种发布方式的优缺点.

 

刚开始出于安全性考虑, gitlab-runner选用的是docker模式, 后来发现性能比shell模式差比较多.由于系统边界管理做的还不错, 为了追求性能改为了shell模式. 各位看各自的实际情况进行选择.

 

安装步骤官网描述的很清晰, 就不再赘述. 只列出了.gitlab-ci.yml的例子.

 

这个例子能用gitlab的账号系统, 登录后一键点击实现:

  1. 发布代码到预发布环境.
  2. 发布代码到生产环境.
  3. 手动会退到指定版本.
  4. 可以方便查看发布频率和状态.
before_script:
  - JOB_NAME=( $CI_BUILD_NAME ) && export SSH_HOST=${JOB_NAME[1]} && echo $SSH_HOST
  - RUN_MODE_SETTING=production
  - date
  - git status
  - git show && echo $?

stages:
  - deploy_staging
  - deploy_production
  - re_trigger_deploy

.rsync_artifacts:
  script: &rsync_artifacts |
    rsync -rtlpvz --delete $CI_PROJECT_DIR/ $SSH_USER@$SSH_HOST:$PROD_BASE_DIR/ --exclude='.git' --exclude='.gitlab-ci.yml' --exclude='.gitignore' --exclude='some/folder/'
    date

.reload_php7_fpm:
  script: &reload_php7_fpm |
    ssh $SSH_USER@$SSH_HOST "sudo /bin/bash -c 'date >> /tmp/ci.log; /etc/init.d/php7-fpm reload >>/tmp/ci.log 2>&1' &"

.do_other_command:
  script: &do_other_command |
    ssh $SSH_USER@$SSH_HOST "sudo /bin/bash -c 'date >> /tmp/ci.log;/bin/bash other_shell.sh arg >>/tmp/ci.log 2>&1' &"

.deploy_production: &deploy_production
  stage: deploy_production
  environment: production
  only:
    - master
    - triggers

.deploy_staging: &deploy_staging
  stage: deploy_staging
  environment: staging
  when: manual

.deploy_staging_template: &deploy_staging_template
  <<: *deploy_staging
  script:
    - '[ "${DEPLOY_PRODUCTION}" != "true" ] || exit 0'
    - *rsync_artifacts
    - *reload_php7_fpm
    - export RUN_MODE_SETTING=beta
    - *do_other_command
    - date

.deploy_production_template: &deploy_production_template
  <<: *deploy_production
  script:
    - '[ "${DEPLOY_PRODUCTION}" != "true" ] && exit 0'
    - *rsync_artifacts
    - *reload_php7_fpm

# ################上面是模板,下面是调用配置################
# 发布到服务器1
deploy_production 1.1.1.1: *deploy_production_template
# 发布到服务器2
deploy_production 2.2.2.2: *deploy_production_template
# 发布到预发布环境3
deploy_staging 3.3.3.3: *deploy_staging_template
# 发布到服务器4, 并执行其他命令
deploy_production 4.4.4.4:
  <<: *deploy_production
  script:
    - '[ "${DEPLOY_PRODUCTION}" != "true" ] && exit 0'
    - *rsync_artifacts
    - *reload_php7_fpm
    - *do_other_command
    - date

trigger_build:
  stage: re_trigger_deploy
  environment: production
  script:
    - echo ${CI_PROJECT_ID}
    - "curl -X POST -F token=${CI_TRIGGER_TOKEN} -F ref=master -F variables[DEPLOY_PRODUCTION]=true https://gitlab.yourcompany.com/api/v3/projects/${CI_PROJECT_ID}/trigger/builds"
  when: manual
  tags:
    - api
# 手工回退的方法:
# 1. 回退的SHA-1点打tag.例如: revert-20170322, 并把tag推送到服务器.
# 2. 模拟curl -X POST -F token=${CI_TRIGGER_TOKEN} -F "ref=revert-20170322" -F "variables[DEPLOY_PRODUCTION]=true" https://gitlab.yourcompany.com/api/v3/projects/${CI_PROJECT_ID}/trigger/builds

不足: 由于这个版本的gitlab ci在工作流下一步判断的时候逻辑有问题, shell的function功能支持也不是很好, 有了这么一个workaround的方式. 后续版本的gitlab可能会修复这些问题, 会更方便.

搭建第三方开发者代码/服务托管系统

 

公司新业务, 希望支持第三方开发者开发插件, 并实现插件代码的托管. 开发者可以上传代码, 审核通过后自动做资源分配, 代码发布等.

支撑公司内部开发人员和第三方开发者也需要关注更多的内容.

  1. 安全风险, 对于未知的第三方开发者. 要做好资源隔离并避免污染.
  2. 保证业务连续性.
  3. 自动实现代码发布, 支持回退/指定tag发布, 减少人工成本.
  4. 方便查看第三方应用产生的日志.
  5. 支持区分测试环境/生产环境.

业务上也需要支持框架, 如权限鉴定/一套k/v存储接口等.

 

运维的架构, 从来都应该是面向需求, 要满足业务需求, 但是又不能过早引入复杂的设计, 一步一步进行演变.

每个应用会分配给开发者一个仓库地址. 开发人员提交代码后, 在运维平台提交发布请求(仓库地址, 生成/测试环境), 管理员审核通过/拒绝, 自动发布代码到指定环境., 采用的实现方案如下:

通过docker来实现环境隔离.

通过nginx upstream机制做故障转移.

通过gitlab ci/cd实现代码发布接口.

通过运维平台提供资源分配, 生成配置文件, 调用发布接口, 提供日志接口, 修改子域名dns.

以第三方开发者的插件是nginx/php实现, 平台提供一个标准模板.

docker资源隔离:

提供Dockerfile

[nginx Dockerfile]

FROM alpine:3.3

MAINTAINER NGINX Docker Maintainers "docker-maint@nginx.com"

ENV NGINX_VERSION 1.10.1

ENV GPG_KEYS B0F4253373F8F6F510D42178520A9993A1C052F8
ENV CONFIG "\
  --prefix=/etc/nginx \
  --sbin-path=/usr/sbin/nginx \
  --modules-path=/usr/lib/nginx/modules \
  --conf-path=/etc/nginx/nginx.conf \
  --error-log-path=/var/log/nginx/error.log \
  --http-log-path=/var/log/nginx/access.log \
  --pid-path=/var/run/nginx.pid \
  --lock-path=/var/run/nginx.lock \
  --http-client-body-temp-path=/var/cache/nginx/client_temp \
  --http-proxy-temp-path=/var/cache/nginx/proxy_temp \
  --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
  --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
  --http-scgi-temp-path=/var/cache/nginx/scgi_temp \
  --user=nobody \
  --group=nobody \
  --with-http_ssl_module \
  --with-http_realip_module \
  --with-http_addition_module \
  --with-http_sub_module \
  --with-http_dav_module \
  --with-http_flv_module \
  --with-http_mp4_module \
  --with-http_gunzip_module \
  --with-http_gzip_static_module \
  --with-http_random_index_module \
  --with-http_secure_link_module \
  --with-http_stub_status_module \
  --with-http_auth_request_module \
  --with-http_xslt_module=dynamic \
  --with-http_image_filter_module=dynamic \
  --with-http_geoip_module=dynamic \
  --with-http_perl_module=dynamic \
  --with-threads \
  --with-stream \
  --with-stream_ssl_module \
  --with-http_slice_module \
  --with-mail \
  --with-mail_ssl_module \
  --with-file-aio \
  --with-http_v2_module \
  --with-ipv6 \
  "

RUN \
  apk add --no-cache --virtual .build-deps \
    gcc \
    libc-dev \
    make \
    openssl-dev \
    pcre-dev \
    zlib-dev \
    linux-headers \
    curl \
    gnupg \
    libxslt-dev \
    gd-dev \
    geoip-dev \
    perl-dev \
  && curl -fSL http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz -o nginx.tar.gz \
  && curl -fSL http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz.asc  -o nginx.tar.gz.asc \
  && export GNUPGHOME="$(mktemp -d)" \
  && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$GPG_KEYS" \
  && gpg --batch --verify nginx.tar.gz.asc nginx.tar.gz \
  && rm -r "$GNUPGHOME" nginx.tar.gz.asc \
  && mkdir -p /usr/src \
  && mkdir -p /var/log/nginx \
  && mkdir -p /var/cache/nginx/client_temp \
  && mkdir -p /var/cache/nginx/proxy_temp \
  && mkdir -p /var/cache/nginx/fastcgi_temp \
  && mkdir -p /var/cache/nginx/uwsgi_temp \
  && mkdir -p /var/cache/nginx/scgi_temp \
  && tar -zxC /usr/src -f nginx.tar.gz \
  && rm nginx.tar.gz \
  && cd /usr/src/nginx-$NGINX_VERSION \
  && ./configure $CONFIG --with-debug \
  && make \
  && mv objs/nginx objs/nginx-debug \
  && mv objs/ngx_http_xslt_filter_module.so objs/ngx_http_xslt_filter_module-debug.so \
  && mv objs/ngx_http_image_filter_module.so objs/ngx_http_image_filter_module-debug.so \
  && mv objs/ngx_http_geoip_module.so objs/ngx_http_geoip_module-debug.so \
  && mv objs/ngx_http_perl_module.so objs/ngx_http_perl_module-debug.so \
  && ./configure $CONFIG \
  && make \
  && make install \
  && rm -rf /etc/nginx/html/ \
  && mkdir /etc/nginx/conf.d/ \
  && mkdir -p /usr/share/nginx/html/ \
  && install -m644 html/index.html /usr/share/nginx/html/ \
  && install -m644 html/50x.html /usr/share/nginx/html/ \
  && install -m755 objs/nginx-debug /usr/sbin/nginx-debug \
  && install -m755 objs/ngx_http_xslt_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_xslt_filter_module-debug.so \
  && install -m755 objs/ngx_http_image_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_image_filter_module-debug.so \
  && install -m755 objs/ngx_http_geoip_module-debug.so /usr/lib/nginx/modules/ngx_http_geoip_module-debug.so \
  && install -m755 objs/ngx_http_perl_module-debug.so /usr/lib/nginx/modules/ngx_http_perl_module-debug.so \
  && ln -s ../../usr/lib/nginx/modules /etc/nginx/modules \
  && strip /usr/sbin/nginx* \
  && strip /usr/lib/nginx/modules/*.so \
  && runDeps="$( \
    scanelf --needed --nobanner /usr/sbin/nginx /usr/lib/nginx/modules/*.so \
      | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
      | sort -u \
      | xargs -r apk info --installed \
      | sort -u \
  )" \
  && apk add --virtual .nginx-rundeps $runDeps \
  && apk del .build-deps \
  && rm -rf /usr/src/nginx-$NGINX_VERSION \
  && apk add --no-cache gettext \
  \
  # forward request and error logs to docker log collector
  && ln -sf /dev/stdout /var/log/nginx/access.log \
  && ln -sf /dev/stderr /var/log/nginx/error.log

COPY nginx.conf /etc/nginx/nginx.conf
COPY nginx.vh.default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80 443

CMD ["nginx", "-g", "daemon off;"]

docker用到的nginx配置文件:

user  nobody;
worker_processes  4;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    use epoll;
    worker_connections  10240;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

 

nginx.vh.default.conf:

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/log/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

[php Dockerfile]

FROM php:7-fpm-alpine

MAINTAINER foobar Docker Maintainers "docker@foobar.com"
# install php-core-extensions
RUN apk add --no-cache freetype libpng libjpeg-turbo freetype-dev libpng-dev libjpeg-turbo-dev libmcrypt-dev && \
  docker-php-ext-configure gd \
    --with-gd \
    --with-freetype-dir=/usr/include/ \
    --with-png-dir=/usr/include/ \
    --with-jpeg-dir=/usr/include/ && \
  NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \
  docker-php-ext-install -j${NPROC} gd mcrypt && \
  apk del --no-cache freetype-dev libpng-dev libjpeg-turbo-dev

生成环境nginx故障转移的实现方式:

默认使用docker环境处理请求, 当docker挂掉后, 可以切换到宿主机进行处理(宿主机处理有安全风险),用这个方式实施简单,不需要引入docker集群相关的内容.

upstream app1000239 {

     server 10.47.49.233:11239;

     server 127.0.0.1:10239 backup;

}

server {

    listen 443 ssl;

    server_name app1000239.foobar.com;

    server_tokens off;

    proxy_set_header           Host $host;

    proxy_set_header           X-Real-IP $remote_addr;

    proxy_set_header           X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {

        proxy_pass      http://app1000239;

    }

    access_log /data/logs/nginx/${host}_access.log combined;

}

server {

        listen 127.0.0.1:10239;

        root   /data/web/app1000239/web/;

        index  index.html index.htm index.php;

        location ~* \.php$ {

                fastcgi_pass   127.0.0.1:9000;

                fastcgi_index  index.php;

                fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;

                include        fastcgi_params;

               

        }

        location / {

            index index.php;

            try_files $uri $uri/ /index.php$is_args$args;

        }

        access_log /data/logs/nginx/app1000239_vm_access.log combined;

    }

gitlab 实现ci/cd

gitlab支持通过在代码仓库中放置.gitlab-ci.yml来实现, 但是如果开发者直接支持管理.gitlab-ci.yml会有学习成本和较大的安全风险, 因此可以通过2个仓库来达到目的.

开发者可以上传代码到仓库A. 然后在运维平台提交发布申请.

仓库A对应的管理仓库AA. AA可以存放.gitlab-ci.yml, php/nginx的配置文件等. 运维平台可以调用仓库AA的代码发布来完成发布.

AA仓库的目录结构:

.gitlab-ci.yml (存放发布相关)

codes/ (这里是用的gitlab的子模块功能, 把仓库A当做子模块)

conf/ 这里存放渲染好的nginx/php配置文件, docker-compose文件

仓库AA可以设置一些默认参数, 并开启一个webhooks监听pipline event, 向运维平台发送发布成功/失败的结果.

附录:

[docker-compose.yaml文件]

version: '2'
services:
  app-nginx:
    image: registry.foobar.com/images/foobar-nginx
    volumes_from:
      - app-php
    ports:
     - "1.1.1.1:11038:80"
    command: nginx -g 'daemon off;'
    links:
      - app-php
    extra_hosts:
      - "api.foobar.com:2.3.4.5"
  app-php:
    image: registry.foobar.com/images/foobar-php-fpm-gd
    extra_hosts:
      - "api.foobar.com:2.3.4.5"
    volumes:
     - /data/web/foobar_app1000038:/usr/share/nginx/html
     - ./app-php.conf:/usr/local/etc/php-fpm.d/www.conf
     - ./app-nginx.conf:/etc/nginx/conf.d/default.conf
     - /etc/localtime:/etc/localtime:ro
     - /etc/resolv.conf:/etc/resolv.conf:ro

[app-php.conf]

[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 100
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20

[app-nginx.conf]

server {
    listen      80;
    server_tokens  off;

    root /usr/share/nginx/html/web/;
    index  index.html index.htm;
    location ~* \.php$ {
            fastcgi_pass   app-php:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include        fastcgi_params;
    }
    location / {
        index index.php;
        try_files $uri $uri/ /index.php$is_args$args;
    }
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    error_page   500 502 503 504  /50x.html;
    access_log  /var/log/nginx/access.log  main;
}

[.gitlab-ci.yml]

image: foobar/open_docs_php:latest
before_script:
  - ping git.foobar.com -w 2
  - eval $(ssh-agent -s)
  - ssh-add <(echo "$SSH_PRIVATE_KEY")
  - ssh-add <(echo "$GIT_CLONE_PRIVATE_KEY")
  - mkdir -p ~/.ssh
  - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
  - cd $CI_PROJECT_DIR && git checkout . && git submodule update --init --force
  - cd $CI_PROJECT_DIR/codes && echo ${commit_ref} && git fetch --all && git checkout ${commit_ref} .
stages:
  - test
  - build
  - deploy
deploy_stage:
  stage: deploy
  script:
    - if [ "${deploy_to}" == "development" ]; then echo "development" && rsync -vzrtog --delete $CI_PROJECT_DIR/codes/ $SSH_USER@$SSH_HOST:$DEV_BASE_DIR/ --exclude=.git; elif [ "${deploy_to}" == "production" ]; then echo "production" && rsync -vzrtog --delete $CI_PROJECT_DIR/codes/ $SSH_USER@$SSH_HOST:$DEV_BASE_DIR/ --exclude=.git; else echo "no variable provided"; exit 1; fi

 

运维平台实现:

开发者创建新应用后分配一个仓库账号,地址

分配域名:

创建针对测试/正式环境用的域名.

调用域名商接口配置对应的A记录.

创建对应的管理仓库.

初始化管理仓库:

创建.gitlab-ci.yml

创建子模块并放在codes/目录

渲染出的配置文件放到conf目录

创建仓库的变量(.gitlab-ci.yml里面会用到)

创建webhooks

调用gitlab ci发布代码, 推送conf里配置文件到宿主服务器, 使用docker-compose启动docker镜像, 并启动/重启外部nginx服务.

运维平台用的flask + sqlalchemy配合一些模块如python-gitlab, GitPython等实现.

整个代码/服务托管的运维方案这样就转起来了.

 

SQL schema 转为 orm class

在使用flask-sqlalchemy的时候, 可以通过定义class来生成数据库schema.
今天做cmdb的时候却碰到个另外一个需求, 想参考itop的sql schema, 并转为orm class.
在网上搜索了一下, 看到这篇博客里写,用的是 SqlAutoCode
http://blog.sina.com.cn/s/blog_537f224501013jk6.html
但测试SqlAutoCode有一些bug,更推荐pip install sqlacodegen
针对flask的是https://github.com/ksindi/sqlacodegen

这样就能来转换啦.

利用keepalived做redis的双机热备

应用场景:
服务压力不大,但是对可靠性要求极其高
特性:
至少需要两台服务器,其中一台为master始终提供服务,另外一台只有在主服务器挂掉的才提供服务。
优点:数据同步非常简单,不像负载均衡对数据一致性要求非常高
缺点:服务器有点浪费,始终有一台处于空闲状态

说明:
操作系统:CentOS 6.3 64位
redis服务器:172.27.4.38、172.27.4.39

架构规划:
需要在Master与Slave上均安装Keepalived和Redis,并且redis开启本地化策略。
master:172.27.4.38
slave:172.27.4.39
Virtural IP Address (VIP): 172.27.4.250

当 Master 与 Slave 均运作正常时, Master负责服务,Slave负责Standby;
当 Master 挂掉,Slave 正常时, Slave接管服务,同时关闭主从复制功能;
当 Master 恢复正常,则从Slave同步数据,同步数据之后关闭主从复制功能,恢复Master身份。与此同时Slave等待Master同步数据完成之后,恢复Slave身份。

redis安装步骤:
wget http://download.redis.io/releases/redis-2.4.15.tar.gz
tar zxvf redis-2.4.15.tar.gz
mkdir -p /usr/local/redis2.4.15/etc
mkdir -p /usr/local/redis2.4.15/data
mkdir -p /usr/local/redis2.4.15/logs

mv redis.conf /usr/local/redis2.4.15/etc
cd redis-2.4.15
make PREFIX=/usr/local/redis2.4.15 install
cd src/
make install

安装完毕
tree /usr/local/redis2.4.15/
/usr/local/redis2.4.15/
├── bin
│   ├── redis-benchmark
│   ├── redis-check-aof
│   ├── redis-check-dump
│   ├── redis-cli
│   └── redis-server
├── etc
└── redis.conf
2 directories, 6 files

修改redis配置文件

vim /usr/local/redis2.4.15/redis.conf
daemonize yes #当为yes时redis回后台运行,关闭命令为redis-cli shutdown
logfile /usr/local/redis2.4.15/logs/redis.log #指定日志文件目录
dbfilename dump.rdb #指定数据文件名称
dir /usr/local/redis2.4.15/data/ #指定数据文件文件夹未知

开启redis的命令为:
/usr/local/redis2.4.15/bin/redis-server /usr/local/redis2.4.15/etc/redis.conf
至此redis安装完毕

然后用客户端测试一下是否启动成功。

redis-cli
redis> set foo bar
OK
redis> get foo
“bar”

Keepalived安装
官方地址:http://www.keepalived.org/download.html 大家可以到这里下载最新版本。
环境配置:安装make 和 gcc openssl openssl-devel popt ipvsadm 等
yum -y install gcc make openssl openssl-devel wget kernel-devel
cd /usr/local/src/
wget http://www.keepalived.org/software/keepalived-1.2.13.tar.gz
tar zxvf keepalived-1.2.13.tar.gz
cd keepalived-1.2.13
预编译如果指定目录的话
./configure –prefix=/usr/local/keepalived
也可以直接使用默认
./configure
预编译后出现:
Keepalived configuration
————————
Keepalived version : 1.2.13
Compiler : gcc
Compiler flags : -g -O2
Extra Lib : -lssl -lcrypto -lcrypt
Use IPVS Framework : Yes
IPVS sync daemon support : Yes
IPVS use libnl : No
fwmark socket support : Yes
Use VRRP Framework : Yes
Use VRRP VMAC : Yes
SNMP support : No
SHA1 support : No
Use Debug flags : No

make && make install

整理管理文件:
ln -s /usr/local/keepalived/sbin/keepalived /usr/sbin/
ln -s /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
ln -s /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/
至此keepalived安装完毕
注册为服务并设置开机启动
chkconfig –add keepalived
chkconfig keepalived on
service keepalived start

ps -ef | grep keepalived
root 1332 1908 0 09:32 pts/0 00:00:00 grep keepalived
root 1977 1 0 Nov20 ? 00:00:02 keepalived -D
root 1978 1977 0 Nov20 ? 00:00:02 keepalived -D
root 1979 1977 0 Nov20 ? 00:00:17 keepalived -D
redis服务启动后会有3个进程。

首先,在Master上创建如下配置文件:
mkdir -p /etc/keepalived/scripts/
vim /etc/keepalived/keepalived.conf

vrrp_script chk_redis {
script “/etc/keepalived/scripts/redis_check.sh” ###监控脚本
interval 2 ###监控时间
}
vrrp_instance VI_1 {
state MASTER ###设置为MASTER
interface eth0 ###监控网卡
virtual_router_id 51
priority 101 ###权重值
authentication {
auth_type PASS ###加密
auth_pass redis ###密码
}
track_script {
chk_redis ###执行上面定义的chk_redis
}
virtual_ipaddress {
172.27.4.250 ###VIP
}
notify_master /etc/keepalived/scripts/redis_master.sh
notify_backup /etc/keepalived/scripts/redis_backup.sh
notify_fault /etc/keepalived/scripts/redis_fault.sh
notify_stop /etc/keepalived/scripts/redis_stop.sh

然后,在Slave上创建如下配置文件:

mkdir -p /etc/keepalived/scripts/
vim /etc/keepalived/keepalived.conf

vrrp_script chk_redis {
script “/etc/keepalived/scripts/redis_check.sh” ###监控脚本
interval 2 ###监控时间
}
vrrp_instance VI_1 {
state BACKUP ###设置为BACKUP
interface eth0 ###监控网卡
virtual_router_id 51
priority 100 ###比MASTRE权重值低
authentication {
auth_type PASS
auth_pass redis ###密码与MASTRE相同
}
track_script {
chk_redis ###执行上面定义的chk_redis
}
virtual_ipaddress {
172.27.4.250 ###VIP
}
notify_master /etc/keepalived/scripts/redis_master.sh
notify_backup /etc/keepalived/scripts/redis_backup.sh
notify_fault /etc/keepalived/scripts/redis_fault.sh
notify_stop /etc/keepalived/scripts/redis_stop.sh
}

在Master和Slave上创建监控Redis的脚本:
vim /etc/keepalived/scripts/redis_check.sh
#!/bin/bash

ALIVE=`/opt/redis/bin/redis-cli PING`
if [ “$ALIVE” == “PONG” ]; then
echo $ALIVE
exit 0
else
echo $ALIVE
exit 1
fi

编写以下负责运作的关键脚本:
notify_master /etc/keepalived/scripts/redis_master.sh
notify_backup /etc/keepalived/scripts/redis_backup.sh
notify_fault /etc/keepalived/scripts/redis_fault.sh
notify_stop /etc/keepalived/scripts/redis_stop.sh

因为Keepalived在转换状态时会依照状态来呼叫:
当进入Master状态时会呼叫notify_master
当进入Backup状态时会呼叫notify_backup
当发现异常情况时进入Fault状态呼叫notify_fault
当Keepalived程序终止时则呼叫notify_stop

首先,在Redis Master上创建notity_master与notify_backup脚本:

vim /etc/keepalived/scripts/redis_master.sh
#!/bin/bash

REDISCLI=”/usr/local/redis2.8.9/bin/redis-cli”
LOGFILE=”/var/log/keepalived-redis-state.log”

echo “[master]” >> $LOGFILE
date >> $LOGFILE
echo “Being master….” >> $LOGFILE 2>&1

echo “Run SLAVEOF cmd …” >> $LOGFILE
$REDISCLI SLAVEOF 172.27.4.39 6379 >> $LOGFILE 2>&1
sleep 10
#延迟10秒以后待数据同步完成后再取消同步状态

echo “Run SLAVEOF NO ONE cmd …” >> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1

vim /etc/keepalived/scripts/redis_backup.sh

#!/bin/bash

REDISCLI=”/usr/local/redis2.8.9/bin/redis-cli”
LOGFILE=”/var/log/keepalived-redis-state.log”

echo “[backup]” >> $LOGFILE
date >> $LOGFILE
echo “Being slave….” >> $LOGFILE 2>&1

sleep 15
#延迟15秒待数据被对方同步完成之后再切换主从角色
echo “Run SLAVEOF cmd …” >> $LOGFILE
$REDISCLI SLAVEOF 172.27.4.39 6379 >> $LOGFILE 2>&1

接着,在Redis Slave上创建notity_master与notify_backup脚本:

vim /etc/keepalived/scripts/redis_master.sh

#!/bin/bash

REDISCLI=”/usr/local/redis2.8.9/bin/redis-cli”
LOGFILE=”/var/log/keepalived-redis-state.log”

echo “[master]” >> $LOGFILE
date >> $LOGFILE
echo “Being master….” >> $LOGFILE 2>&1

echo “Run SLAVEOF cmd …” >> $LOGFILE
$REDISCLI SLAVEOF 172.27.4.38 6379 >> $LOGFILE 2>&1
sleep 10
#延迟10秒以后待数据同步完成后再取消同步状态

echo “Run SLAVEOF NO ONE cmd …” >> $LOGFILE
$REDISCLI SLAVEOF NO ONE >> $LOGFILE 2>&1

vim /etc/keepalived/scripts/redis_backup.sh

#!/bin/bash

REDISCLI=”/usr/local/redis2.8.9/bin/redis-cli”
LOGFILE=”/var/log/keepalived-redis-state.log”

echo “[backup]” >> $LOGFILE
date >> $LOGFILE
echo “Being slave….” >> $LOGFILE 2>&1

sleep 15
#延迟15秒待数据被对方同步完成之后再切换主从角色
echo “Run SLAVEOF cmd …” >> $LOGFILE
$REDISCLI SLAVEOF 172.27.4.38 6379 >> $LOGFILE 2>&1

然后在Master与Slave创建如下相同的脚本:

vim /etc/keepalived/scripts/redis_fault.sh
#!/bin/bash

LOGFILE=/var/log/keepalived-redis-state.log

echo “[fault]” >> $LOGFILE
date >> $LOGFILE

vim /etc/keepalived/scripts/redis_stop.sh
#!/bin/bash

LOGFILE=/var/log/keepalived-redis-state.log

echo “[stop]” >> $LOGFILE
date >> $LOGFILE

给脚本都加上可执行权限:
chmod +x /etc/keepalived/scripts/*.sh

脚本创建完成以后,我们开始按照如下流程进行测试:
1.启动Master上的Redis
/etc/init.d/redis restart

2.启动Slave上的Redis
/etc/init.d/redis restart

3.启动Master上的Keepalived
/etc/init.d/keepalived restart

4.启动Slave上的Keepalived
/etc/init.d/keepalived restart

5.尝试通过VIP连接Redis:
redis-cli -h 172.27.4.250 INFO

连接成功,Slave也连接上来了。
role:master
slave0:172.27.4.39,6379,online

6.尝试插入一些数据:
redis-cli -h 172.27.4.250 SET Hello Redis
OK

从VIP读取数据
redis-cli -h 172.27.4.250GET Hello
“Redis”

从Master读取数据
redis-cli -h 172.27.4.38 GET Hello
“Redis”

从Slave读取数据
redis-cli -h 172.27.4.39 GET Hello
“Redis”

下面,模拟故障产生:
将Master上的Redis进程杀死:
killall -9 redis-server
或者redis-cli shutdown

查看Master上的Keepalived日志
tail -f /var/log/keepalived-redis-state.log
[fault]
Thu Sep 27 08:29:01 CST 2012

同时Slave上的日志显示:
tailf /var/log/keepalived-redis-state.log
[master]
Fri Sep 28 14:14:09 CST 2012
Being master….
Run SLAVEOF cmd …
OK
Run SLAVEOF NO ONE cmd …
OK

然后我们可以发现,Slave已经接管服务,并且担任Master的角色了。
redis-cli -h 172.27.4.250 INFO
redis-cli -h 172.27.4.39 INFO
role:master

然后我们恢复Master的Redis进程
/etc/init.d/redis start

查看Master上的Keepalived日志
tailf /var/log/keepalived-redis-state.log
[master]
Thu Sep 27 08:31:33 CST 2012
Being master….
Run SLAVEOF cmd …
OK
Run SLAVEOF NO ONE cmd …
OK

同时Slave上的日志显示:
tailf /var/log/keepalived-redis-state.log
[backup]
Fri Sep 28 14:16:37 CST 2012
Being slave….
Run SLAVEOF cmd …
OK

可以发现目前的Master已经再次恢复了Master的角色,故障切换以及自动恢复都成功了

Mysql删除某字段中有重复数据的行

有一次业务需求变更,需要将web_user_mobile_time_limit中的mobile字段值设置为唯一。
原有表结构如下:
mysql> desc web_user_mobile_time_limit;
+———-+————-+——+—–+——————-+—————-+
| Field | Type | Null | Key | Default | Extra |
+———-+————-+——+—–+——————-+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| mobile | varchar(20) | YES | | NULL | |
| dateTime | timestamp | YES | | CURRENT_TIMESTAMP | |
+———-+————-+——+—–+——————-+—————-+

如果mobile字段没有重复数据,可以执行下一行语句修改表结构:
Alter table web_user_mobile_time_limit add unique(mobile);

由于现有表的mobile字段的数据已经有一些是重复的。
mysql> select * from web_user_mobile_time_limit;
+—-+————-+———————+
| id | mobile | dateTime |
+—-+————-+———————+
| 1 | 13800138000 | 2014-09-23 20:18:17 |
| 2 | 13800138000 | 2014-09-23 20:18:57 |
| 3 | 13800138001 | 2014-09-23 20:19:04 |
| 4 | 13800138000 | 2014-09-23 20:19:18 |
| 5 | 13800138001 | 2014-09-23 20:19:22 |
| 6 | 13800138002 | 2014-09-23 20:19:25 |
| 7 | 13800138001 | 2014-09-23 20:19:29 |
| 8 | 13800138002 | 2014-09-23 20:19:34 |
| 9 | 13800138003 | 2014-09-23 20:19:39 |
| 10 | 13800138004 | 2014-09-23 20:19:43 |
| 11 | 13800138004 | 2014-09-23 20:19:46 |
+—-+————-+———————+
11 rows in set

在修改表结构的过会提示如下错误:
mysql> Alter table web_user_mobile_time_limit add unique(mobile);
ERROR 1062 : Duplicate entry ‘13800138000’ for key ‘mobile’

业务部门的需求是直接删除掉重复数据中更新时间较晚的数据,执行方法是这样:
delete from a using web_user_mobile_time_limit as a,web_user_mobile_time_limit as b where a.id>b.id and a.mobile = b.mobile;
如果想保留较旧的数据则执行:
delete from a using web_user_mobile_time_limit as a,web_user_mobile_time_limit as b where a.id<b.id and a.mobile = b.mobile;

再次查看结果如下:
mysql> select * from web_user_mobile_time_limit;
+—-+————-+———————+
| id | mobile | dateTime |
+—-+————-+———————+
| 1 | 13800138000 | 2014-09-23 20:18:17 |
| 3 | 13800138001 | 2014-09-23 20:19:04 |
| 6 | 13800138002 | 2014-09-23 20:19:25 |
| 9 | 13800138003 | 2014-09-23 20:19:39 |
| 10 | 13800138004 | 2014-09-23 20:19:43 |
+—-+————-+———————+
5 rows in set
至此,重复数据已经删除,现在修改表结构就不会报错了;

mysql> Alter table web_user_mobile_time_limit add unique(mobile);
mysql> desc web_user_mobile_time_limit;
+———-+————-+——+—–+——————-+—————-+
| Field | Type | Null | Key | Default | Extra |
+———-+————-+——+—–+——————-+—————-+
| id | int(11) | NO | PRI | NULL | auto_increment |
| mobile | varchar(20) | YES | UNI | NULL | |
| dateTime | timestamp | YES | | CURRENT_TIMESTAMP | |
+———-+————-+——+—–+——————-+—————-+
3 rows in set

MySQL服务器合并

不中断web服务的前提下合并多个MySQL服务器到单独的一台服务器上

 

目标描述:
公司内部生产环境中有多个MySQL服务器系统配置过剩、管理复杂,计划将三台MySQL数据库服务器A1(A2从)、B1(B2从)、C1(C2从)合并到配置稍高Mysql服务器D1(D2从)上。前提要求是不中断web服务,不能丢失数据。
由于A1、B1、C1的数据库database_name并不重复,数据引擎为INNODB,合并的难度就小了很多。
A1中有DBA1、DBA2、DBA3,IP 172.16.1.101
B1中有DBB1,IP 172.16.1.102
C1中有DBC1,IP 172.16.1.103
D1 IP 172.16.1.104

合并思路:
1、在D1上创建账号并赋予权限;A1、B1、C1创建同步账号(曾创建)、开启二进制文件(曾开启)
2、设置D1为A1的从库:A1数据,导入D1、设置同步,切换web代码中的数据库连接文件。
3、导出B1数据,导入到D1中,重设D1为B1的从库,同步完成后切换web代码中数据库连接文件。
4、同理导出C1数据,导入到D1中,重设D1为C1的从库,同步完成后切换web代码中数据库连接文件。
5、重设D1为主服务器,创建同步账号,导出D1数据库并导入到D2,设置主从和备份。

mysqldump关键参数:
–master-data=2:记录数据导出时的数据库使用的日志文件名和position
–single-transaction:导出时不锁表
–databases/–B:同时导出多个数据库
具体释义请man mysqldump

实施过程(本例数据库名、IP、权限等已替换为非无意义单词,仅展示原理和步骤):

1、在D1服务器安装同版本的MySQL数据库,并修改MySQL的配置文件/etc/my.cnf,本例中直接在A1复制/etc/my.cnf,并修改server-id=value中value不和A1、B1、C1冲突
2、确认A1、B1、C1中存在同步用的账号,并已赋予同步权限,以A1为例:
mysql> show databases;
+——————–+
| Database |
+——————–+
| information_schema |
| DBA1 |
| DBA2 |
| DBA13 |
| mysql |
| performance_schema |
+——————–+
6 rows in set (0.00 sec)

mysql> select user,host from mysql.user;
+———–+————–+
| user | host |
+———–+————–+
| root | 127.0.0.1 |
| sync_user | 172.16.1.% |
| DBAU1 | 172.16.1.1 |
| root | localhost |
+———–+————–+
4 rows in set (0.00 sec)
mysql> show grants for ‘sync_user’@’172.16.1.%’;
+——————————————————————————————————————————-+
| Grants for sync_user@172.16.1.% |
+——————————————————————————————————————————-+
| GRANT REPLICATION SLAVE ON *.* TO ‘sync_user’@’172.16.1.%’ IDENTIFIED BY PASSWORD ‘*****************************************’ |
+——————————————————————————————————————————-+
1 row in set (0.00 sec)

GRANT REPLICATION SLAVE [ … ] 说明已经赋予sync_user账号在172.16.1.%的同步权限。

3、在D1服务器上创建DBAU1账号和权限与A1服务器的权限相同。
在A1服务器执行下列语句查看DBAU1的账号权限:
mysql> show grants for ‘DBAU1’@’172.16.1.1’;
+—————————————————————————————————————–+
| Grants for DBAU1@172.16.1.19 |
+—————————————————————————————————————–+
| GRANT USAGE ON *.* TO ‘DBAU1’@’172.16.1.19’ IDENTIFIED BY PASSWORD ‘*****************************************’ |
| GRANT ALL PRIVILEGES ON `DBA1`.`hl_city_exchange_record` TO ‘DBAU1’@’172.16.1.1’ WITH GRANT OPTION |
| GRANT ALL PRIVILEGES ON `DBA2`.`hl_community_detail` TO ‘DBAU1’@’172.16.1.1’ WITH GRANT OPTION |
| GRANT ALL PRIVILEGES ON `DBA3`.`hl_city_price_trend` TO ‘DBAU1’@’172.16.1.1’ WITH GRANT OPTION |
+—————————————————————————————————————–+
4 rows in set (0.00 sec)
在D1中执行如下命令赋权限:
mysql>GRANT ALL PRIVILEGES ON `DBA1`.`hl_city_exchange_record` TO ‘DBAU1’@’172.16.1.1’ IDENTIFIED BY ‘YOUR PASSWORD’ WITH GRANT OPTION;
mysql>GRANT ALL PRIVILEGES ON `DBA2`.`hl_community_detail` TO ‘DBAU1’@’172.16.1.1’ WITH GRANT OPTION;
mysql>GRANT ALL PRIVILEGES ON `DBA3`.`hl_city_price_trend` TO ‘DBAU1’@’172.16.1.1′ WITH GRANT OPTION;

4、导出A1中的DBA1、DBA2、DBA3数据库,并修改导出时使用的二进制文件和日志位置;
shell>mysqldump -uroot -p –master-data=2 –single-transaction –database DBA1 DBA2 DBA3 >A1_DBA123.sql

将数据文件传送到D1上:
scp A1_DBA123.sql 172.17.1.104L/root/

–master-data=2:参数值为2时日志文件和日志位置时被注释的状态,需要根据实际情况修改
查找记录位置的行并增加主库信息:
cat -n A1_DBA123.sql | grep “CHANGE MASTER TO”,找到如下
22 — MASTER_LOG_FILE=’mysql-bin.000044′, MASTER_LOG_POS=627859448; 修改为下行(n为行数,file和pos值每个文件不同)
shell> sed -i “N,22a\CHANGE MASTER TO MASTER_HOST=’172.16.1.101′, MASTER_USER=’sync_user’, MASTER_PASSWORD=’SYNC USER PASSWORD’, MASTER_LOG_FILE=’mysql-bin.000044′, MASTER_LOG_POS=627859448;”
导入数据:在D1上执行下行中的命令将修改后的SQL导入到D1中。
shell> mysql -uroot -p <A1_DBA123.sql

然后开启D1的slave.
mysql>start slave;
之后做一些必要的检查:
mysql>show slave status\G;

[ … ]
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
[ … ]
Seconds_Behind_Master: 0
[ … ]

显示中如果包含以上三列代码值为YES、YES、0则说明同步成功,如果不为0需要等待从库同步完毕。

5、确认数据库同步成功后修改web代码中数据库连接模块,将数据库IP指向D1服务器,并观察数据运行情况;
查看mysql运行状态
mysql> show processlist;
至此A1数据库已无缝迁移完毕。
测试部门确认页面访问正常后停掉D1的slave(后续导入B1数据库时会提示停掉slave)
mysql> stop slave;

6、B1数据库导入的情况大体一致,根据步骤2、3、4、5做一些相应修改后导入到D1中,做必要检查数据库前已完毕后切换web代码中数据库连接模块。
7、C1数据库导入的情况大体一致,根据步骤2、3、4、5做一些相应修改后导入到D1中,做必要检查数据库前已完毕后切换web代码中数据库连接模块。

8、数据库导入完毕后等测试部门确认前台无误。进行下一步。

9、重设D1为主库;
重设D1为主库;
mysql> reset master;
(服务器端执行reset master会重设master,并清理掉作为master的二进制日志)
mysql> quit;
重启数据库
shell> service mysqld restart
测试查看D1的slave状态如下:
mysql> show slave status\G;
Empty set (0.00 sec)

ERROR:
No query specified
10、导出D1的所有数据库,并设置D2为D1的从库;
shell>mysqldump -uroot -p –master-data=2 –single-transaction –all-databases >D1_all-databases.sql
查找记录位置的行并增加主库信息:
cat -n D1_all-databases.sql | grep “CHANGE MASTER TO”,找到如下
22 — MASTER_LOG_FILE=’mysql-bin.00001′, MASTER_LOG_POS=627859448; 修改为下行(n为行数,file和pos值每个文件不同)
shell> sed -i “N,22a\CHANGE MASTER TO MASTER_HOST=’172.16.1.101′, MASTER_USER=’sync_user’, MASTER_PASSWORD=’SYNC USER PASSWORD’, MASTER_LOG_FILE=’mysql-bin.000001′, MASTER_LOG_POS=627859448;”
(“N,22a…” 此中22为行数,和cat中的行数对应)
导入数据:在D2上执行下行中的命令将修改后的SQL导入到D2中。
然后开启D2的作为从库:
shell> start slave;
并做一些必要的检查。

至此,三台MySQL数据库服务器A1(A2从)、B1(B2从)、C1(C2从)成功合并到D1(D2从)。

在D1上设置备份(略写)
shell>echo “password of backup user” > backup.pass
shell> chown 600 backup.pass
这样设置目的是只有当前用户能够读取到backup_user的密码。
备份的关键部分是:
mysqldump -uroot -p`cat backup.pass` –master-data –single-transaction –all-databases > BackupDate`date +%Y%m%d-%H%M%S`.sql
也可以扩展比如清理mtime大于30天的文件等。不再详述。