副标
Dockerize your Python Application
dockerfile python example
dockerfile python flask example

flask框架为例

Dockerfile

FROM alpine:latest

# 打标签
LABEL version="1.0" \
    description="alpine:latest" \
    maintainer="wwek<licoolgo@gmail.com>"

# 配置apk包加速镜像为阿里
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

# 安装一些基础包
RUN apk update \
    && apk upgrade \
    && apk add s6 \
    && apk add bash \
    # && apk add nghttp2-dev \
    && apk add ca-certificates \
    && apk add wget \
    # && apk add curl \
    # && apk add tcpdump \
    # && apk add bash-completion \
    && apk add iputils \
    && apk add iproute2 \
    && apk add libc6-compat \
    && apk add -U tzdata \
    && rm -rf /var/cache/apk/*

# 设置 操作系统时区
RUN rm -rf /etc/localtime \
 && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

# 设置时区变量
ENV TIME_ZONE Asia/Shanghai

# 安装 python3、升级pip、setuptools
RUN apk add --no-cache python3 \
    #&& apk add --no-cache python3-dev \
    && python3 -m ensurepip \
    && rm -r /usr/lib/python*/ensurepip \
    && pip3 install --default-timeout=100 --no-cache-dir --upgrade pip \
    && pip3 install --default-timeout=100 --no-cache-dir --upgrade setuptools \
    && if [ ! -e /usr/bin/pip ]; then ln -s pip3 /usr/bin/pip ; fi \
    && if [[ ! -e /usr/bin/python ]]; then ln -sf /usr/bin/python3 /usr/bin/python; fi \
    && rm -rf /var/cache/apk/* \
    && rm -rf ~/.cache/pip

# 设置 语言支持
ENV LANG=C.UTF-8

# 配置 应用工作目录
WORKDIR /data/apps/appdir

# 增加 项目文件
ADD appmain.py ./
ADD 你的py文件2.py ./
ADD 目录1 ./
ADD requirements.txt ./

# 安装 项目依赖包
RUN pip install -r requirements.txt

# 配置 对外端口
EXPOSE 11000

# 设置启动时预期的命令参数, 可以被 docker run 的参数覆盖掉.
CMD ["python", "appmain.py"]

前言

不管是PHP各种框架的日志
还是java的 Java Stack Traces
他们都是多行日志模式
这个时候如果使用
filebeat + elasticsearch + kibana 做日志的采集,存储,展示那么就需要用到
filebeat的 multiline 配置
一词日志是多行记录,其实这种多行的日志会有日志分段的分隔符
写出对于的分隔符正则即可解决filebeat采集一次日志记录为多行的情况

配置示例

例如我们有日志文件

/var/www/a.iamle.com/runtime/log/201904/01.log
/var/www/b.iamle.com/Application/Runtime/Logs/201904/02.log
/var/www/c.iamle.com/runtime/log/201905/03.log
/var/www/d.iamle.com/Application/Runtime/Logs/201906/04.log

分析日志后得出某一次日志通过什么标识作为分隔符

# 标识方式1 63个"-"作为分割符 正则为 ^\-{63}
# ...
---------------------------------------------------------------
2019-04-08T13:58:19+08:00 192.168.0.1 GET a.iamle.com/v1/1?type_id=1
[运行时间:0.582681s] [吞吐率:1.72req/s] [内存消耗:3,387.72kb] [文件加载:80]
[ err ] [ LANG ] /var/releases/balabala/20181227-101450/fw/lang/zh-cn.php
[ err ] [ ROUTE ] array (
  'rule' => 'v1',
  'route' => 'index/v1/1',
---------------------------------------------------------------
2019-04-08T13:58:19+08:00 192.168.0.1 GET b.iamle.com/v1/1?type_id=1
[运行时间:0.582681s] [吞吐率:1.72req/s] [内存消耗:3,387.72kb] [文件加载:80]
[ err ] [ LANG ] /var/releases/balabala/20181227-101450/fw/lang/zh-cn.php
[ err ] [ ROUTE ] array (
  'rule' => 'v1',
  'route' => 'index/v1/1',
---------------------------------------------------------------
# ...

# 标识方式2 "[ 2019 作为分隔符 正则为 ^\[\s{1}\d{4}
# ...

[ 2019-04-09T13:58:19+08:00 ] 192.168.0.1 GET a.iamle.com/v2
[运行时间:0.681s] [吞吐率:1.72req/s] [内存消耗:3,387.72kb] [文件加载:82]
[ err ] [ LANG ] /var/releases/balabala/20191227-101450/fw/lang/zh-cn.php
[ err ] [ ROUTE ] array (
  'rule' => 'v2',
  'route' => 'index/v2',

[ 2019-04-09T13:58:19+08:00 ] 192.168.0.1 GET b.iamle.com/v2/1
[运行时间:0.681s] [吞吐率:1.72req/s] [内存消耗:3,387.72kb] [文件加载:81]
[ err ] [ LANG ] /var/releases/balabala/20191227-101450/fw/lang/zh-cn.php
[ err ] [ ROUTE ] array (
  'rule' => 'v2',
  'route' => 'index/v2',
# ...

filebeat中配置文件
/etc/filebeat/filebeat.yml

# ...
- type: log
  enabled: true
  paths:
    - /var/www/*.iamle.com/runtime/log/*/*.log
    - /var/www/*.iamle.com/Application/Runtime/Logs/*/*.log
  # 正则需要支持2种不同的分隔符
  multiline.pattern: '^(\-{63}|\[\s{1}\d{4})'
  multiline.negate: true
  multiline.match: after

