公司新业务, 希望支持第三方开发者开发插件, 并实现插件代码的托管. 开发者可以上传代码, 审核通过后自动做资源分配, 代码发布等.
支撑公司内部开发人员和第三方开发者也需要关注更多的内容.
- 安全风险, 对于未知的第三方开发者. 要做好资源隔离并避免污染.
- 保证业务连续性.
- 自动实现代码发布, 支持回退/指定tag发布, 减少人工成本.
- 方便查看第三方应用产生的日志.
- 支持区分测试环境/生产环境.
业务上也需要支持框架, 如权限鉴定/一套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等实现.
整个代码/服务托管的运维方案这样就转起来了.