标签: 腾讯云

  • 使用Fluentd收集容器内PHP-FPM下Monolog的日志

    K8S回归Docker Compose

    接上回说到,从K8S回归到Docker Compose,有一块调整的比较大,就是日志。原来有点懒惰,腾讯云的TKE集群可以默认接入他的LogListener,这个日志组件可以很方便从Container里取日志,方便到你根本不用改造原来的应用。但现在由于离开了K8S,还是要提升下收集日志的效率,所以所有容器内的应用都会由init进程管理输出到stdout或stderr,再由容器接住,新版本docker已经支持--log-driver=fluentd来指定一个fluentd的后端来接受所有日志。

    日志规划流向

    应用日志

    Monolog写php://stderr → PHP-FPM接到error_log → init进程Supervisor接到/dev/stderr → Container接到日志 → Docker daemon设置了log-driver → 写到Fluentd → Fluentd处理后通过Kafka协议发到腾讯云CLS日志处理

    访问日志

    因为Traefik不支持FastCGI协议,所以FPM前面是挡了一层Nginx,访问日志都从Nginx取,类似应用日志,不同的是访问日志都写了stdout,这其实并没有关系,因为最后在Fluentd处理的时候还是会用正则匹配的

    STEP0: NGINX ACCESS_LOG 访问日志

    在nginx.conf的http段中配置JSON格式的字段,根据你的需要来,我建议加入request_id,方便关联应用日志。

    http {
      log_format mylog escape=json '{"time_local":"$time_iso8601",'
                               '"request_id":"$request_id",'
                               '"host":"$server_addr",'
                               '"clientip":"$remote_addr",'
                               '"size":$body_bytes_sent,'
                               '"request":"$request",'
                               '"request_body":"$request_body",'
                               '"responsetime":$request_time,'
                               '"upstreamtime":"$upstream_response_time",'
                               '"http_host":"$host",'
                               '"url":"$uri",'
                               '"domain":"$host",'
                               '"xff":"$http_x_forwarded_for",'
                               '"referer":"$http_referer",'
                               '"http_user_agent":"$http_user_agent",'
                               '"status":"$status"}';
      include mime.types;
      server_tokens off;
      tcp_nopush on;
      tcp_nodelay on;
      keepalive_timeout 120;
      types_hash_max_size 2048;
      client_max_body_size 100m;
      default_type application/octet-stream;
      access_log off;
      error_log off;
      gzip on;
      gzip_disable "msie6";
      include vhost/*.conf;
    }

    在具体的虚拟机中就可以配置你设置的json格式access log

    server {
        server_name symfony.tld;
        root /data/www/public;
    
        location / {
            # try to serve file directly, fallback to index.php
            try_files $uri /index.php$is_args$args;
        }
    
        location ~ ^/index\.php(/|$) {
            fastcgi_pass unix:/dev/shm/php-fpm.sock;
            fastcgi_split_path_info ^(.+\.php)(/.*)$;
            include fastcgi_params;
    
            fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
            fastcgi_param DOCUMENT_ROOT $realpath_root;
            fastcgi_param HTTPS off;
            fastcgi_param REQUEST_ID $request_id;
    
            internal;
        }
    
        # return 404 for all other php files not matching the front controller
        # this prevents access to other php files you don't want to be accessible.
        location ~ \.php$ {
            return 404;
        }
    
        error_log stderr;
        access_log /dev/stdout mylog;
    }

    STEP1: 改造APP LOG

    App的日志一般都会用主流的包可以选择,在这个例子中,我用的是Symfony原配的Monolog,这里可以根据自己的需要进行拦截。例如访问量小的时候,你可以把所有用户侧的异常抓下来看看,所以app这个pool里,level是info的我都抓了。

    在你的异常拦截里面加上日志,可以从$_SERVER['REQUEST_ID']取得你需要的nginx侧请求ID。

    $this->logger->info($exception->getMessage(),[你需要的其他数据]);

    mololog的配置

    monolog:
        handlers:
            app:
                level: info
                type: stream
                path: "php://stderr"
                channels: [ app ]
                formatter: 'monolog.formatter.json'
            main:
                type: fingers_crossed
                action_level: error
                handler: nested
                excluded_404s:
                    # regex: exclude all 404 errors from the logs
                    - ^/
            nested:
                type: stream
                path: "php://stderr"
                formatter: 'monolog.formatter.json'
            console:
                type:   console
                process_psr_3_messages: false
                channels: ["!event", "!doctrine"]

    STEP2: 给Supervisor加上参数

    记得给启动命令加上参数--force-stderr --nodaemonize,否则PHP-FPM的/proc/pid/fd/2不是pipe,参考之前文章PHP-FPM在Docker没有日志输出到/dev/stder

    [program:php-fpm]
    command=/usr/local/sbin/php-fpm --force-stderr --nodaemonize --fpm-config /usr/local/etc/php-fpm.d/www.conf
    autostart=true
    autorestart=true
    priority=5
    stdout_events_enabled=true
    stderr_events_enabled=true
    stdout_logfile=/dev/stdout
    stdout_logfile_maxbytes=0
    stderr_logfile=/dev/stderr
    stderr_logfile_maxbytes=0
    stopsignal=QUIT

    STEP3: 修改容器日志输出驱动格式

    普通容器可以在run的时候加上--log-driver=fluentd,如果是compose可以在配置里加上logging字段。

    version: '3'
    
    services:
      delta:
        image: ccr.ccs.tencentyun.com/zhouzhou/phpallinone:allinone-master-20220331
        ports:
          - "80:80"
        logging:
          driver: "fluentd"
          options:
            fluentd-address: localhost:24224
            tag: delta
    networks:
      default:
        driver: bridge

    STEP4: 配置Fluentd

    Fluentd可以做很多事情,当然其他日志收集器也可以,但Docker内置了Fluentd驱动,就用了它。我使用的场景很简单,单纯的在宿主机进行收集,简单的格式化处理,由于日质体量很小,所以发送到腾讯云CLS来处理。

    发送到腾讯云CLS需要Kafka协议,所以我们要做一个自己的Fluentd镜像,加上了kafka协议和兼容腾讯侧的rdkafka库,请注意,github官网的debian镜像是有问题的,例子用的是Alpine版本。

    FROM fluentd:v1.14.0-1.0
    
    # Use root account to use apk
    USER root
    
    # below RUN includes plugin as examples elasticsearch is not required
    # you may customize including plugins as you wish
    # 安装rdkafka和kafka插件
    RUN apk add --no-cache --update --virtual .build-deps \
            sudo build-base ruby-dev bash\
     && sudo gem install rdkafka  \
     && sudo gem install fluent-plugin-kafka \
     && sudo gem sources --clear-all \
     && apk del .build-deps \
     && rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem
    
    USER fluent
    
    # 覆盖一下
    ENTRYPOINT ["tini",  "--", "/bin/entrypoint.sh"]
    CMD ["fluentd"]

    配置这里说下思路,Fluentd的配置顺序简单来说是由上自下,并没有很强大的语法,利用本身的插件来完成一些其实本身很简单的操作,所以有点反直觉。我的容器中目前有访问日志和应用日志两类,我需要根据各自的特征发送到各自在腾讯云的主题进行分析。

    <source>
      @type forward
      port 24224
      bind 0.0.0.0
    </source>
    
    <match *>
      @type copy
      <store>
        @type relabel
        @label @PHPERROR
      </store>
      <store>
        @type relabel
        @label @NGINXACCESS
      </store>
    </match>
    
    <label @PHPERROR>
      <filter delta>
        @type grep
        <regexp>
          key log
          pattern \[pool ([^\]]*)\]
        </regexp>
      </filter>
    
      # 解析原始的PHP-FPM日志
      <filter delta>
        @type parser
        key_name log
        reserve_data true
        # reserve_data true
        # hash_value_field parsed
        <parse>
          @type regexp
          expression /^\[(?<logtime>[^\]]*)\] (?<level>(DEBUG|INFO|NOTICE|WARNING|ERROR|CRITICAL|ALERT|EMERGENCY)): \[pool (?<pool>[^\]]*)\] child (?<child>\d+) said into stderr: \"(?<message>.+)\"$/
        </parse>
      </filter>
    
      # 转json
      <filter delta>
        @type parser
        reserve_data true
        key_name message
        <parse>
          @type json
          time_key logtime
        </parse>
      </filter>
    
      <match delta>
        @type rdkafka2
    
        brokers sh-producer.cls.tencentcs.com:9096
        use_event_time true
    
        <format>
          @type json
        </format>
    
        <buffer topic>
          @type memory
          flush_interval 3s
        </buffer>
    
        # 腾讯云日志主题
        default_topic 9d7fc234-e090-4a48-8888-642f5b56e82c
    
        # producer settings
        required_acks -1
        rdkafka_delivery_handle_poll_timeout 5
    
        rdkafka_options {
          "security.protocol" : "SASL_PLAINTEXT",
          "sasl.mechanism" : "PLAIN",
          # 日志集主题
          "sasl.username" : "e63de52e-a012-45f6-8888-ca2dab7aeda1",
          "sasl.password" : "SecurityId#SecurityKey"
        }
      </match>
    
    </label>
    
    <label @NGINXACCESS>
      <filter delta>
        @type grep
        <regexp>
          key source
          pattern stdout
        </regexp>
      </filter>
    
      <filter delta>
        @type parser
        key_name log
        <parse>
          @type json
          # 指定nginx原始日志的事件作为事件事件
          time_key time_local
        </parse>
      </filter>
    
      <match delta>
        @type rdkafka2
    
        brokers sh-producer.cls.tencentcs.com:9096
        use_event_time true
    
        <format>
          @type json
        </format>
    
        <buffer topic>
          @type memory
          flush_interval 3s
        </buffer>
    
        # 腾讯云日志主题
        default_topic 0c0872c5-00b5-8888-b658-e88769118c30
    
        # producer settings
        required_acks -1
        rdkafka_delivery_handle_poll_timeout 5
    
        rdkafka_options {
          "security.protocol" : "SASL_PLAINTEXT",
          "sasl.mechanism" : "PLAIN",
          # 日志集主题
          "sasl.username" : "e63de52e-a012-8888-8932-ca2dab7aeda1",
          "sasl.password" : "SecurityId#SecurityKey"
        }
      </match>
    </label>

    以上配置简单的把单容器所有日志输出接住,先用relabel这个插件把日志复制出两股Label进行分别处理,这里有点反直觉。然后在不同的Label下进行json处理和投递工作,注意的是,PHP-FPM的Error LOG是不能定义格式的,所以需要正则匹配,然后把message部分进行json处理,注意使用reserve_data = true防止之前的key被覆写。最后记得腾讯云目前的Kafka环境必须用rdkafka库,SASL_PLAINTEXT密码的配置请注意!

    全部搞定

    请注意的时候,腾讯云的kafka可以走内网,域名和端口和外网的不同。你也可以自己做Elasticsearch和Kibana,组你自己的EFK

  • WordPress在cdn开启quic后有HTTPS跳转302循环的bug

    上海最近每天感染人数愈发多,没有控制住,说回正题,这两天把服务器重新弄了下。

    再重新部署Wordpress的时候,数据库都是以前的,但登陆界面302无限loop循环。寻找原因发现,官方在hub上的说明写的很清楚Using a Reverse Proxy,但是这里的示范代码有一个bug,

    if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)

    这段代码判断了前面扔过来的HTTP_X_FORWARDED_PROTO头部,但是我用的腾讯云开启了quic之后,回传过来的“quic”,所以导致无限302跳转,所以只有去关闭quic,或者修改代码让$_SERVER['HTTPS'] = 'on'就能解决问题了!

  • 撤销了Serverless回到容器版本的wordpress

    今天撤回WP回到自己的K8S上面,发现腾讯云的DOCKER HUB官方同步镜像停止更新了,需要自己把官方的wordpress镜像搬到CCR腾讯的个人版本容器镜像服务才能继续使用。拉到本地,打了个TAG,推到了CCR,重新部署完毕,是的,又好了。

  • 腾讯云CLB负载均衡大涨价,Serverless仍然是纸上谈兵。

    年底了,腾讯云突然给CLB负载均衡涨价了,要知道一个问题,当你的服务全部容器化之后,暴露出的服务最好走的是ingress,而ingress是和负载均衡的功能绑定的,使用量最小单位是一个k8s命名空间使用一个(曾经腾讯的ingress是只能绑定一个证书,现在可以绑多个了)。这样带来的结果是,你的费用会突然大幅度的提高,尤其当你划分某些辅助用的业务在一个独立的namespace里方便管理,而单独提供此命名空间的clb费用可能比你单独租用轻服务器的费用还高,这样显然是极其不合理的!

    试图降低费用,我把zhouzhou.net的服务以serverless的方式从k8s中分离。体验之后发现,serverless在现阶段依然是纸上谈兵,通过api网关他视图提供一个web服务的标准生态,比如版本,路由,日志等等重要的基础功能。但是serverless的问题是,第一点,它对代码是入侵的,你的代码必须和你使用的IaaS云服务绑定,而各个供应商之间是没有标准的,这就导致你需要修改代码才能部署,虽然不入侵到业务层面,但完整的测试流程依然是让你头痛的,他可能适用于SaaS方式给客户部署业务的供应商,这样的成本会相对的低廉。第二点,serverless的生态即它的运行时环境其实不友好,你得到的环境都相对的不是最新的,是云商给你做的,可能和你开发环境有非常大的差异,当你发现有因为环境而导致出现问题自己却无法解决的时候,是难受的。第三点,以前有传统的web容器,所以我们有了CGI(通用网关接口)来和各个语言通信,现在由于web容器的服务被api网关全部代替了,那么里面跑的是什么呢?很不幸,以PHP为例,是以内置服务器方式跑,而这个模式在官方手册中不建议跑生产环境。当然这个问题k8s也会碰到,你的镜像大都有套娃,ingress里面套nginx,只是为了看上去平滑兼容,如果有一种通用容器接口,就能解决最后一公里的问题。

  • 现阶段腾讯云的容器服务TKE是最好生产环境的 理由

    TKE和腾讯云的资源结合很好,和自己的云服务器CVM配合完美,如果有钱也可以用黑石服务器。

    使用了TKE就不用关心Master节点了,因为完全被腾讯云托管了,换言之,不管你有几个node,你不用支付Master节点的费用。

    随着TencentHub这个DevOps半成品被腾讯自己收购的coding取代,你会发现TKE的镜像特别好用,因为大部分走了容器这条路之后,大部分公司尤其初创企业他没有特别多的构建步骤,TKE的镜像纯BUILD是完全可以满足你的,如果你要走完成的持续集成也不难。

    TKE的日志服务也很方便,可以走kafka,ES,或者你自己的CLS服务,如果是小项目直接cls的控制台操作操作就很方便,开箱即用。

    TKE的K8S版本更新还是比较快的,尤其是Master节点被托管了,升级没有什么压力,如果node升级失败,移出集群重新加回来即可。

    我自己用了2年TKE,看着他一点点增强,还是非常满意的,即使遇到问题,腾云客服的响应速度也是杠杠滴!