# ...
# 检查filebeat是否配置正确
 filebeat test config
# 重启filebeat
 systemctl restart filebeat

写正则的时候推荐用正则可视化工具检查正则
例如本文的正则

扩展

这种非自己能定义的日志,思路是寻找多行日志分段规律写出对于的正则即可
那么自己打日志的情况下,不太推荐这种多行日志了,采用结构化的json日志,一行一条是比较推荐的方式
这样在后期的处理分析中将会便利太多太多

参考

Filebeat Reference [7.0] » Configuring Filebeat » Manage multiline messages

1. 前言

云端开发环境 云端开发环境安装 搭建云ide 远程调试开发
一直想建立一个云端开发环境
有以下尝试,以及目前用的最终方案
仅供参考

2. 常见云端IDE环境

2.1 SSH+VIM

此乃一苇渡江,vim高手首选方案
但是对于vim只停留在基础的配置文件修改水平,习惯了vscode/Jetbrains全家桶后就不太适应这种需要较高学习和配置曲线的方案了
有人说你可以用一些预制vim配置,实际用起来你还是需要去熟悉和调整的,要花大把时间的

2.2 AWS Cloud9

Cloud9官网
可以用AWS的云端服务,也可以私有部署
如果为了环境干净可以自己打docker镜像
哪有本地IDE工具功能强大啊
img

2.3 CodingIDE(Cloud Studio)

Cloud Studio官网
Cloud Studio – 开启云端开发模式WebIDE
可用云端服务,目前已经卖给腾讯云了,也可以私有部署(暂不清楚开源版还在更新没),功能上弱于AWS Cloud9
哪有本地IDE工具功能强大啊
img

2.4 code-server

code-server官网
这货是vscode的服务端运行版本
使用基本和本地vscode保持一致,功能上强于 Cloud9和CodingIDE
当然自己也可以打docker镜像使用,不过打docker镜像有个弊端,就是开发环境变更问题
虽然开发环境几乎不会这么变,但是终归还是会有变动的,如果用docker的方式,每次变更都去同步修改Dockfile打镜像还是烦的
img

2.5 Linux Desktop + VNC + noVNC

选一个自己喜欢的Linux桌面发行版,装VNC server,再装一个noVNC让VNC在web浏览器中可以访问
Linux Desktop 我选的 Kali,他基于debian定制的, 主打安全并集成了较多安全方面的工具
图形化选的Xfce
和本地开发完全一致,想用什么IDE用什么IDE
img

3. 结论

对vim非常熟悉那么随便一个vps/vm虚拟机就开干
有条件开虚拟机的建议开vm虚拟机用Linux桌面版本
不能自己开虚拟机但是有vps/vm虚拟机用的可以装 code-server 用
喜欢让系统干干净净的处女座也不怕麻烦可以把code-server、Cloud9等打docker镜像用

没有最好,只有最适合,满足自己需求即可
个人最终选的方案5

4. 扩展

另外一个知识点,docker中是可以用桌面版的,这个大多数人都不知道
例如实验楼这个网站中的实验环境其实就是docker中运行的Linux桌面
搜索关键字“docker linux desktop”获取跟多的信息
要用docker中的Linux桌面版,可以采用VNC + noVNC的方式

前言

Caddy 是一款用golang编写的轻量级web server

老早就看到caddy web server,但是找不到去用的理由,因为nginx足够好
如果用docker打包应用,如果要实现跑php php-fpm应用且要单容器,用nginx就不太爽了
一般用php官方docker镜像作为base,然后在上面再装nginx,nginx复杂的编译环境非常麻烦
试用了下 caddy,他就没那么麻烦,还是用php官方docker镜像,然后只需要简单的几行就可以把caddy加进去
非常特别的一点是caddy自带php-fpm的进程启动
在配置文件中增加on startup php-fpm —nodaemonize 即可
容器的入口只需要启动caddy即可,caddy可以自动启动php-fpm进程
目前发现唯一的问题是caddy对个人免费,对公司和商业收费,价格还非常贵
下面是一些参考文件

Dockerfile 参考

FROM php:7.2-fpm-alpine

LABEL version="7.2" \
    description="php-fpm7.2 alpine" \
    maintainer="wwek<licoolgo@gmail.com>"

# 国内源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
# 系统基础包
RUN apk update \
    && apk upgrade \
    # && apk add s6 \
    && apk add bash \
    # && apk add openssl \
    # && apk add openssl-dev \
    && apk add ca-certificates \
    #&& apk add wget \
    && apk add curl \
    #&& apk add tcpdump \
    && apk add iputils \
    && apk add iproute2 \
    && apk add libc6-compat \
    && apk add zlib \
    && apk add -U tzdata \
    && apk add libpng \
    && apk add libpng-dev \
    #&& apk add libstdc++ \
    && rm -rf /var/cache/apk/*

# 系统设置(设置时区)
RUN rm -rf /etc/localtime \
    && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" /etc/timezone

# --- PHP --- #
## PHP Core核心扩展
RUN docker-php-ext-install opcache mysqli pdo_mysql mbstring zip pcntl gd
## 三方扩展
## redis
RUN set -xe \
    #&& apk add --no-cache --update --virtual .phpize-deps $PHPIZE_DEPS \
    && apk add --update --virtual .phpize-deps $PHPIZE_DEPS \
    && pecl install -o -f redis  \
    #&& pecl install -o -f swoole  \
    && echo "extension=redis.so" > /usr/local/etc/php/conf.d/redis.ini \
    #&& echo "extension=swoole.so" > /usr/local/etc/php/conf.d/swoole.ini \
    && rm -rf /usr/share/php \
    && rm -rf /tmp/* \
    && apk del  .phpize-deps


# 增加 PHP 配置文件
ADD docker/www.conf /usr/local/etc/php-fpm.d/
ADD docker/php.ini /usr/local/etc/php

##  PHP设置
# 设置php运行权限为母鸡中 www的用户和组 1000:1000
# RUN addgroup -g 1000 -S www && \
#     adduser -u 1000 -S www -G www www

# 设置php-fpm运行账户和组为 www www
# RUN set -ex \
#     && cd /usr/local/etc \
#     && { \
#         echo 'listen.owner = www'; \
#         echo 'listen.group = www'; \
#         echo 'listen.mode = 0660'; \
#     } | tee php-fpm.d/www.conf

# -- Caddy -- #
## 安装 Caddy Web Server
#RUN curl -sSL -o- "https://caddyserver.com/download/linux/amd64?plugins=http.jwt,http.login,http.prometheus,http.realip,http.restic,http.expires&license=personal&telemetry=off" | tar -xvz -C /usr/bin/
RUN curl --silent --show-error --fail --location \
    --header "Accept: application/tar+gzip, application/x-gzip, application/octet-stream" -o - \
    "https://caddyserver.com/download/linux/amd64?plugins=http.jwt,http.login,http.prometheus,http.realip,http.restic,http.expires&license=personal&telemetry=off" \
    | tar --no-same-owner -C /usr/bin/ -xz caddy \
    && chmod 0755 /usr/bin/caddy \
    && /usr/bin/caddy -version

## 增加 Caddy 配置文件
ADD docker/Caddyfile /etc/Caddyfile


# -- APP -- #
## APP环境变量
ENV APPROOT="/data/wwwroot/www.iamle.com/" \
    PHP_MYSQL_HOSTNAME='' \
    PHP_MYSQL_DATABASE='' \
    PHP_MYSQL_USERNAME='' \
    PHP_MYSQL_PASSWORD='' \
    PHP_MYSQL_PORT='' \
    PHP_REDIS_HOST=''

## APP增加
RUN mkdir -p $APPROOT
COPY ./ $APPROOT


## APP配置
WORKDIR $APPROOT
# 巴拉巴拉 APP 配置修改


## APP 权限
## APP设置文件权限 这里的82为php-fpm容器中的 www-data的 usreid 和groupid
RUN chown -R www-data:www-data $APPROOT
#RUN chown -R 82:82 $APPROOT

# 入口
CMD ["/usr/bin/caddy", "--conf", "/etc/Caddyfile", "--log", "stdout"]

Caddyfile 参考

0.0.0.0:80
log stdout
errors stdout

root /data/wwwroot/www.iamle.com/public/

fastcgi / 127.0.0.1:9000 php {
    ext   .php
    split .php
    index index.php
}

rewrite {
    to {path} {path}/ /index.php?s={uri}
}

on startup php-fpm --nodaemonize

www.conf

[www]
prefix = /var/
user = www-data
group = www-data
listen = 127.0.0.1:9000
;listen.backlog = 511
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
;listen.acl_users =
;listen.acl_groups =
;listen.allowed_clients = 127.0.0.1

pm = dynamic
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
pm.max_children = 100
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 20
pm.min_spare_servers = 20
pm.max_spare_servers = 100
;pm.process_idle_timeout = 10s;
pm.max_requests = 50000
;pm.status_path = /status

;ping.path = /ping
;ping.response = pong

;access.log = log/$pool.access.log

;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"

slowlog = log/$pool.slow.log

request_slowlog_timeout = 30

;request_terminate_timeout = 0

;rlimit_files = 1024

;rlimit_core = 0

;chroot =

;chdir = /var/www

catch_workers_output = yes

;clear_env = no

;security.limit_extensions = .php .php3 .php4 .php5 .php7

;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp


;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
;php_admin_value[error_log] = /var/log/fpm-php.www.log
;php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 32M

其他

在dockerfile中,是直接下载的caddy二进制程序,这个会受到caddy的授权协议影响,对个人免费,对商业收费
如果是基于源码自己编译安装那么就只需要遵守Apache license协议即可,即可以用于商业

常见应用框架配置参考
https://github.com/caddyserver/examples

cady缺省(占位符)变量
https://caddyserver.com/docs/placeholders

1. 前言

filebeat 6.6.2 nginx module自定义字段
filebeat提供了多种Module预制模块,简化了各种日志的格式化
在nginx中默认的字段并不满足实际需求,例如我们需要记录额外的Nginx字段
例如 请求时间、后端响应时间、主机头等信息
那么在filebeat的nginx module中需要同步定义

2. rpm模式安装filebeat

curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.6.2-x86_64.rpm
sudo rpm -vi filebeat-6.6.2-x86_64.rpm

3. Nginx 自定义Log格式化

nginx原始log格式化定义:

        #log format
        log_format access '$remote_addr - $remote_user [$time_local] "$request" '
                            '$status $body_bytes_sent "$http_referer" '
                            '"$http_user_agent" "$http_x_forwarded_for" '

nginx自定义格式化log:

        #log format
        log_format access '$remote_addr - $remote_user [$time_local] "$request" '
                            '$status $body_bytes_sent "$http_referer" '
                            '"$http_user_agent" "$http_x_forwarded_for" '
                            '"$http_x_real_ip" "$server_addr" "$host" '
                            '$request_time $upstream_response_time "$upstream_addr" '
                            '"$time_iso8601"';

4. 启用filebeat的nginx module

执行

filebeat modules enable nginx

filebeat nginx module 增加字段
文件 /usr/share/filebeat/module/nginx/access/ingest/default.json 中
原始:

    "grok": {
      "field": "message",
      "patterns":[
        "\"?%{IP_LIST:nginx.access.remote_ip_list} - %{DATA:nginx.access.user_name} \\[%{HTTPDATE:nginx.access.time}\\] \"%{GREEDYDATA:nginx.access.info}\" %{NUMBER:nginx.access.response_code} %{NUMBER:nginx.access.body_sent.bytes} \"%{DATA:nginx.access.referrer}\" \"%{DATA:nginx.access.agent}\""
        ],
      "pattern_definitions": {
        "IP_LIST": "%{IP}(\"?,?\\s*%{IP})*"
      }

自定义修改:

    "grok": {
      "field": "message",
      "patterns":[
        "\"?%{IP_LIST:nginx.access.remote_ip_list} - %{DATA:nginx.access.user_name} \\[%{HTTPDATE:nginx.access.time}\\] \"%{GREEDYDATA:nginx.access.info}\" %{NUMBER:nginx.access.response_code} %{NUMBER:nginx.access.body_sent.bytes} \"%{DATA:nginx.access.referrer}\" \"%{DATA:nginx.access.agent}\" \"%{DATA:nginx.access.xff}\" \"%{DATA:nginx.access.x_real_ip}\" \"%{DATA:nginx.access.server_addr}\" \"%{DATA:nginx.access.host}\" %{DATA:nginx.access.request_time} %{DATA:nginx.access.upstream_response_time} \"%{DATA:nginx.access.upstream_addr}\" \"%{DATA:nginx.access.time_iso8601}\""
        ],
      "pattern_definitions": {
        "IP_LIST": "%{IP}(\"?,?\\s*%{IP})*"
      }

如果定制自己的字段
可以使用http://grokdebug.herokuapp.com/ 在线工具验证grok规则
可以使用Json工具处理转义问题

文件 /etc/filebeat/fields.yml 中
找到nginx字段配置

            - name: agent
              type: text
              description: >
                Contains the un-parsed user agent string. Only present if the user
                agent Elasticsearch plugin is not available or not used.

在后面增加

            - name: xff
              type: group
              description: >
                http_x_forwarded_for.
            - name: x_real_ip
              type: group
              description: >
                http_x_real_ip.
            - name: server_addr
              type: group
              description: >
                server_addr 服务器地址.
            - name: host
              type: group
              description: >
                host http_host http主机头.
            - name: request_time
              type: group
              description: >
                request_time 请求时间.
            - name: upstream_response_time
              type: group
              description: >
                upstream_response_time 后端响应时间.
            - name: upstream_addr
              type: group
              description: >
                upstream_addr 后端地址.
            - name: time_iso8601
              type: group
              description: >
                time_iso8601 iso8601格式时间.

5. Filebeta中 nginx 日志路径定义

在文件 /etc/filebeat/modules.d/nginx.yml 中修改日志路径

- module: nginx
  # Access logs
  access:
    enabled: true

    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths: ["/data/wwwlogs/*.log*"]

  # Error logs
  error:
    enabled: true

    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths: ["/usr/local/nginx/logs/error.log*"]

6. 扩展

6.1 解决增加字段后Kibana中不显示

  • 在Kibana的 Dev Tools中执行
DELETE _ingest/pipeline/filebeat-6.6.2-nginx-access-default
  • 字段出来以后有黄色小三角,刷新一下index 的字段缓存就好了
  • 重启下Filebeat
    方法来源

2018年微服务服务治理现状

目前就2种方案

1. 服务治理框架方案,基于业务代码侵入式的方案

单语言最全 Java体系的 Spring Cloud 全家桶套餐 优:成熟大规模验证 劣: 单语言,其他语言接入麻烦
支持多语言的 Tars(腾讯) 服务治理框架 优:腾讯十余年的服务治理经验的开源输出,框架本身完整 劣:只能在平台上玩,外部生产案例较少
其他语言自实现的,如PHP的Swoole方案,Golang的Gokit、GoMicro等 优: 自主可控,随意定制 劣: 需要基础架构团队来hold
其他的一些RPC框架,因为不是一套的服务治理框架这里不谈

本方案最大的劣势是业务代码侵入,要潜入一大堆业务无关的代码,维护客户端的成本也非常巨大

2. ServiceMesh服务网格方案,以Istio为代表的以SideCar(边车)流量代理的方案

Istio背后由IBM和Google共同支持,被誉为下一代的服务治理框架
全套解决方案,服务治理需要的功能统统都有,默认需要运行在Kubernetes上

宜人贷有部分业务已经线上使用 (宜人贷Kubernetes 容器云实践方案 https://mp.weixin.qq.com/s/Mn0TWdiuJSiYw5DlBlT61g)
支付宝公司蚂蚁金服也在持续探索中 (蚂蚁金服大规模微服务架构下的Service Mesh探索之路 http://www.servicemesher.com/blog/the-way-to-service-mesh-in-ant-financial/)
其他有实践的公司: 优信二手车

据公开的资料显示,唯品会,新浪微博都是采用SideCar(边车)流量代理的方式做的多语言接入
Sidecar就是ServiceMesh的雏形

本方案最大的优势是0业务代码侵入,现有业务项目可以直接接入,同时天下没有免费的午餐,最大的劣势是复杂,需要懂操作系统,懂网络,懂容器,懂Kubernetes
后续如果Istio在云上成熟,配套的管理工具成熟,能一定程度降低门槛,让业务开发在OPS上解放出来。

学习推荐

  • IBM 微讲堂 Istio 系列 https://developer.ibm.com/cn/os-academy-istio/
    目标听众:对微服务治理感兴趣的企业决策者、技术调研者、架构师、技术开发人员、运维人员等。
  • 免费的Istio在线实验平台 https://www.katacoda.com/courses/istio
  • 中文用户 ServiceMesher 社群 http://www.servicemesher.com/blog/