Kubernetes 如何打赢容器之战?

阿里妹导读:Kubernetes 近几年很热门,在各大技术论坛上被炒的很火。它提供了强大的容器编排能力,与此同时 DevOps 的概念也来到大家身边,广大的开发同学也能简单地运维复杂的商业化分布式系统,打破了传统开发和运维之间的界限。本文会以初学者的视角,希望能让读者更好地理解 Kubernetes 出现的背景、超前的设计理念和优秀的技术架构。背景PaaSPaaS 技术,一句话概括就是:它提供了“应用托管”的能力。早期的主流做法基本上是租 AWS 或者 OpenStack 的虚拟机,然后把这些虚拟机当作物理机一样,用脚本或者手工的方式在上面部署应用。这个过程中如何保证本地环境和云端环境的一致性是一个很大的课题,而提供云计算服务的公司的核心竞争力就是比拼谁做的更好。从某种意义上来说 PaaS 的出现,算是一个比较好的解决方案。以 Cloud Foundry 为例,在虚拟机上部署上 Cloud Foundry 项目后,用户可以很方便地把自己的应用上云。以上帝视角来看这个过程:Cloud Foundry 最核心的是提供了一套应用的打包和分发机制,它为不同的编程语言定义了不同的打包格式,它能把可执行文件、启动参数等等一起打包成压缩包然后上传至 Cloud Foundry 存储中心,最后由调度器选择虚拟机,由虚拟机上的 Agent 下载并启动应用。分布式系统随着软件的规模越来越大,业务模式越来越复杂,用户量的上升、地区的分布、系统性能的苛刻要求都促成服务架构从最初的单体变成 SOA 再到如今的微服务,未来还可能演变为 Service Mesh ,Serverless 等等。如今,一个完整的后端系统不再是单体应用架构了,多年前的 DDD 概念重新回到大家的视线中。现在的系统被不同的职责和功能拆成多个服务,服务之间复杂的关系以及单机的单点性能瓶颈让部署和运维变得很复杂,所以部署和运维大型分布式系统的需求急迫待解决。容器技术前面提到诸如 Cloud Foundry 的 PaaS,用户必须为不同语言、不同框架区分不同的打包方式,这个打包过程是非常具有灾难性的。而现实往往更糟糕,当在本地跑的好好的应用,由于和远端环境的不一致,在打包后却需要在云端各种调试,最终才能让应用“平稳”运行。而 Docker 的出现改变了一切,它凭借镜像解决了这个问题。Docker 一不做二不休,干脆把完整的操作系统目录也打包进去,如此高的集成度,保证了云端和本地环境的高度一致,并且随时随地轻易地移植。谁也不知道就因为“镜像”这个简单的功能,Docker 完成了对 PaaS 的降维打击,占有了市场。此时,一些聪明的技术公司纷纷跟进 Docker,推出了自家的容器集群管理项目,并且称之为 CaaS。容器技术利用 Namespace 实现隔离,利用 Cgroups 实现限制;在 Docker 实现上,通过镜像,为容器提供完整的系统执行环境,并且通过 UnionFS 实现 Layer 的设计。Docker 容器是完全使用沙箱机制,相互之间不会有任何接口。通过 Docker,实现进程、网络、挂载点和文件隔离,更好地利用宿主机资源。Docker 强大到不需要关心宿主机的依赖,所有的一切都可以在镜像构建时完成,这也是 Docker 目前成为容器技术标准的原因。所以我们能看到在 Kubernetes 中默认使用 Docker 作为容器(也支持 rkt)。Kubernetes铺垫了这么多,终于说到本文的主角了。说 Kubernetes 之前,不得不提 Compose、Swarm、Machine 三剑客,其实在 Kubernetes 还未一统江湖之前,它们已经能实现大部分容器编排的能力了。但是在真正的大型系统上,它们却远远不如 Mesosphere 公司出品的大型集群管理系统,更别说之后的 Kubernetes 了。在容器化和微服务时代,服务越来越多,容器个数也越来越多。Docker 如它 Logo 所示一样,一只只鲸鱼在大海里自由地游荡,而 Kubernetes 就像一个掌舵的船长,带着它们,有序的管理它们,这个过程其实就是容器编排。Kubernetes 起源于 Google,很多设计都是源自于 Borg,是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes 的目标是让部署容器化的应用简单并且高效,并且提供了应用部署,规划,更新,维护的一种机制。小结至此,读者了解了 Kubernetes 的前世今生,由 PaaS 的火热,引爆了容器技术的战争,而赢得这场战争中最关键的即是拥有强大的容器编排的能力,而 Kubernetes 无疑是这场战争的胜利者。设计理念这一部分,我们会围绕 Kubernetes 的四个设计理念看看这些做法能给我们带来什么。声明式 VS 命令式声明式和命令式是截然不同的两种编程方式,在命令式 API 中,我们可以直接发出服务器要执行的命令,例如: “运行容器”、“停止容器”等;在声明式 API 中,我们声明系统要执行的操作,系统将不断向该状态驱动。我们常用的 SQL 就是一种声明式语言,告诉数据库想要的结果集,数据库会帮我们设计获取这个结果集的执行路径,并返回结果集。众所周知,使用 SQL 语言获取数据,要比自行编写处理过程去获取数据容易的多。apiVersion: extensions/v1beta1kind: Deploymentmetadata: name: etcd-operatorspec: replicas: 1 template: metadata: labels: name: etcd-operator spec: containers: - name: etcd-operator image: quay.io/coreos/etcd-operator:v0.2.1 env: - name: MY_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: MY_POD_NAME valueFrom: fieldRef: fieldPath: metadata.name我们来看看相同设计的 YAML,利用它,我们可以告诉 Kubernetes 最终想要的是什么,然后 Kubernetes 会完成目标。声明式 API 使系统更加健壮,在分布式系统中,任何组件都可能随时出现故障。当组件恢复时,需要弄清楚要做什么,使用命令式 API 时,处理起来就很棘手。但是使用声明式 API ,组件只需查看 API 服务器的当前状态,即可确定它需要执行的操作。显式的 APIKubernetes 是透明的,它没有隐藏的内部 API。换句话说 Kubernetes 系统内部用来交互的 API 和我们用来与 Kubernetes 交互的 API 相同。这样做的好处是,当 Kubernetes 默认的组件无法满足我们的需求时,我们可以利用已有的 API 实现我们自定义的特性。无侵入性感谢 Docker 容器技术的流行,使得 Kubernetes 为大家提供了无缝的使用方式。在容器化的时代,我们的应用达到镜像后,不需要改动就可以遨游在 Kubernetes 集群中。Kubernetes 还提供存储 Secret、Configuration 等包含但不局限于密码、证书、容器镜像信息、应用启动参数能力。如此,Kubernetes 以一种友好的方式将这些东西注入 Pod,减少了大家的工作量,而无需重写或者很大幅度改变原有的应用代码。有状态的移植在有状态的存储场景下,Kubernetes 如何做到对于服务和存储的分离呢?假设一个大型分布式系统使用了多家云厂商的存储方案,如何做到开发者无感于底层的存储技术体系,并且做到方便的移植?为了实现这一目标,Kubernetes 引入了 PersistentVolumeClaim(PVC)和 PersistentVolume(PV)API 对象。这些对象将存储实现与存储使用分离。PersistentVolumeClaim 对象用作用户以与实现无关的方式请求存储的方法,通过它来抹除对底层 PersistentVolume 的差异性。这样就使 Kubernetes 拥有了跨集群的移植能力。架构首先要提及的是 Kubernetes 使用很具代表性的 C/S 架构方式,Client 可以使用 kubectl 命令行或者 RESTful 接口与 Kubernetes 集群进行交互。下面这张图是从宏观上看 Kubernetes 的整体架构,每一个 Kubernetes 集群都由 Master 节点 和 很多的 Node 节点组成。MasterMaster 是 Kubernetes 集群的管理节点,负责管理集群,提供集群的资源数据访问入口。拥有 Etcd 存储服务,运行 API Server 进程,Controller Manager 服务进程及 Scheduler 服务进程,关联工作节点 Node。Kubernetes API Server 提供 HTTP Rest 接口的关键服务进程,是 Kubernetes 里所有资源的增、删、改、查等操作的唯一入口。也是集群控制的入口进程; Kubernetes Controller Manager 是 Kubernetes 所有资源对象的自动化控制中心,它驱使集群向着我们所需要的最终目的状态; Kubernetes Schedule 是负责 Pod 调度的进程。NodeNode 是 Kubernetes 集群架构中运行 Pod 的服务节点。Node 是 Kubernetes 集群操作的单元,用来承载被分配 Pod 的运行,是 Pod 运行的宿主机。关联 Master 管理节点,拥有名称和 IP、系统资源信息。运行 Docker Runtime、kubelet 和 kube-proxy。kubelet 负责对 Pod 对于的容器的创建、启停等任务,发送宿主机当前状态; kube-proxy 实现 Kubernetes Service 的通信与负载均衡机制的重要组件; Docker Runtime 负责本机容器的创建和管理工作。实现原理为了尽可能地让读者能明白 Kubernetes 是如何运作的,这里不会涉及到具体的细节实现,如有读者感兴趣可以自行参阅官网文档。这里以一个简单的应用部署示例来阐述一些概念和原理。创建 Kubernetes 集群介绍架构的时候我们知道,Kubernetes 集群由 Master 和 Node 组成。Master 管理集群的所有行为例如:应用调度、改变应用的状态,扩缩容,更新/降级应用等。Node 可以是是一个虚拟机或者物理机,它是应用的“逻辑主机”,每一个 Node 拥有一个 Kubelet,Kubelet 负责管理 Node 节点与 Master 节点的交互,同时 Node 还需要有容器操作的能力,比如 Docker 或者 rkt。理论上来说,一个 Kubernetes 为了应对生产环境的流量,最少部署3个 Node 节点。当我们需要在 Kubernetes 上部署应用时,我们告诉 Master 节点,Master 会调度容器跑在合适的 Node 节点上。我们可以使用 Minikube 在本地搭一个单 Node 的 Kubernetes 集群。部署应用当创建好一个 Kubernetes 集群后,就可以把容器化的应用跑在上面了。我们需要创建一个 Deployment,它会告诉 Kubernetes Master 如何去创建应用,也可以来更新应用。当应用实例创建后,Deployment 会不断地观察这些实例,如果 Node 上的 Pod 挂了,Deployment 会自动创建新的实例并且替换它。相比传统脚本运维的方式,这种方式更加优雅。我们能通过 kubectl 命令或者 YAML 文件来创建 Deployment,在创建的时候需要指定应用镜像和要跑的实例个数,之后 Kubernetes 会自动帮我们处理。查看 Pods 和 Nodes下面来介绍下 Pod 和 Node:当我们创建好 Deployment 的时候,Kubernetes 会自动创建 Pod 来承载应用实例。Pod 是一个抽象的概念,像一个“逻辑主机”,它代表一组应用容器的集合,这些应用容器共享资源,包括存储,网络和相同的内部集群 IP。任何一个 Pod 都需要跑在一个 Node 节点上。Node 是一个“虚拟机器”,它可以是虚拟机也可以是物理机,一个 Node 可以有多个 Pods,Kubernetes 会自动调度 Pod 到合适的 Node 上。Service 与 LabelSelectorPods 终有一死,也就是说 Pods 也有自己的生命周期,当一个 Pod 挂了的时候,ReplicaSet 会创建新的,并且调度到合适的 Node 节点上。考虑下访问的问题,Pod 替换伴随着 IP 的变化,对于访问者来说,变化的 IP 是合理的;并且当有多个 Pod 节点时,如何 SLB 访问也是个问题,Service 就是为了解决这些问题的。Service 是一个抽象的概念,它定义了一组逻辑 Pods,并且提供访问它们的策略。和其他对象一样,Service 也能通过 kubectl 或者 YAML 创建。Service 定义的 Pod 可以写在 LabelSelector 选项中(下文会介绍),也存在不指定 Pods 的情况,这种比较复杂,感兴趣的读者可以自行查阅资料。Service 有以下几种类型:ClusterIP(默认):在集群中内部IP上暴露服务,此类型使Service只能从群集中访问;NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 :,可以从集群的外部访问一个 NodePort 服务;LoadBalancer:使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务;ExternalName:通过返回 CNAME 和它的值,(适用于外部 DNS 的场景)Labels 和 Selectors 能够让 Kubernetes 拥有逻辑运算的能力,有点像 SQL。举个例子:可以查找 app=hello_word 的所有对象,也可以查找 app in (a,b,c) abc的所有对象。Labels是一个绑定在对象上的 K/V 结构,它可以在创建或者之后的时候的定义,在任何时候都可以改变。扩容应用前文提到我们可以使用 Deployment 增加实例个数,下图是原始的集群状态:我们可以随意的更改 replicas (实例个数)来扩容,当我们更改了 Deployment 中的 replicas 值时,Kubernetes 会自动帮我们达到想要的目标实例个数,如下图:更新应用更新应用和扩容类似,我们可以更改 Deployment 中的容器镜像,然后 Kubernetes 会帮住我们应用更新(蓝绿、金丝雀等方式),通过此功能,我们还可以实现切换应用环境、回滚、不停机 CI/CD。下面是部署的过程,需要注意的是我们可以指定新创建的 Pod 最大个数和不可用 Pod 最大个数:总结到了最后,大家对 Kubernetes 有个大概的了解了,但 Kubernetes 远远不止本文所介绍的这些内容。在云原生概念逐渐清晰的今天,Kubernetes 作为 CNCF 中一个接地气的落地项目,其重要性不言而喻。本文作者: 淘敏阅读原文本文来自云栖社区合作伙伴“ 阿里技术”,如需转载请联系原作者。 ...

March 28, 2019 · 3 min · jiezi

URLOS-0.4.0发布:推进中小企业网站安全防护升级

近日,广州市万岁云计算有限公司推出了更灵活更强大的URLOS-0.4.0新版本!产品新增了网站攻击防护、主机监控等功能,修复了已知问题以及强化了系统稳定性,进一步满足用户需求,提升用户体验。(图1:攻击防护)URLOS是由万岁云创始团队独立自主研发,具有自主知识产权的Linux容器应用管理软件。其易安装、易使用、易交接,开箱即用,凭借容器级自我修复和弹性伸缩能力,可在10分钟内快速创建3节点网站容灾环境,为中小企业连续业务和数据资产安全保驾护航。(图2:主机监控)URLOS是一个应用容器管理软件,基于Docker容器技术打包和运行应用,可自动识别机器和云应用的故障并将云应用转移至可用的机器上,单机故障并不影响业务开展,配合云存储便可轻松搭建7x24小时持续运行的应用环境。(URLOS 0.4.0)URLOS 0.4.0.1的更新包括:新增网站攻击防御系统,可有效防御CC攻击等一系列常见网站功能方式;新增主机监控系统,可通过微信查看主机运行以及网站流量情况;修复若干已知问题,优化用户体验;URLOS安装命令:curl -SO https://www.urlos.com/install && chmod 544 install && ./installURLOS升级命令:curl -SO https://www.urlos.com/update && chmod 544 update && ./update温馨提示:由于存储算法升级,如果您的URLOS是从0.3.x升级到0.4.x版本,请在升级后登录URLOS重新填写已安装的数据库服务的密码并暴力部署一次!《攻击防护开启教程》https://www.urlos.com/center-…《主机监控开启教程》https://www.urlos.com/center-…

March 28, 2019 · 1 min · jiezi

docker jenkins gitlab 自动部署NodeJs项目 及 env node not found 解决

一、Jenkins配置1.安装NodeJS Plugin 在插件管理界面 搜索Node 找到NodeJS,安装、重启,成功后如下图:2.配置NodeJS Server 在全局工具配置中,如下配置:二、项目配置选择"构建一个自由软件风格的项目" ,配置如下:1.配置git项2.Build Environment3.Build配置项其中echo $PATH 、which node、 node -v、npm -v 可选,只是打印一下信息4.Build后的操作执行的命令,根据自己情况自由发挥三、遇到问题“env node not found” 遇到这个问题,jenkins一直无法打包。 找到解决问题的过程很曲折,这里直接贴结果:https://stackoverflow.com/que…在第二个回到中:$ docker exec -u 0 -it jenkins-1 bashbash-4.3# apk add –no-cache nodejsbash-4.3# node –versionv6.9.5bash-4.3# npm –version5.6.0其实就是 进入docker的命令行,然后执行apk add –no-cache nodejs ,自己手动安装nodejs , 问题就解决了。

March 27, 2019 · 1 min · jiezi

Docker 入门

安装 Docker For WindowsDocker For Windows 仅支持window 10(64位) 专业版、企业版、教育版或者更高版本双击Docker Desktop for Windows Installer.exe以运行安装程序。如果您尚未下载安装程序(Docker Desktop Installer.exe),则可以从download.docker.com获取 。安装后Docker不会自动启动。要启动它,请搜索Docker,在搜索结果中选择Docker Desktop for Windows,然后单击它(或按Enter键)。安装过程可能会多次自动重启,当桌面右下角的 Docker Desktop is starting 变成 Docker Desktop is running 时表示安装完成。当状态栏中的鲸鱼保持稳定时,Docker正在运行,并且可以从任何终端窗口访问。如果鲸鱼隐藏在通知区域中,请单击任务栏上的向上箭头以显示它。参考:Install Docker Desktop for Windows

March 27, 2019 · 1 min · jiezi

Windows 7 使用docker需要注意的地方

安装的是Docker toolbox不是Docker DesktopDocker Toolbox是通过创建virtualBox虚拟机实现的,虚拟机的虚拟磁盘默认在C:\Users\用户名.docker下,如果你系统盘剩余空间不多的话,后面使用会出错。可以使用virtualBox里的虚拟介质管理移动到其他盘使用Docker Quickstart Terminal运行服务后就可以关掉它了(当然直接在这里使用也可以,但是我在这个命令行不能复制和粘贴),然后在需要运行命令的地方打开git bash就可以如果你的docker-compose文件夹不在C:\Users\用户名.docker下,那你需要去virtualBox设置共享文件夹。如路径为D:\docker,共享文件夹名称为d/docker,勾上固定挂载和自动分配。关闭虚拟机后,再次使用Docker Quickstart Terminal启动就可以。docker的端口映射只是映射到虚拟机上,需要设置端口转发后才可以访问。把虚拟机的80端口转发到127.0.0.2上,这样使用127.0.0.2就能访问,也不会占用本机80端口。总结:最好用win10的docker desktop,估计就没有那么多问题。只要记住win7的docker是跑在virtualBox的虚拟机下,很多问题就可以解决了。

March 26, 2019 · 1 min · jiezi

docker搭建php+nginx+swoole+mysql+redis环境

操作系统:阿里云esc实例centos7.4软件:docker-ce version 18.09.3, docker-compose version 1.23.2一.创建带有swoole-redis-pdo_mysql-gd扩展的docker image1.创建dockerfile文件vim dockerfile2.在dockerfile文件写入From php:7.1-fpmRUN apt-get update && apt-get install -y \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev &amp;& docker-php-ext-install -j$(nproc) iconv &amp;& docker-php-ext-configure gd –with-freetype-dir=/usr/include/ –with-jpeg-dir=/usr/include/ &amp;& docker-php-ext-install -j$(nproc) gd &amp;& docker-php-ext-configure pdo_mysql &amp;& docker-php-ext-install pdo_mysql &amp;& pecl install redis-4.3.0 &amp;& pecl install swoole &amp;& docker-php-ext-enable redis swoole3.创建自定义的php镜像,主要不要漏了最后的 ‘.’,是指定当前目录构建镜像docker build -t myphp4 .运行指令,由于网络问题等,需要等比较长的时间,成功后会出现类似下面的代码…Build process completed successfullyInstalling ‘/usr/local/include/php/ext/swoole/config.h’Installing ‘/usr/local/lib/php/extensions/no-debug-non-zts-20160303/swoole.so’install ok: channel://pecl.php.net/swoole-4.3.1configuration option “php_ini” is not set to php.ini locationYou should add “extension=swoole.so” to php.iniRemoving intermediate container ad1420f7554f —> 2f2f332d73ceSuccessfully built 2f2f332d73ceSuccessfully tagged myphp4:latest至此docker 的自定义myphp4 image创建成功!二.创建docker-compose.yml文件mkdir pnsmrcd pnsmrvim docker-compose.yml写入下面代码version: ‘3.0’services:nginx: image: “nginx:latest” ports: - “10000:80” volumes: - /var/www/html:/usr/share/nginx/htmlphp-fpm: image: “myphp4” volumes: - /var/www/html:/usr/share/nginx/htmlmysql: image: “mysql:latest"redis: image: “redis:4.0"运行指令docker-compose up -d成功可以看到WARNING: The Docker Engine you’re using is running in swarm mode.Compose does not use swarm mode to deploy services to multiple nodes in a swarm. All containers will be scheduled on the current node.To deploy your application across the swarm, use docker stack deploy.Creating network “pnsmr_default” with the default driverCreating pnsmr_php-fpm_1 … doneCreating pnsmr_redis_1 … doneCreating pnsmr_mysql_1 … doneCreating pnsmr_nginx_1 … done至此,已开启nginx mysql redis php 服务三.修改各服务配置文件 1.浏览器输入 127.0.0.1:9998 #此处应输入你的服务器ip地址,可以看到下图 2.接下来要修改容器里nginx的配置文件,先使用指令查看各容器的docker IP地址docker inspect -f ‘{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}’ $(docker ps -aq)此指令可以查看所有用docker-compose 开启的容器的ip,结果类似下图,可以用对应的ip地址进行内部通讯3.复制nginx容器的配置文件出来,并修改替换,使nginx能解析phpdocker cp pnsmr_nginx_1:/etc/nginx/conf.d/default.conf nginx.confvim nginx.conf修改为下列代码server { listen 80; server_name localhost; #charset koi8-r; #access_log /var/log/nginx/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 172.24.0.3:9000;#此处需要填写你的php容器的docker内部通讯ip fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/$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; #}}docker cp nginx.conf pnsmr_nginx_1:/etc/nginx/conf.d/default.conf #将修改好的配置文件拷贝到容器里docker container stop pnsmr_nginx_1 docker container start pnsmr_nginx_1 #重启nginx容器使配置文件生效vim /var/www/html/index.php #在服务器本地目录新建 index.php 文件,输入<?php phpinfo(); 并保存vim /var/www/html/index.html #在服务器本地目录新建 index.html 文件,输出helloworld访问127.0.0.1:9998, html文件解析正常 访问127.0.0.1:9998/index.php,php文件解析正常 4.测试mysql,redis是否生效vim /var/www/html/redis.php #用于测试redis是否配置成功<?php$redis = new Redis();$redis->connect(“172.24.0.4”,6379);$redis->set(’test’,’this is a test for redis’);echo $redis->get(’test’);访问127.0.0.1:9998/redis.php,redis已生效进入mysql容器docker exec -it pnsmr_mysql_1 bash进入mysql并更改root用户密码创建测试文件vim /var/www/html/mysql.php<?php$pdo = new PDO(‘mysql:host=172.24.0.2;dbname=mysql;port=3306’,‘root’,‘root123’);var_dump($pdo);访问127.0.0.1:9998/mysql.php,mysql已生效四.总结 虽然环境是配置成功了,并可以用docker-compose up 指令一键生成,但是还要改各容器的配置文件,仍然不够方便,需要优化;另外docker的集群,堆栈功能也没用上,后面再继续学习. ...

March 25, 2019 · 2 min · jiezi

解决Windows10下无法对docker容器进行端口访问(端口映射的问题)

解决Windows10下无法对docker容器进行端口访问(端口映射的问题)在Windows10系统服务器中安装了docker和docker-compose并尝试在其中运行Nginx服务,映射也做好问题:在主机的浏览器中,打开localhost:port无法访问对应的Web服务。问题解析原因:docker是运行在Linux上的,在Windows中运行docker,实际上还是在Windows下先安装了一个Linux环境,然后在这个系统中运行的docker。也就是说,服务中使用的localhost指的是这个Linux环境的地址,而不是我们的宿主环境Windows10。解决办法启动docker命令行窗口输入命令docker-machine ip defaultLinux的ip地址,一般情况下这个地址是192.168.99.100然后在Windows的浏览器中,输入 http://IP:port 即可启用(http://192.168.99.100:8069)

March 19, 2019 · 1 min · jiezi

论文解读:Design patterns for container-based distributed systems

这是由Kubernetes创始人发表的论文,总结了基于容器的分布式系统的设计模式,让我们来一览究竟吧。论文认为,继OOP(面向对象编程)所引领的软件开发革命之后,如今似乎在分布式系统开发中也发生着一场相似的革命:基于容器化组件构建的微服务架构。容器的一大独特优势在于:良好的边界——恰好适合应用开发的隔离性。作者总结了一些设计模式,并且分为三大类型:Single-container management patterns容器的传统接口有run(), pause(), stop()可以有更丰富的接口“向上看”的视角:metrics, config, logs等,通常用HTTP+JSON来暴露“向下看”的视角:lifecycle(提供生命周期回调钩子), priority(为了空出资源给高优先级任务,甚至能提前关掉低优先级任务),“replicate yourself”(迅速创建一组相同的服务容器来应对突发流量)接下来两类都依赖Pod抽象(Kubernetes有提供),因为都涉及容器编排了,而且已进入Sevice Mesh这门新概念的范围。Single-node, multi-container application patterns多个容器组成一个原子单位Sidecar模式例如:web server + log collector前提是容器间能共享“存储卷”之类的资源顺带一提,为什么要用多容器,而非容器内多进程?资源审计和分配:这点很重要,虽然多进程也勉强能做,但多容器做得更成熟职责划分、解耦重用:如果一个容器包含多种进程,就笨重而难以重用了,小巧的单用途容器更适合重用故障隔离:重启一个容器,比修复容器内的故障进程要容易多了交付隔离:每个容器能独立地升级/降级Ambassador模式类似于OOP的proxy模式例如:memcache + twemproxy,这类模式是单结点的,因此twemproxy要和其中1个memcache部署到同一结点上前提是容器间能共享localhost网络接口Adapter模式例如:统一的metrics接口(JMX,statsd等统一收集到HTTP端点)可以免于修改已有的容器(因为已经以容器作为软件开发的单位了)Multi-node application patternsLeader election模式领导者选举这件事已经有很多库了,但往往对编程语言有所限定容器则对编程语言中立容器只需构建一次就能重用,高度贯彻了抽象和封装的原则Work queue模式传统的工作队列框架对编程语言有所限定实现了run(), mount()接口的容器,可作为“工作”的抽象基于此可打造一种通用的工作队列框架(可能是Kubernetes的未来方向)虽然没给例子,但有些类似Mesos的可插拔调度器架构Scatter/gather模式这样传递请求:client->root->serverroot结点把请求分发给很多servers,再把它们的响应汇总到一起,交给client例如:搜索引擎,分布式查询引擎多个leaf容器+1个merge容器,就能实现通用的scatter/gather框架(可能也是Kubernetes的未来方向)结语总之,论文将容器视为软件开发的一等公民,像OOP的对象一样重要,提倡单用途可组合可重用的容器。这似乎是对UNIX编程艺术的重申。

March 18, 2019 · 1 min · jiezi

使用Docker部署Nginx+Flask+Mongo的应用

使用Docker部署Nginx+Flask+Mongo的应用Nginx做为服务器,Mongo为数据库支持,Flask为Python语言的Web框架,利用Docker的容器特性,可以简单地部署在linux服务器上项目准备项目主要目录如下__ project-name |__ docker-file |__ ningx |__ Dockerfile |__ conf |__ nginx.conf |__ flask |__ Dockerfile |__ requirements.txt |__ mongo |__ Dockerfile |__ setup.sh |__ docker-compose.yml |__ src |__ app |__ … |__ run.py简要说明docker-file目录为docker部署的配置文件src目录为flask应用的python代码Docker的详细配置docker-compose配置version: ‘2.2’services: mongo: build: ./mongo volumes: - “./mongo/db:/data/db” restart: always ports: - “27017:27017” environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: 123456 flask: build: ./flask links: - mongo ports: - “5000:5000” expose: - “5000” volumes: - ../src:/home/web nginx: build: ./nginx links: - flask volumes: - “./nginx/log:/var/log/nginx” - “../:/usr/share/nginx/html” ports: - “80:80” - “8080:8080” - “443:443” restart: alwaysMongoDB的配置/mongo/Dockerfile的内容如下FROM mongo:3.6# 设置时区ENV TZ=Asia/ShanghaiRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone# 设置工作目录ENV WORKDIR /usr/local/workENV AUTO_RUN_DIR /docker-entrypoint-initdb.dENV INSTALL_MONGO_SHELL setup.shRUN mkdir -p $WORKDIR# 复制数据库的初始化命令COPY ./$INSTALL_MONGO_SHELL $AUTO_RUN_DIR/RUN chmod +x $AUTO_RUN_DIR/$INSTALL_MONGO_SHELL/mongo/setup.sh的内容如下该文件的目的是,启动MongoDB后创建一个密码为test的用户test,并赋予它数据库test的读写操作#!/bin/bashmongo <<EOFuse admin;db.auth(‘root’, ‘123456’);use dmx_aluminum;db.createUser({user:’test’,pwd:’test’,roles:[{role:‘readWrite’,db:’test’}]});db.createCollection(“user”);EOFFlask应用的配置/flask/Dockerfile的内容如下FROM python:3.6# 设置时区ENV TZ=Asia/ShanghaiRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone# 设置工作区RUN mkdir -p /home/webWORKDIR /home/web# 添加依赖ADD requirements.txt /home/web/requirements.txtRUN pip3 install -i https://pypi.douban.com/simple/ -r requirements.txt# 使用gunicorn启动应用CMD gunicorn -w 4 -b 0.0.0.0:5000 run:app/src/app/run.py的代码此处注释了调试用的 app.run(),发布时用gunicorn启动from app import create_appapp = create_app(‘default’)app.debug=False# if name == ‘main’:# app.run()Nginx的配置/nginx/Dockerfile的内容如下FROM nginx:1.14# 设置时区ENV TZ=Asia/ShanghaiRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone# 复制配置COPY conf/nginx.conf /etc/nginx/nginx.conf/nignx/conf/nginx.conf的内容如下user nginx;worker_processes 1;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid;events { worker_connections 1024;}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 gzip on; gzip_min_length 1k; gzip_buffers 4 16k; #gzip_http_version 1.0; gzip_comp_level 1; gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; gzip_vary off; gzip_disable “MSIE [1-6].”; server { listen 80; server_name localhost; keepalive_timeout 5; root /usr/share/nginx/html; location /static/ { alias /usr/share/nginx/html/src/app/static/; } location / { # checks for static file, if not found proxy to app try_files $uri @flask_app; } location @flask_app { proxy_pass http://192.168.0.2:5000; # 发布在阿里云上,应填写内网IP proxy_redirect off; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; #proxy_buffers 8 32k; #proxy_buffer_size 64k; } }}启动部署进入docker-flie目录 cd docker-flie启动docker docker-compose up查看容器状态 docker ps本地部署浏览器输入 127.0.0.1即可最后出现类似docker_file_nginx_1,docker_file_mongo_1, docker_file_flask_1的3个容器,说明成功!!!踩坑吐槽1 mongol容器中的初始化文件需要放在 docker-entrypoint-initdb.d 目录下本人做过如下尝试,会显示 mongdb未启动。ADD setup.sh /data/setup.shRUN chmod +x /data/setup.shCMD ["/data/setup.sh"]2 flask应用无法连接mongo,本文使用link方式。在数据库的配置应相应写成:MONGODB_SETTINGS = { ‘db’: ’test’, ‘host’: ‘mongo’, # 127.0.0.1 host地址一定要写你配置的–link的名字 ‘username’: ’test’, ‘password’: ’test’, ‘port’: 27017 }本地测试时改回127.0.0.13 nginx中配置使用的代理模式,其中执行flask应用的IP,应为内网IP ...

March 17, 2019 · 2 min · jiezi

Docker for windows教程

下载和安装第一步:下载docker安装包(链接:https://www.docker.com/get-started)第二步:安装,双击下载好的安装包安装完成开始入门进入开始页面,同时,打开一个cmd窗口,鉴于国内网络问题,后续拉取Docker镜像十分缓慢,需要配置国内镜像加速,在系统右下角托盘Docker 图标内右键菜单选择Settings,打开配置窗口后左侧导航菜单选择Daemon,在Registry mirrors 一栏中填写官方中国加速器地址https://registry.docker-cn.com ,之后点击Apply保存后Docker就会重启并应用配置的镜像地址了。查看docker版本,然后运行hello-world程序,如果出现下面的界面则表示docker已经安装完成常用命令删除镜像(image)docker rm image-name常见问题Error response from daemon: conflict: unable to delete fce289e99eb9 (must be forced) - image is being used by stopped container a11e5a20d7bb解决办法:强制删除 docker rmi -f image-id

March 15, 2019 · 1 min · jiezi

利用Serverless Kubernetes和Kaniko快速自动化构建容器镜像

摘要: 本文介绍了一种新的面向开发者的简单镜像构建实践,基于阿里云Serverless Kubernetes容器服务,可以自动化而且低成本的构建容器镜像,以便让开发者了解如何使用Serverless运行CI/CD和自动化任务。前言:在云原生时代中,容器镜像是一切应用分发的基础载体,除了dockerhub作为流行的镜像仓库外,各大公有云厂商也都提供了功能丰富镜像仓库服务,如ACR(Aliyun Container Registry), GCR(Goolge Container Registry),构建容器镜像已是所有开发者必须掌握的基础实践能力。无论开发者选择在本地使用docker完成基本的镜像构建,还是使用CI/CD服务(如Jenkins),本质上都是遵循“pull -> build -> push”的过程,完成镜像的构建、分发和同步等操作。本文介绍了一种新的面向开发者的简单镜像构建实践,基于阿里云Serverless Kubernetes容器服务,可以自动化而且低成本的构建容器镜像,以便让开发者了解如何使用Serverless运行CI/CD和自动化任务。why serverless kubernetes?容器镜像的构建是需要计算资源的,开发者在本地使用docker pull/build/push时,其计算资源是本地开发机器,如果开发者在传统kubernetes集群中部署Jenkins或Gitlab-runner服务,其计算资源也是需要持续运行。但是,容器镜像的构建基本属于高度动态的行为,往往是定时或者条件触发引起的操作,所以为了动态的构建操作而维护一个固定的计算资源池对成本是不利的。Serverless Kubernetes不同与传统基于节点的k8s集群,serverless集群中只有pod运行时才会收费,意味着只有在构建镜像时用户才需要付费,当构建结束时,也就停止计费。所以在成本上与传统k8s集群或ecs部署的方式相比显著减少。Kaniko在Serverless Kubernetes集群中,pod没有privileged权限,无法访问主机上的docker daemon,也就无法使用docker in docker方案进行镜像的操作,那么如何在kubernetes集群中不依赖宿主机的Docker情况下构建镜像呢?显然这是一个通用需求,社区也有了推荐的方案:kaniko。kaniko的工作原理与docker build类似,但是不依赖docker daemon,在pod中完成Dockfile各层的解析和build,这使得pod不需要privileged权限也可以完成镜像的pull/build/push。实践示例:定时同步容器镜像下面让我们使用Serverless Kubernetes和Kaniko实现一个简单的镜像build实验:定时同步镜像到国内ACR。步骤1: 创建Serverless Kubernetes集群登陆容器服务控制台, 5s即可完成Serverless集群创建。(如果是国外的源镜像仓库,可以选择美西区域)步骤2: 创建secret,配置ACR的用户名密码,用于推送镜像到ACR可登陆cloudshell操作如下命令。#docker login registry.cn-hangzhou.aliyuncs.com…#kubectl create secret generic regsecret –from-file=/root/.docker/config.json…步骤3: 创建定时任务CronJob在控制台选择模版创建如下定时任务,每小时同步busybox镜像到ACR。apiVersion: v1kind: ConfigMapmetadata: name: dockerfile-cmdata: Dockerfile: | FROM busybox:latest—apiVersion: batch/v1beta1kind: CronJobmetadata: name: kaniko-builderspec: schedule: “*/60 * * * *” jobTemplate: spec: template: spec: containers: - name: builder image: gcr.io/kaniko-project/executor:latest imagePullPolicy: Always args: - “–dockerfile=/configs/Dockerfile” - “–destination=registry.cn-hangzhou.aliyuncs.com/jovizhangwei/busybox:latest” volumeMounts: - name: dockerfile readOnly: true mountPath: “/configs/” - name: secrets readOnly: true mountPath: “/root/.docker/” volumes: - name: dockerfile configMap: name: dockerfile-cm - name: secrets secret: secretName: regsecret restartPolicy: OnFailure待job执行后,可查看ACR镜像仓库,确认镜像已同步。用户可以按照需求定制此模版文件,比如修改需要同步的镜像,添加build步骤等,也可以设置pod的资源限制(vcpu 0.25/0.5/1等), 以最小的计算成本完成同步任务。结束通过上面的示例,我们看到基于Serverless Kubernetes的按需付费特性,可以使用很低的成本运行一些定时和CI/CD任务,而不用维护一个固定的计算资源池,其同样适用于压力测试、数据计算、工作流处理等其他场景。Happy Hacking!更多参考信息Serverless Kubernetes快速部署jenkins环境及执行流水线构建: https://yq.aliyun.com/articles/685219kaniko intro:https://cloud.google.com/blog/products/gcp/introducing-kaniko-build-container-images-in-kubernetes-and-google-container-builder-even-without-root-access本文作者:贤维阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 15, 2019 · 1 min · jiezi

开源PaaS Rainbond V5.1发布,支持管理异构复杂的微服务架构

开源PaaS Rainbond V5.1发布,支持管理异构复杂的微服务架构3月的北京春意盎然,Rainbond如期为大家带来了大版本V5.1更新,在此首先对所有为Rainbon项目提过建议的用户朋友们表示感谢,V5.1版本的较多思考就是来自于众多用户为Rainbond项目提出的建议。Rainbond是开源的企业应用云操作系统,支撑企业应用的开发、架构、交付和运维的全流程,通过无侵入架构,无缝衔接各类企业应用,底层资源可以对接和管理IaaS、虚拟机和物理服务器。支持第三方微服务集成和管理Rainbond作为一款云应用操作系统开源产品,在众多的企业中落地使用的过程中出现了两类共同的问题:循序渐进的迁移策略,已经上Rainbond的服务如何与遗留服务通信和统一管理。Rainbond应用网关很好用,但是遗留的服务没办法与Rainbond上的服务共享外网端口或域名。Rainbond V5.1版本中在提出了第三方服务的概念,即将运行于Rainbond集群外且与Rainbond可以正常网络通信的服务称为第三方服务。对于此类服务,我们支持以静态注册、动态注册(Etcd、Zookeeper、Consule)的方式来获取第三方服务的通信地址,赋予第三方服务以下能力:集成Rainbond内置的ServiceMesh架构,与集群内服务无缝互联,并提供服务通信治理功能。集成Rainbond 应用网关,统一管理服务外网访问。运行于不同环境和系统的业务系统统一管理和可视化,形成完整业务架构。更多第三方服务的说明和支持情况,见文档: Rainbond支持第三方服务集成支持微服务启动顺序在一个复杂微服务架构下,一些服务必须依赖于另一些服务才能正常工作,如何根据依赖关系处理服务的启动顺序是简化复杂微服务架构管理的关键。Rainbond实现了,根据依赖关系自动处理启动顺序,当被依赖的服务正常工作后,才会启动后续服务,依次迭代启动所有服务。服务正常工作有几种判断依据:服务的运行状态处于“运行中”(默认)服务的端口处于监听状态(可设置)根据Http服务返回状态码(可设置)Service Mesh内置的服务代理更换为envoy内置服务代理更换为envoy 1.9.0内置Service Mesh控制面板重构,提供标准的XDS服务(gRPC)优化应用市场管理应用市场支持多版本安装和管理从应用市场安装服务,支持跨越大版本从应用市场升级源码构建系统升级基于源代码持续构建服务是Rainbond用户使用最多的功能之一,既5.0版本作较大升级以后,5.1版本继续带来升级:增加对NodeJS前端项目源码类型的支持,可以部署Vue和React。Java-Maven增加maven编译参数的UI配置。所有Java类型支持OpenJDK版本和OracleJDK版本的UI配置。PHP、静态语言支持UI选择中间件类型和版本。将公共代码模块和资源从云端本地化、更好的支持离线环境下源码构建支持服务源码类型重新检测和变更另外Rainbond对各类型源码的支持规范文档进行了更加细致的描述,请参考 Rainbond源码支持规范新版Rainbond文档系统上线团队一直在为Rainbond文档的可用性和完整性做努力,5.1版本发布之际,我们同时发布了5.1版本的文档系统,在平台基础使用、运维、各类使用场景等方面丰富了大量有价值内容。访问地址:https://www.rainbond.com/docs其他特性应用网关内置Openresty升级到1.13.6.2版本应用网关增加域名、服务访问情况监控,监控域名的访问量、延时、通信数据量团队首页改版,更多可视化管理默认安装的Docker版本升级到 18.06.3-ce默认安装的Kubernetes版本升级到 1.10.13安装流程支持指定分布式存储类型和配置参数,无需用户自行处理存储挂载Rainbond V5.1 安装:https://www.rainbond.com/docs…Rainbond 历史版本升级到V5.1https://www.rainbond.com/docs…

March 15, 2019 · 1 min · jiezi

initContainer 使用案例

将glusterfs存储同时挂载到initContainer和container的指定目录上,如:/var/data/在initContainer中拉取资源放到/var/data/,也就推到了分布式存储glusterfs上,如:wget -P /var/data/ http://127.0.0.1:8081/repository/k8s/kubectl/kubectl-v1.10.0-linux-amd64.tar.gz这样当业务容器启动后,就可以在指定目录/var/data下看到initContainer拉取到的资源在initContainer中,最好增加一部检测指定资源是否存在,以防重复拉取,如:if [ ! -f “/var/data/kubectl-v1.10.0-linux-amd64.tar.gz” ]; then wget -P /var/data/ http://127.0.0.1:8081/repository/k8s/kubectl/kubectl-v1.10.0-linux-amd64.tar.gz; fiapiVersion: apps/v1kind: Deploymentmetadata: name: mysqlspec: replicas: 1 selector: matchLabels: name: mysql template: metadata: labels: name: mysql spec: initContainers: - name: getresource image: busybox:v0.1.0 command: [‘sh’, ‘-c’, ‘wget -P /var/data/ http://127.0.0.1:8081/repository/k8s/kubectl/kubectl-v1.10.0-linux-amd64.tar.gz ‘] volumeMounts: - name: mysql-pvc mountPath: /var/data containers: - name: mysql image: percona:5.7.22 imagePullPolicy: Always ports: - containerPort: 3306 resources: limits: memory: “500Mi” cpu: “500m” requests: memory: “500Mi” cpu: “250m” env: - name: MYSQL_ROOT_PASSWORD value: “mysql” volumeMounts: - name: mysql-pvc mountPath: /var/data volumes: - name: mysql-pvc persistentVolumeClaim: claimName: mysql—apiVersion: v1kind: PersistentVolumeClaimmetadata: name: mysqlspec: accessModes: - ReadWriteMany resources: requests: storage: “5Gi” volumeName: storageClassName: glusterfs—kind: ServiceapiVersion: v1metadata: name: mysqlspec: type: ClusterIP ports: - name: mysql port: 3306 targetPort: 3306 protocol: TCP selector: name: mysql ...

March 13, 2019 · 1 min · jiezi

容器监控实践—Prometheus存储机制

概述Prometheus提供了本地存储,即tsdb时序数据库,本地存储给Prometheus带来了简单高效的使用体验,prometheus2.0以后压缩数据能力也得到了很大的提升。可以在单节点的情况下满足大部分用户的监控需求。但本地存储也限制了Prometheus的可扩展性,带来了数据持久化等一系列的问题。为了解决单节点存储的限制,prometheus没有自己实现集群存储,而是提供了远程读写的接口,让用户自己选择合适的时序数据库来实现prometheus的扩展性。Prometheus 1.x版本的TSDB(V2存储引擎)基于LevelDB,并且使用了和Facebook Gorilla一样的压缩算法,能够将16个字节的数据点压缩到平均1.37个字节。Prometheus 2.x版本引入了全新的V3存储引擎,提供了更高的写入和查询性能以下所有内容均基于prometheus2.7版本本地存储存储原理Prometheus按2小时一个block进行存储,每个block由一个目录组成,该目录里包含:一个或者多个chunk文件(保存timeseries数据)、一个metadata文件、一个index文件(通过metric name和labels查找timeseries数据在chunk文件的位置)。最新写入的数据保存在内存block中,达到2小时后写入磁盘。为了防止程序崩溃导致数据丢失,实现了WAL(write-ahead-log)机制,启动时会以写入日志(WAL)的方式来实现重播,从而恢复数据。删除数据时,删除条目会记录在独立的tombstone文件中,而不是立即从chunk文件删除。通过时间窗口的形式保存所有的样本数据,可以明显提高Prometheus的查询效率,当查询一段时间范围内的所有样本数据时,只需要简单的从落在该范围内的块中查询数据即可。这些2小时的block会在后台压缩成更大的block,数据压缩合并成更高level的block文件后删除低level的block文件。这个和leveldb、rocksdb等LSM树的思路一致。这些设计和Gorilla的设计高度相似,所以Prometheus几乎就是等于一个缓存TSDB。它本地存储的特点决定了它不能用于long-term数据存储,只能用于短期窗口的timeseries数据保存和查询,并且不具有高可用性(宕机会导致历史数据无法读取)。内存中的block数据未写入磁盘时,block目录下面主要保存wal文件:./data/01BKGV7JBM69T2G1BGBGM6KB12./data/01BKGV7JBM69T2G1BGBGM6KB12/meta.json./data/01BKGV7JBM69T2G1BGBGM6KB12/wal/000002./data/01BKGV7JBM69T2G1BGBGM6KB12/wal/000001持久化的block目录下wal文件被删除,timeseries数据保存在chunk文件里。index用于索引timeseries在wal文件里的位置。./data/01BKGV7JC0RY8A6MACW02A2PJD./data/01BKGV7JC0RY8A6MACW02A2PJD/meta.json./data/01BKGV7JC0RY8A6MACW02A2PJD/index./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks/000001./data/01BKGV7JC0RY8A6MACW02A2PJD/tombstones存储配置对于本地存储,prometheus提供了一些配置项,主要包括:–storage.tsdb.path: 存储数据的目录,默认为data/,如果要挂外部存储,可以指定该目录–storage.tsdb.retention.time: 数据过期清理时间,默认保存15天–storage.tsdb.retention.size: 实验性质,声明数据块的最大值,不包括wal文件,如512MB–storage.tsdb.retention: 已被废弃,改为使用storage.tsdb.retention.timePrometheus将所有当前使用的块保留在内存中。此外,它将最新使用的块保留在内存中,最大内存可以通过storage.local.memory-chunks标志配置。监测当前使用的内存量:prometheus_local_storage_memory_chunksprocess_resident_memory_bytes监测当前使用的存储指标:prometheus_local_storage_memory_series: 时间序列持有的内存当前块数量prometheus_local_storage_memory_chunks: 在内存中持久块的当前数量prometheus_local_storage_chunks_to_persist: 当前仍然需要持久化到磁盘的的内存块数量prometheus_local_storage_persistence_urgency_score: 紧急程度分数prometheus 2.0的存储升级prometheus 2.0于2017-11-08发布,主要是存储引擎进行了优化。性能的整体提高:与 Prometheus 1.8 相比,CPU使用率降低了 20% - 40%与 Prometheus 1.8 相比,磁盘空间使用率降低了 33% - 50%没有太多查询,平均负载的磁盘 I/O<1%在Kubernetes集群这样的动态环境中,prometheus的数据平面通常看起来是这种样式垂直维度表示所有存储的序列水平维度表示样本传播的时间如:requests_total{path="/status", method=“GET”, instance=“10.0.0.1:80”}requests_total{path="/status", method=“POST”, instance=“10.0.0.3:80”}requests_total{path="/", method=“GET”, instance=“10.0.0.2:80”}Prometheus定期为所有系列收集新数据点,这意味着它必须在时间轴的右端执行垂直写入。但是,在查询时,我们可能希望访问平面上任意区域的矩形(各种label条件)因此为了能够在大量数据中有效地查找查询序列,我们需要一个索引。在Prometheus 1.x存储层可以很好地处理垂直写入模式,但是随着规模增大,索引或出现一些问题,因此在2.0版本中重新设计了存储引擎和索引,主要改造是:样本压缩现有存储层的样本压缩功能在Prometheus的早期版本中发挥了重要作用。单个原始数据点占用16个字节的存储空间。但当普罗米修斯每秒收集数十万个数据点时,可以快速填满硬盘。但,同一系列中的样本往往非常相似,我们可以利用这一类样品(同样label)进行有效的压缩。批量压缩一系列的许多样本的块,在内存中,将每个数据点压缩到平均1.37字节的存储。这种压缩方案运行良好,也保留在新版本2存储层的设计中。具体压缩算法可以参考:Facebook的“Gorilla”论文中时间分片我们将新的存储层划分为块(block),每个块在一段时间内保存所有序列。每个块充当独立数据库。这样每次查询,仅检查所请求的时间范围内的块子集,查询执行时间自然会减少。这种布局也使删除旧数据变得非常容易(这在1.x的存储设计中是一个很耗时的操作)。但在2.x中,一旦块的时间范围完全落后于配置的保留边界,它就可以完全丢弃。索引一般prometheus的查询是把metric+label做关键字的,而且是很宽泛,完全用户自定义的字符,因此没办法使用常规的sql数据库,prometheus的存储层使用了全文检索中的倒排索引概念,将每个时间序列视为一个小文档。而metric和label对应的是文档中的单词。例如,requests_total{path="/status", method=“GET”, instance=“10.0.0.1:80”}是包含以下单词的文档:name=“requests_total"path="/status"method=“GET"instance=“10.0.0.1:80"基准测试cpu、内存、查询效率都比1.x版本得到了大幅度的提升具体测试结果参考:https://dzone.com/articles/pr…故障恢复如果您怀疑数据库中的损坏引起的问题,则可以通过使用storage.local.dirtyflag配置,来启动服务器来强制执行崩溃恢复。如果没有帮助,或者如果您只想删除现有的数据库,可以通过删除存储目录的内容轻松地启动:1.停止服务:stop prometheus.2.删除数据目录:rm -r <storage path>/*3.启动服务:start prometheus远程存储Prometheus默认是自己带有存储的,保存的时间为15天。但本地存储也意味着Prometheus无法持久化数据,无法存储大量历史数据,同时也无法灵活扩展。为了保证Prometheus的简单性,Prometheus并没有从自身集群的维度来解决这些问题,而是定义了两种接口,remote_write/remote_read,将数据抛出去,你自己处理。Prometheus的remote_storage 其实是一个adapter,至于在adapter的另一端是什么类型的时序数据库它根本不关心,如果你愿意,你也可以编写自己的adpater。如:存储的方式为:Prometheus —-发送数据—- > remote_storage_adapter —- 存储数据 —-> influxdb。prometheus通过下面两种方式来实现与其他的远端存储系统对接:Prometheus 按照标准的格式将metrics写到远端存储Prometheus 按照标准格式从远端的url来读取metrics远程读在远程读的流程当中,当用户发起查询请求后,Promthues将向remote_read中配置的URL发起查询请求(matchers,ranges),Adaptor根据请求条件从第三方存储服务中获取响应的数据。同时将数据转换为Promthues的原始样本数据返回给Prometheus Server。当获取到样本数据后,Promthues在本地使用PromQL对样本数据进行二次处理。远程写用户可以在Promtheus配置文件中指定Remote Write(远程写)的URL地址,一旦设置了该配置项,Prometheus将样本数据通过HTTP的形式发送给适配器(Adaptor)。而用户则可以在适配器中对接外部任意的服务。外部服务可以是真正的存储系统,公有云的存储服务,也可以是消息队列等任意形式。配置配置非常简单,只需要将对应的地址配置下就行remote_write: - url: “http://localhost:9201/write"remote_read: - url: “http://localhost:9201/read"社区支持现在社区已经实现了以下的远程存储方案AppOptics: writeChronix: writeCortex: read and writeCrateDB: read and writeElasticsearch: writeGnocchi: writeGraphite: writeInfluxDB: read and writeOpenTSDB: writePostgreSQL/TimescaleDB: read and writeSignalFx: write可以使用读写完整的InfluxDB,我们使用了多prometheus server同时远程读+写,验证了速度还是可以的。并且InfluxDB生态完整,自带了很多管理工具。容量规划在一般情况下,Prometheus中存储的每一个样本大概占用1-2字节大小。如果需要对Prometheus Server的本地磁盘空间做容量规划时,可以通过以下公式计算:磁盘大小 = 保留时间 * 每秒获取样本数 * 样本大小保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的情况下,如果想减少本地磁盘的容量需求,只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。因此有两种手段,一是减少时间序列的数量,二是增加采集样本的时间间隔。考虑到Prometheus会对时间序列进行压缩,因此减少时间序列的数量效果更明显。其他远程读写解决了Promtheus的数据持久化问题。使其可以进行弹性扩展。另外还支持联邦集群模式,用于解决横向扩展、网络分区的问题(如地域A+B+C的监控数据,统一汇总到D),联邦集群的配置将在后面的Promthues高可用文章中详细说明。附:kubecon2018上讲Prometheus 2.0的帅哥还有一本专门讲Prometheus的书:Prometheus: Up & Running(600多页…)国内没找到卖的,找到了一本英文pdf的,还在翻译理解中,有新的内容会继续同步在这个系列博客。。。又找到一本:https://www.prometheusbook.com/参考资料:https://prometheus.io/docs/pr…https://coreos.com/blog/prome...https://dzone.com/articles/pr...https://www.linuxidc.com/Linu...http://ylzheng.com/2018/03/06...https://www.cnblogs.com/vovli...https://files-cdn.cnblogs.com...https://www.bookstack.cn/read…本文为容器监控实践系列文章,完整内容见:container-monitor-book ...

March 12, 2019 · 1 min · jiezi

容器监控实践—Prometheus数据可视化

一. 概述Prometheus自带了一个web服务,包括一个默认的dashboard,可以使用表达式查询并进行图表可视化,默认服务的地址为:http://prometheus_ip:9090如下图:自带的web展示一般只用于表达式快速输入或者临时调试,因为默认服务没有鉴权,且图表表达能力有限,因此不会作为线上可视化方案,正式的监控数据可视化一般使用Grafana来配套prometheus可视化方案:自带web服务:在验证指标时是非常好用的,grafana虽然是作为可视化展示,但一般是先确认表达式,才去配置到grafana面板grafana可视化Console templates:官方给的一种选择,使用go templete来实现,使用难度较大,不太推荐promviz:开源项目,不算是监控图,可以做集群实时流量的可视化。二. Grafana可视化Grafana 是一个开源的图表可视化系统,简单说图表配置比较方便、生成的图表比较漂亮。并且模板众多,默认支持了prometheus作为数据源,也是prometheus官方推荐方案这里只对grafana做简单介绍,更多详细的内容参考展示-Grafana2.1 部署grafana是很成熟的(商业)项目,可以在官网下载客户端,或者在github主页自己build为镜像。主要的配置文件为conf文件夹下的defaults.ini文件,常用的配置可以配置在文件中,如果是docker运行或者在k8s中运行,可以使用env的方式,传入全局变量,将覆盖原有的defaults.ini配置。使用docker运行:docker run -d –name=grafana -p 3000:3000 grafana/grafana访问:http://127.0.0.1:3000账号:admin密码:admin2.2 配置第一步:配置数据源进入grafana后,第一步需要配置数据源,grafana默认支持prometheus作为数据源,因此Type直接选择PrometheusHttp的url地址为prometheus的服务地址,如果是同一个pod内,可以127.0.0.1:9090,不同pod的话,可以使用svc地址:http://prometheus.kube-system…:9090数据源配置后,点击save&test,可以验证数据源是否可用:第二步:配置面板:点击左侧的加号,可以添加面板(dashboard),并在该面板中添加各种类型的图表。对于面板,可以设置变量,用于下拉框筛选等场景,如设置机器变量:节点信息然后使用该变量,配置查询语句:得到各节点的cpu使用率面板demo配置完成后,可以出现类似图表,可以点击分享按钮,将本面板分享为json文件也可以筛选时间周期,设置自动刷新上图的json文件如下,仅供参考(需要安装node-exporter)json文件:https://raw.githubusercontent…本文为容器监控实践系列文章,完整内容见:container-monitor-book

March 12, 2019 · 1 min · jiezi

使用drone和gogs搭建自己的CI/CD系统

drone是一个基于容器的本地持续交付平台,和Jenkins是差不多的,然后配合轻量级的gogs来作为git管理,都是基于golang开发的很符合我的需求,我们来把它们结合作为一个完整的CI、CD平台。首先我们要先安装docker,上次的篇幅我们已经说过了我就不赘述了。需要的东西有:linux,docker,docker-compose,drone,gogs,git等。安装gogs和drone配合荣锋亮大哥的yml文件和docker-compose我们可以很容易安装他们:version: ‘3’services: drone-server: image: drone/drone:latest ports: - “8080:80” - 8843:443 - 9000 volumes: - ./drone:/var/lib/drone/ - /var/run/docker.sock:/var/run/docker.sock environment: - DRONE_OPEN=true - DRONE_SERVER_HOST=drone-server - DRONE_DEBUG=true - DRONE_GIT_ALWAYS_AUTH=false - DRONE_GOGS=true - DRONE_GOGS_SKIP_VERIFY=false - DRONE_GOGS_SERVER=http://gogs:3000 - DRONE_PROVIDER=gogs - DRONE_DATABASE_DATASOURCE=/var/lib/drone/drone.sqlite - DRONE_DATABASE_DRIVER=sqlite3 - DRONE_SERVER_PROTO=http - DRONE_RPC_SECRET=ALQU2M0KdptXUdTPKcEw - DRONE_SECRET=ALQU2M0KdptXUdTPKcEw gogs: image: gogs/gogs:latest ports: - “10022:22” - “3000:3000” volumes: - ./data/gogs:/data depends_on: - mysql mysql: image: mysql:5.7.16 volumes: - ./gogs/mysql:/var/lib/mysql - /var/run/docker.sock:/var/run/docker.sock ports: - 3308:3306 command: –character-set-server=utf8mb4 –collation-server=utf8mb4_unicode_ci environment: MYSQL_ROOT_PASSWORD: pass MYSQL_DATABASE: gogs MYSQL_USER: gogs MYSQL_PASSWORD: pass TZ: Asia/Shanghai drone-agent: image: drone/agent:latest depends_on: - drone-server environment: - DRONE_RPC_SERVER=http://drone-server - DRONE_RPC_SECRET=ALQU2M0KdptXUdTPKcEw - DRONE_DEBUG=true - DOCKER_HOST=tcp://docker-bind:2375 - DRONE_SERVER=drone-server:9000 - DRONE_SECRET=ALQU2M0KdptXUdTPKcEw - DRONE_MAX_PROCS=5 docker-bind: image: docker:dind privileged: true # command: –storage-driver=overlay我们创建一个存放docker-compose.yml文件的目录比如就叫gogs,然后我们把这些yml保存成docker-compose.yml,然后执行docker-compose来安装:$ docker-compose up -d配合yml文件,我们就安装好了drone-server和drone-agent还有gogs,然后我们用浏览器打开http://localhost:3000/来进入gogs并初始化它。域名和应用URL记得一样接着我们创建一个管理员用户,然后其他的都默认,点击立即安装完成。初始化成功之后我们可以在gogs里边创建一个仓库,然后登陆drone。drone打开浏览器输入http://localhost/直接进入drone,密码是gogs的你的刚刚的账户和密码。我们会看到一个刚刚创建的仓库,激活它!激活之后,我们回到gogs那边,仓库的设置里边的webhook应该已经配置好了我们可以测试web hook,如果没有问题的话,应该会提示成功。上传源码测试没有问题之后,我们初始化我们的代码文件夹为git仓库,然后push到gogos上边然后为你的仓库加上.drone.yml配置文件,drone-server会自动读取这个文件进行CI、CD操作等。以下这个是我们的示例文件kind: pipelinename: demosteps: - name: build image: golang:1.11.4 commands: - pwd - go version - go build . - go test demo/util # - name: frontend # image: node:6 # commands: # - npm install # - npm test - name: publish image: plugins/docker:latest settings: username: from_secret: docker_username password: from_secret: docker_password repo: example/demo tags: latest - name: deploy image: appleboy/drone-ssh pull: true settings: host: example.me user: root key: from_secret: deploy_key script: - cd /data - mkdir app/ - cd /data/app - docker rmi -f example/demo - echo “login docker” - echo “login success, pulling…” - docker pull example/demo:latest - echo “image running” - docker run -p 8088:8088 -d example/demo - echo “run success"我们首先进行简单的golang build和test操作然后根据Dockerfile文件把我们的程序构建成docker镜像,接着上传到docker hub中,然后通过drone-ssh插件部署这个镜像。开始构建有了配置文件之后,推送代码我们就可以去drone查看构建进度:drone的设置在进入drone的时候,选择一个项目我们可以进行一些必要的设置,比如配置secrets,定时任务和徽章等等。比如配置文件需要的密钥,用户名和密码,一些环境变量都可以在secrets设置,构建状态徽章可以在你的项目README.md文件加上去。项目加上徽章:示例代码,本文完。 ...

March 11, 2019 · 2 min · jiezi

k8s与configmap--安利configmap-reload组件

前言在kubernetes集群内,当ConfigMap以volume形式挂载到pod内时,更新ConfigMap,kubernetes会自动同步被挂载到pod内的文件内容。当然并不是更改立即生效的,大约是需要10S钟后,才会生效。实际生产使用案例中,假如你的应用具备hot reload 功能, 这时可以增加一些监测配置文件变更的脚本,然后reload对应服务。比如prometheus。今天就给大家介绍一个configmap-reload 组件。configmap-reloadconfigmap-reload 采用rust语言实现,作为主业务容器的sidercar,主要用于k8s当中监听configmap的变化,待变化后通过http接口的方式通知主业务。在资源消耗上,更小。具体如下:[root@ip-172-xx-xx-10 src]# kubectl top podsNAME CPU(cores) MEMORY(bytes)configmap-reload-6bbbb8b45b-7zg2x 0m 1Mi输入参数可以通过configmap-reload -h 获取:configmap-reload 0.1.0gaohj <gaohj2015@yeah.net>USAGE: configmap-reload [OPTIONS]FLAGS: -h, –help Prints help information -V, –version Prints version informationOPTIONS: -l, –log_level <LOG_LEVEL> log level: error|warn|info|debug|trace [default: info] -p, –path <VOLUME_PATH> the config map volume directory to watch for updates [default: ] -m, –webhook_method <WEBHOOK_METHOD> the HTTP method url to use to send the webhook: GET|POST [default: POST] -c, –webhook_status_code <WEBHOOK_STATUS_CODE> the HTTP status code indicating successful triggering of reload [default: 200] -u, –webhook_url <WEBHOOK_URL> the HTTP method url to use to send the webhook [default: ] 示例使用:—apiVersion: v1kind: ConfigMapmetadata: labels: app: configmap-reload name: configmap-reload-cmdata: test.ini: |- key: a—kind: DeploymentapiVersion: apps/v1metadata: name: configmap-reload labels: app: configmap-reloadspec: replicas: 1 selector: matchLabels: app: configmap-reload template: metadata: labels: app: configmap-reload spec: volumes: - name: config configMap: name: configmap-reload-cm containers: - name: configmap-reload image: ‘iyacontrol/configmap-reload:v0.1’ command: - configmap-reload args: - -l - debug - -p - /etc/test/ - -c - ‘200’ - -u - https://www.baidu.com volumeMounts: - name: config mountPath: /etc/test/ imagePullPolicy: Always—总结大家直接可以拉取 dockerhub 中的镜像。当然仓库中已经提供了Dockerfile文件,FROM clux/muslrust:stable as builderWORKDIR /configmap-reloadCOPY ./ ./ARG use_mirrorRUN if [ $use_mirror ]; then \ mkdir -p $HOME/.cargo; \ mv -f ./docker/cargo_config $HOME/.cargo/config; \ fiRUN cargo build –release#####################################FROM alpine:latest as prodRUN apk add –no-cache ca-certificates COPY –from=0 /configmap-reload/target/x86_64-unknown-linux-musl/release/configmap-reload /usr/bin/configmap-reloadRUN chmod +x /usr/bin/configmap-reloadENTRYPOINT [“configmap-reload”]大家可以自己打镜像,然后push到自己的仓库中。 ...

March 10, 2019 · 1 min · jiezi

使用 Jenkins 部署 React 项目

背景公司的前端项目部署方式比较简单,整个过程基本上是手动的;目标通过工具实现以下几个任务:编译、部署自动化;选择指定版本进行回滚;区分不同的分支(环境);技术方案选用 jenkins 作为部署工具;也便于后续 CI 的接入;使用 docker 进行编译,确保每次编译的环境的稳定;步骤步骤一:搭建 Jenkins搭建 Jenkins 有很多方案,这里选择使用 docker 搭建。docker-compose.yml 的内容如下:version: ‘3’services: fejenkins: user: root image: jenkinsci/blueocean ports: - “8080:8080” - “50000:50000” volumes: - /data/jenkins_home:/var/jenkins_home - /data/nm_cache:/var/nm_cache - /var/run/docker.sock:/var/run/docker.sock通过 docker-compose up 命令启动;启动后通过初始密码进行第一个用户的创建和 Jenkins 初始化的一些列操作,初始密码会打印在 jenkins docker 启动命令行的输出中,注意查看。当然也可以不使用 docker-compose:docker run –rm -u root -v /data/jenkins_home:/var/jenkins_home -v /data/nm_cache:/var/nm_cache -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 -p 50000:50000 jenkinsci/blueocean稍做解释:/data/jenkins_home:/var/jenkins_home /var/jenkins_home 是 jenkinsci image 的默认数据存放路径,这里将路径映射到宿主机的指定文件夹;/data/nm_cache:/var/nm_cache nm_cache 涵义是 node_modules cache,顾名思义,这里是想对编译所需的 node_modules 做缓存,将缓存文件夹也映射到宿主机;/var/run/docker.sock:/var/run/docker.sock 这里是将宿主机运行 docker 后产生的 sock 文件映射到了 jenkins container 中。这样,jenkins 中使用 docker 进行编译时,其实是使用宿主机的 docker 来运行的,而不是在 docker container 中又启动了 docker。这里稍微有点绕,若是暂时看不明白,需要找一些深入介绍 docker 的文章阅读;步骤二:配置 Jenkins添加 Credentials通过 Jenkins 进行 git 操作需要对应 git repo 的权限,这里需要用到有 git repo 权限的密钥文件。同样,通过 Jenkins 将编译产物 scp 到服务器上的时候,也需要服务器的密钥文件。这两类密钥文件需要配置在 Jenkins 中,在:Jenkins > Credentials > System > Global credentials (unrestricted) 里进行 Add Credentials 的操作。添加 Jenkins ItemJenkins > New Item,然后选择 Pipeline,在下面的 Pipeline 配置区域的 Definition 中选择 Pipeline script,Script 如下:pipeline { environment { SERVER_IP_1 = “11.22.33.44” SERVER_CREDENTIALSID = “abcd1234-abcd-abcd-abcd-abcd1234abcd” SERVER_DEPLOY_DIR = “/your/www/path/” CACHE_DIR = “/var/nm_cache/your_project_name/” GIT_URL = “git@github.com:example/example.git” GIT_BRANCH = “master” GIT_CREDENTIALSID = “abcd1234-abcd-abcd-abcd-abcd1234abcd” } agent none stages { stage(‘Checkout code’) { agent any steps { git ( branch: “${GIT_BRANCH}”, credentialsId: “${GIT_CREDENTIALSID}”, url: “${GIT_URL}”, changelog: true ) sh ’’’ ls -al cache_dir="${CACHE_DIR}" cache_nm="${CACHE_DIR}node_modules" cache_lock="${CACHE_DIR}yarn.lock" if [ ! -d “$cache_dir” ]; then mkdir ${cache_dir}; fi if [ ! -d “$cache_nm” ]; then mkdir ${cache_nm}; fi if [ -d “$cache_nm” ]; then ln -sf ${cache_nm} ./; fi if [ -f “$cache_lock” ]; then mv -n ${cache_lock} .; fi ls -al ’’’ } } stage(‘Build’) { agent { docker { image ’node:8-alpine’ args ’’ } } steps { sh ’’’ npm config set registry https://registry.npm.taobao.org yarn install yarn build tar -cvf build.tar build ls -al mv ./yarn.lock ${CACHE_DIR} rm -rf ./node_modules ls -al ’’’ archiveArtifacts artifacts: ‘build.tar’, fingerprint: true } } stage(‘Deploy’) { agent any steps { unarchive mapping: [‘build.tar’: ‘build.tar’] echo ‘— Deploy —’ sshagent(["${SERVER_CREDENTIALSID}"]) { sh “scp -o StrictHostKeyChecking=no build.tar root@${SERVER_IP_1}:${SERVER_DEPLOY_DIR}” sh “ssh -o StrictHostKeyChecking=no root@${SERVER_IP_1} "rm -rf ${SERVER_DEPLOY_DIR}build; tar -xvf ${SERVER_DEPLOY_DIR}build.tar -C ${SERVER_DEPLOY_DIR}"” } } } }}稍做解释:这个部署脚本分为三个步骤:Checkout code(在指定 git 仓库通过指定证书文件获取代码)Build(通过指定命令进行编译,将编译后的产物存档)Deploy(通过指定命令部署)在 Build 阶段前后,我们各做了一些工作,以求每次部署可以复用 node_modules,因为下载 node_modules 的时间可能很长,而并不是每次都会修改 package.json,所以其实 node_modules 大概率可以复用;编译前:看指定 node_modules 缓存文件夹是否存在,不存在则新建该文件夹;看缓存文件夹中是否有 node_modules 文件夹,如果没有则新建该文件夹;并且将该文件夹软连接到当前目录;看缓存文件夹中是否有 yarn.lock 文件,如果有则移动到当前文件夹;编译后:移除 node_modules 文件夹的软连接;将 yarn.lock 文件移动到缓存文件夹中;这里使用了 yarn install 的某些特性:没有 node_modules 或者 yarn.lock 时会安装全量依赖;有 node_modules 和 yarn.lock 但是 yarn.lock 和 package.json 不匹配时,会安装所需依赖;有 node_modules 和 yarn.lock,且 yarn.lock 和 packge.json 配置时,跳过安装依赖;使用编译和部署编译和部署直接点击 Build Now 即可;回滚回滚的本质其实是:重新部署某个历史版本。在 Build History 找到想要重新部署的版本,点击 Restart from Stage,在新页面中选择 Stage Name 为 Deploy。其他若是想要进入 docker container 交互,可以通过以下命令docker exec -i -t [dockername] /bin/bash ...

March 10, 2019 · 2 min · jiezi

如何使用 docker 部署前端应用

如何使用 docker 部署前端应用docker 变得越来越流行,它可以轻便灵活地隔离环境,进行扩容,方便运维管理。对开发者也更方便开发,测试与部署。最重要的是, 当你面对一个陌生的项目,你可以照着 Dockerfile,甚至不看文档(文档也不一定全,全也不一定对)就可以很快让它在本地跑起来。现在很强调 devops 的理念,我把 devops 五个大字放在电脑桌面上,格物致知了一天。豁然开朗,devops 的意思就是写一个 Dockerfile 去跑应用(开玩笑。这里介绍如何使用 Docker 部署前端应用。千里之行,始于足下,足下的意思就是,先让它能够跑起来。本文地址 http://shanyue.tech/post/depl…先让它跑起来首先,简单介绍一下一个典型的前端应用部署流程npm install, 安装依赖npm run build,编译,打包,生成静态资源服务化静态资源介绍完部署流程后,简单写一个 DockerfileFROM node:alpine# 代表生产环境ENV PROJECT_ENV productionWORKDIR /codeADD . /codeRUN npm install && npm run build && npm install -g http-serverEXPOSE 80CMD http-server ./public -p 80现在这个前端服务已经跑起来了。接下来你可以完成部署的其它阶段了。一般情况下,以下就成了运维的工作了,不过,拓展自己的知识边界总是没错的。使用 nginx 或者 traefik 做反向代理使用 kubernetes 或者 compose 等做编排。使用 gitlab ci 或者 drone ci 等做 CI/CD这时镜像存在有两个问题,导致每次部署时间过长,不利于产品的快速交付构建镜像时间过长构建镜像大小过大,1G+从 dependencies 和 devDependencies 下手陆小凤说过,一个前端程序员若是每天工作八个小时,至少有两个小时是白白浪费了的。一个小时用来 npm install,另一个小时用来 npm run build。对于每次部署,如果能够减少无用包的下载,便能够节省很多镜像构建时间。eslint,mocha,chai 等代码风格测试模块可以放到 devDependencies 中。在生产环境中使用 npm install –production 装包。关于两者的区别可以参考文档 https://docs.npmjs.com/files/...FROM node:alpineENV PROJECT_ENV productionWORKDIR /codeADD . /codeRUN npm install –production && npm run build && npm install -g http-serverEXPOSE 80CMD http-server ./public -p 80好像是快了那么一点点。我们注意到,相对于项目的源文件来讲,package.json 是相对稳定的。如果没有新的安装包需要下载,则再次构建镜像时,无需重新装包。则可以在 npm install 上节省一半的时间。利用镜像缓存对于 ADD 来讲,如果需要添加的内容没有发生变化,则可以利用缓存。把 package.json 与源文件分隔开写入镜像是一个很好的选择。目前,如果没有新的安装包更新的话,可以节省一半时间FROM node:alpineENV PROJECT_ENV production# http-server 不变动也可以利用缓存RUN npm install -g http-serverWORKDIR /codeADD package.json /codeRUN npm install –productionADD . /codeRUN npm run buildEXPOSE 80CMD http-server ./public -p 80关于利用缓存有更多细节,需要特别注意一下,如 RUN git clone <repo> 的缓存此类参考官方文档 https://docs.docker.com/devel…多阶段构建得益于缓存,现在镜像构建时间已经快了不少。但是,镜像的体积依旧过于庞大,也会增加每次的部署时间考虑下每次 CI 部署的流程在构建服务器构建镜像把镜像推至镜像仓库服务器,在生产服务器拉取镜像,启动容器显而易见,镜像体积过大造成传输效率低下,增加每次部署的延时。即使,构建服务器与生产服务器在同一节点下,没有延时的问题。减少镜像体积也能够节省磁盘空间关于镜像体积的过大,很大一部分是因为node_modules 臭名昭著的体积但最后我们只需要 public 文件夹下的内容,对于源文件以及node_modules下文件,占用体积过大且不必要,造成浪费。此时可以利用 Docker 的多阶段构建,仅来提取编译后文件参考官方文档 https://docs.docker.com/devel...FROM node:alpine as builderENV PROJECT_ENV production# http-server 不变动也可以利用缓存WORKDIR /codeADD package.json /codeRUN npm install –productionADD . /codeRUN npm run build# 选择更小体积的基础镜像FROM nginx:alpineCOPY –from=builder /code/public /usr/share/nginx/html此时,镜像体积从 1G+ 变成了 50M+使用 CDN分析一下 50M+ 的镜像体积,nginx:alpine 的镜像是16M,剩下的40M是静态资源。如果把静态资源给上传到 CDN,则没有必要打入镜像了,此时镜像大小会控制在 20M 以下关于静态资源,可以分类成两部分/static,此类文件在项目中直接引用根路径,打包时复制进 /public 下,需要被打入镜像/build,此类文件需要 require 使用,会被 webpack 打包并加 hash 值,并可以通过 publicPath 修改资源地址。可以把此类文件上传至 cdn,并加上永久缓存,不需要打入镜像FROM node:alpine as builderENV PROJECT_ENV production# http-server 不变动也可以利用缓存WORKDIR /codeADD package.json /codeRUN npm install –productionADD . /codeRUN npm run build# 选择更小体积的基础镜像FROM nginx:alpineCOPY –from=builder code/public/index.html code/public/favicon.ico /usr/share/nginx/html/COPY –from=builder code/public/static /usr/share/nginx/html/static ...

March 9, 2019 · 2 min · jiezi

Rancher搭建、web应用部署

续:Dockerfile构建PHP开发镜像:Alpine+Nginx+PHP7+Supervisor+Crontab+Laravel一、Rancher搭建1.1 Rancher安装 首先rancher需要安装了docker的linux环境,我的CentOS系统版本为:CentOS Linux release 7.4.1708 (Core) 在docker的基础上启动rancher服务器(传送门:Docker安装),Rancher 服务器是一个 Docker image,所以其软件本身不需要安装,只需要执行 Docker 命令下载并且成功运行 Docker 服务器镜像即可。sudo docker run -d –restart=always -p 8080:8080 rancher/server启动容器并指定端口,如果没有rancher/server镜像会自动下载。1.2 添加主机然后进行添加主机操作,根据网站指引操作,基础架构->保存->生成一条命令,在docker中运行。成功添加主机:二、添加服务2.1 添加基础镜像然后就可以看见在服务中看见刚才添加的服务,并且成功访问:2.2 添加后台管理系统基于基础镜像发布后台管理系统:待续!GitHub地址:https://github.com/tcyfree/anpscDocker Hub镜像地址:https://cloud.docker.com/repo…

March 8, 2019 · 1 min · jiezi

通过阿里云K8S Ingress Controller实现路由配置的动态更新

摘要: 本文主要描述了阿里云Kubernetes集群Ingress Controller如何通过动态更新的方式来极大地缓解转发平面Nginx频繁Reload带来的影响。简介在Kubernetes集群中,Ingress作为集群内服务对外暴露的访问接入点,其几乎承载着集群内服务访问的所有流量。我们知道,Nginx Ingress Controller是Kubernetes社区很重要的一个子项目,其内部主要依托于高性能的负载均衡软件Nginx,将Kubernetes Ingress资源对象实时地自动化转换为Nginx配置规则来对外提供期望的授权访问入口。现实问题当随着Kubernetes集群中部署的微服务越来越多,对外暴露的路由规则越来越复杂,服务后端Endpoint变化的越来越频繁,那么对应地都会引起Nginx Ingress Controller组件内Nginx配置文件的变化越来越频繁;而我们知道,任何一行Nginx配置的变化,都需要Reload Nginx才能生效,这在变化频率较低的场景下索性还能接受,但在高频率变化的场景下就会引起Nginx的频繁Reload。而Nginx频繁Reload带来的问题,这已是一个老生常谈的话题了,其问题本质主要还是源于Nginx本身最初的架构设计模型:一般在Linux服务器中,我们会配置使用Nginx的EPOLL多进程模式;当我们修改了Nginx配置文件后,需要通过nginx -s reload命令来重新热加载新的Nginx配置规则;当Nginx Master进程接收到reload signal后,其会从指定路径重新加载新的Nginx配置文件内容,并校验配置规则的有效性,若检验为有效的配置文件,则会依据新的配置文件中的worker_processes值fork出指定数量的新的Nginx Worker进程,此时新fork出来的子进程完全继承了父进程的内存数据ngx_cycle(其包含了新的解析后的Nginx配置规则),同时将配置中的每一个Listen Socket FD注册到内核的EPOLL事件监听中,此时这些新的Nginx Worker进程可以接收处理来自客户端的请求;同时Nginx Master进程会发送QUIT signal通知老的Nginx Worker进程平滑退出,当老的Nginx Worker进程接收到QTUI信号后,将其之前注册到EPOLL中的监听Event移除,至此不再接收处理新的客户端请求,并依据老配置文件中设置的worker_shutdown_timeout值来设置定时器,然后继续处理完已接收的客户端请求;若在worker_shutdown_timeout之前处理完已有的客户端请求,则自动退出,若未处理完,则被强制Kill退出,此时就会导致该客户端请求响应异常。因此,对于在高频率变化的场景下,Nginx频繁Reload会带来较明显的请求访问问题:造成一定的QPS抖动和访问失败情况对于长连接服务会被频繁断掉造成大量的处于shutting down的Nginx Worker进程,进而引起内存膨胀动态更新为缓解Nginx频繁Reload带来的影响,我们需要通过动态更新的方式来加载Nginx配置规则,即在不Fork新Nginx Worker进程的情况下来实时更新已加载到内存中的Nginx配置规则。首先我们看下Nginx的配置文件样式,主要包含下面几部分配置章节:# 1. main configurationdaemon off;worker_processes 4;events { # 2. event configuration multi_accept on; worker_connections 1024; use epoll;}http { # 3. http main configuration access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; upstream { # 4. upstream configuration server 0.0.0.1; } server { # 5. server configuration server_name _ ; listen 80 default_server; location / { # 6. location configuration proxy_pass http://upstream_balancer; }}而在Kubernetes集群中,一个Ingress资源对象主要被解析映射到Nginx的HTTP Main Block、Server Block、Upstream Block和Location Block等章节的配置规则上,因此我们可以将这几部分频繁变化的配置内容以Shared Memory的方式统一维持在内存中,同时在Ingress Controller内部暴露出管控端口,通过API的方式来实时管理Nginx路由规则配置:当K8S Ingress Controller监控到集群内Ingress及相关联的资源发生变化时,均可通过Internal API将最新的Nginx配置规则推送到统一的共享内存配置中,而不用再通过Reload Nginx的方式来使新配置生效,至此当Nginx处理任何新接收的客户端请求时,都可以基于最新的共享内存中的配置进行规则匹配和路由转发;配置说明1、目前阿里云容器服务Kubernetes集群中最新版本的Nginx Ingress Controller组件默认已开启Upstream的动态更新,同时支持应用服务的灰度发布和蓝绿发布功能,具体配置说明可参考这里;我们可以通过如下命令来查看当前共享内存中的Nginx Upstream的配置列表:kubectl -n kube-system exec -it <NGINX-INGRESS-CONOTROLLER-POD-NAME> – curl http://127.0.0.1:18080/configuration/backends2、同时也支持HTTPS证书的动态更新,可通过修改nginx-ingress-controller deployment的如下参数配置来开启Nginx Ingress Controller的证书动态更新:- args: - /nginx-ingress-controller - –configmap=$(POD_NAMESPACE)/nginx-configuration - –tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - –udp-services-configmap=$(POD_NAMESPACE)/udp-services - –annotations-prefix=nginx.ingress.kubernetes.io - –publish-service=$(POD_NAMESPACE)/nginx-ingress-lb - –enable-dynamic-certificates=true ### 添加该配置 - –v=2当开启HTTPS证书的动态更新后,Ingress的TLS证书都统一维护在Nginx的共享内存中,我们可通过如下命令来查看当前共享内存中配置的证书列表:kubectl -n kube-system exec -it <NGINX-INGRESS-CONOTROLLER-POD-NAME> – curl http://127.0.0.1:18080/configuration/certs3、进一步地我们也支持Nginx Server和Location配置的动态更新,可通过修改nginx-ingress-controller deployment的如下参数配置来开启Nginx Ingress Controller的Server和Location的动态更新:- args: - /nginx-ingress-controller - –configmap=$(POD_NAMESPACE)/nginx-configuration - –tcp-services-configmap=$(POD_NAMESPACE)/tcp-services - –udp-services-configmap=$(POD_NAMESPACE)/udp-services - –annotations-prefix=nginx.ingress.kubernetes.io - –publish-service=$(POD_NAMESPACE)/nginx-ingress-lb - –enable-dynamic-certificates=true ### 添加该配置 - –enable-dynamic-servers=true ### 添加该配置,同时也要enable-dynamic-certificates - –v=2同样地,当我们开启了Nginx Ingress Controller的Server动态更新后,所有Nginx Server和Location的配置都统一维护在共享内存中,我们同样可以通过如下命令来查看当前共享内存中的Server配置列表:kubectl -n kube-system exec -it <NGINX-INGRESS-CONOTROLLER-POD-NAME> – curl http://127.0.0.1:18080/configuration/servers注意说明:当开启Server的动态更新特性后,部分Ingress Annotation配置暂不支持,正在逐步优化支持中,相应地您可直接通过ConfigMap方式来进行配置;本文作者:chenqz阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 8, 2019 · 1 min · jiezi

docker-compose安装及简单入门

docker-compose 编排一组容器的启停的工具,直接在官方github仓库下载二进制文件。安装1. 下载docker-composesudo curl -L “https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)” -o /usr/local/bin/docker-compose2. 给docker-compose增加执行权sudo chmod +x /usr/local/bin/docker-compose搞定,安装完成查看版本信息docker-compose version 输出docker-compose version 1.23.2, build 1110ad01docker-py version: 3.6.0CPython version: 3.6.7OpenSSL version: OpenSSL 1.1.0f 25 May 2017可以获取帮助docker-compose -h卸载直接删除下载的二进制文件即可,linux命令sudo rm /usr/local/bin/docker-compose参考资料:官方文档https://docs.docker.com/compo…编写docker-compose.yml文件docker-compose的容器启停是通过读取yml配置文件来实现的,接下来编写yml文件version: “3"services: registrator: image: gliderlabs/registrator container_name: registrator network_mode: bridge external_links: - consul volumes: - /var/run/docker.sock:/tmp/docker.sock command: consul://consul:8500该文件启动了一个registrator服务,该服务用于向consul注册宿主机内启动的,暴露在外的容器。registrator是服务节点,下面都是这个服务的配置image 是使用的docker image,如果不指定image也可使用buildbuild 指定Dockerfile 文件的位置,image或build两者必须有一个container_name 指定容器namenetwork_mode 网络模式external_links 当所依赖的容器不在此compose网络中,使用此参数连接外部容器volumes 用于宿主机和容器共享文件command 容器启动后执行的命令后台运行这些容器docker-compose up -d用docker ps 查看docker容器CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES88cca28df349 nginx “nginx -g ‘daemon of…” 5 minutes ago Up 5 minutes 0.0.0.0:1889->80/tcp app1启动成功注意,compose文件启动是有顺序的,通过depends_on来指定该服务所依赖的服务,以提前依赖服务的启动顺序,但不是容器内部程序启动成功才去启动下一个容器,容器内部程序启动有耗时,如果你的程序之间互相有依赖,尽量分开写docker-compose文件,避免容器启动失败。提供两个解决方案的博客:https://blog.terminus.io/pamp…https://yq.aliyun.com/article…去网上搜索有一大堆,按照自己需要去使用。compose启动多任务只需多增加一个service version: “3” services: nginx1: image: nginx container_name: app1 ports: - 1888:80 nginx2: image: nginx container_name: app2 ports: - 1889:80 registrator: image: gliderlabs/registrator container_name: regi network_mode: bridge external_links: - consul volumes: - /var/run/docker.sock:/tmp/docker.sock command: consul://consul:8500ports指定该容器暴露的端口号用compose启动,用docker ps查看hsn@ubuntu:/docker/docker-compose/registrator$ docker-compose up -dCreating network “registrator_default” with the default driverCreating app1 … doneCreating regi … doneCreating app2 … donehsn@ubuntu:/docker/docker-compose/registrator$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES88cca28df349 nginx “nginx -g ‘daemon of…” 5 minutes ago Up 5 minutes 0.0.0.0:1889->80/tcp app225e250593a8a gliderlabs/registrator “/bin/registrator co…” 5 minutes ago Up 5 minutes regi1208b98a979b nginx “nginx -g ‘daemon of…” 5 minutes ago Up 5 minutes 0.0.0.0:1888->80/tcp app1启动成功关闭一组服务docker-compose down注意要在docker-compos.yml文件所在的目录执行该命令才可关闭对应的容器会停止并删除容器,docker网络等入门到此结束…. ...

March 7, 2019 · 1 min · jiezi

开发函数计算的正确姿势——运行 Selenium Java

前言首先介绍下在本文出现的几个比较重要的概念:函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息参考。Fun: Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作。Fun 的更多文档参考。备注: 本文介绍的技巧需要 Fun 版本大于等于 2.10.2。依赖工具本项目是在 MacOS 下开发的,涉及到的工具是平台无关的,对于 Linux 和 Windows 桌面系统应该也同样适用。在开始本例之前请确保如下工具已经正确的安装,更新到最新版本,并进行正确的配置。DockerFunFcliFun 和 Fcli 工具依赖于 docker 来模拟本地环境。对于 MacOS 用户可以使用 homebrew 进行安装:brew cask install dockerbrew tap vangie/formulabrew install funbrew install fcliWindows 和 Linux 用户安装请参考:https://github.com/aliyun/fun/blob/master/docs/usage/installation.mdhttps://github.com/aliyun/fcli/releases安装好后,记得先执行 fun config 初始化一下配置。注意, 如果你已经安装过了 fun,确保 fun 的版本在 2.10.2 以上。$ fun –version2.10.1快速开始初始化使用 fun init 命令可以快捷地将本模板项目初始化到本地。fun init vangie/selenium-java-example安装依赖$ fun install…本地测试测试代码 ChromeDemo 的内容为:public class ChromeDemo implements StreamRequestHandler { public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { System.setProperty(“webdriver.chrome.driver”, “/code/chromedriver”); ChromeOptions options = new ChromeOptions(); options.setBinary("/code/headless-chromium"); options.addArguments("–disable-extensions"); // disabling extensions options.addArguments("–disable-gpu"); // applicable to windows os only options.addArguments("–disable-dev-shm-usage"); // overcome limited resource problems options.addArguments("–no-sandbox"); // Bypass OS security model options.addArguments("–headless"); WebDriver driver = new ChromeDriver(options); driver.get(“https://ide.fc.aliyun.com”); outputStream.write((“Page title is: " + driver.getTitle() + “\n”).getBytes()); driver.quit(); }}本地运行$ mvn package && fun local invoke selenium…FC Invoke Start RequestId: 68c83b4c-b053-479c-9b0e-9503582ccb56handle user request is com.aliyun.fc.selenium.ChromeDemo::handleRequestcache is null!Starting ChromeDriver 2.35.528139 (47ead77cb35ad2a9a83248b292151462a66cd881) on port 20652Only local connections are allowed.Mar 05, 2019 11:34:27 AM org.openqa.selenium.remote.ProtocolHandshake createSessionINFO: Detected dialect: OSSPage title is: 云端集成开发环境FC Invoke End RequestId: 68c83b4c-b053-479c-9b0e-9503582ccb56RequestId: 68c83b4c-b053-479c-9b0e-9503582ccb56 Billed Duration: 5265 ms Memory Size: 1998 MB Max Memory Used: 240 MB部署$ mvn package && fun deploy执行$ fcli function invoke -s chrome -f selenium Page title is: 云端集成开发环境关于文件尺寸由于 chromedriver 和 headless-chromium 压缩后体积已经非常接近 50MB,留给用户 Jar 的空间非常少,所以另外制作了一个高压缩比版本,使用压缩比更高的 brotli 算法进行压缩,压缩后的大小为 32.7MB。然后在运行时使用 initializer 进行解压,解压耗时大约为 3.7 S。https://github.com/vangie/packed-selenium-java-example参考阅读https://github.com/smithclay/lambdiumhttps://medium.com/clog/running-selenium-and-headless-chrome-on-aws-lambda-fb350458e4df本文作者:倚贤阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 7, 2019 · 1 min · jiezi

实战生产环境:1.13.3最新版k8s集群部署Heapster插件

本篇文章,所使用的任何镜像和yaml我都会发一个网盘链接,供大家下载学习!链接:https://pan.baidu.com/s/1inmW… 密码:92uagithub:https://github.com/heyangguang有任何问题可以直接联系我的Email:heyangev@cn.ibm.comHeapster是一个收集者,将每个Node上的cAdvisor的数据进行汇总,然后导到第三方工具(如InfluxDB)。Heapster介绍架构图:Heapster首先从K8S Master获取集群中所有Node的信息,然后通过这些Node上的kubelet获取有用数据,而kubelet本身的数据则是从cAdvisor得到。所有获取到的数据都被推到Heapster配置的后端存储中,并还支持数据的可视化。现在后端存储 + 可视化的方法,如InfluxDB + grafana。部署实施:下载heapster镜像,上传heapster.yaml、heapster-mod.yaml文件apply就可以了。k8smaster:[root@k8smaster ~]# docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-amd64:v1.5.4Trying to pull repository registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-amd64 …v1.5.4: Pulling from registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-amd6491380601464e: Pull completef351486260ed: Pull completeDigest: sha256:c4a8d9c0007abb73a1b9e4f9c8bfb044e475aae2b4e6276ab2f8b13959cf6949Status: Downloaded newer image for registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-amd64:v1.5.4[root@k8smaster ~]# lsanaconda-ks.cfg heapster-mod.yaml heapster.yaml images.tar kube-flannel.yml[root@k8smaster ~]# kubectl apply -f heapster.yamlserviceaccount/heapster createdclusterrolebinding.rbac.authorization.k8s.io/heapster createddeployment.apps/heapster createdservice/heapster created[root@k8smaster ~]# kubectl apply -f heapster-mod.yamlWarning: kubectl apply should be used on resource created by either kubectl create –save-config or kubectl applyclusterrole.rbac.authorization.k8s.io/system:heapster configured查看状态:[root@k8smaster ~]# kubectl get pods -n kube-systemNAME READY STATUS RESTARTS AGEcoredns-86c58d9df4-kmfct 1/1 Running 0 23mcoredns-86c58d9df4-qn2k2 1/1 Running 0 23metcd-k8smaster 1/1 Running 0 23mheapster-569b679494-rktzf 1/1 Running 0 18skube-apiserver-k8smaster 1/1 Running 1 22mkube-controller-manager-k8smaster 1/1 Running 0 22mkube-flannel-ds-amd64-9rmfz 1/1 Running 0 19mkube-flannel-ds-amd64-vnwtf 1/1 Running 0 15mkube-flannel-ds-amd64-x7q4s 1/1 Running 0 15mkube-proxy-7zl9n 1/1 Running 0 22mkube-proxy-t2sx9 1/1 Running 0 23mkube-proxy-txsfr 1/1 Running 0 22mkube-scheduler-k8smaster 1/1 Running 0 23m稍等一会,使用kubectl top node查看集群状态即可:[root@k8smaster ~]# kubectl top nodeNAME CPU(cores) CPU% MEMORY(bytes) MEMORY%k8smaster 122m 3% 2848Mi 8%k8snode-1 27m 0% 603Mi 1%k8snode-2 26m 0% 582Mi 1%到这里heapster收集集群数据教程就完成了!希望大家可以给我指出问题,我们一起前进!谢谢大家! ...

March 6, 2019 · 1 min · jiezi

zabbix docker安装中文无法显示的问题

问题描述: You are not able to choose some of the languages, because locales for them are not installed on the web server.中文语言无法选择,这是因为docker容器环境中缺少中文语言包,安装中文语言包即可CentOS:yum -y install kde-l10n-Chineseyum -y reinstall glibc-commonlocaledef -c -f UTF-8 -i zh_CN zh_CN.utf8修改DockerfileFROM zabbix/zabbix-server-mysql RUN yum -y install kde-l10n-Chinese && yum -y reinstall glibc-common && localedef -c -f UTF-8 -i zh_CN zh_CN.utf8ENV LC_ALL zh_CN.utf8 Ubuntu:apt-get install language-pack-zh-hant language-pack-zh-hans

March 6, 2019 · 1 min · jiezi

zabbix 4.0.3 use docker-compose deploy

CentOS 7 使用 docker-compose 部署zabbix 4.0.3docker-compose.yaml 配置文件如下:version: ‘3.1’services: db: image: mysql command: –default-authentication-plugin=mysql_native_password –character-set-server=utf8mb4 –collation-server=utf8mb4_unicode_ci –default-time-zone=’+08:00’ restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: “MysqlPasswd” MYSQL_ALLOW_EMPTY_PASSWORD: “no” ports: - 9306:3306 volumes: - /data/dockerdata/db/data:/var/lib/mysql - /data/dockerdata/db/conf:/etc/mysql/conf.d - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime server: image: zabbix/zabbix-server-mysql:centos-4.0.3 restart: unless-stopped environment: DB_SERVER_HOST: db MYSQL_USER: root MYSQL_PASSWORD: MysqlPasswd ZBX_TIMEOUT: 30 ports: - 9051:10051 - 8444:10051 volumes: - /data/dockerdata/server/alertscripts:/usr/lib/zabbix/alertscripts - /data/dockerdata/server/externalscripts:/usr/lib/zabbix/externalscripts - /data/dockerdata/server/modules:/var/lib/zabbix/modules - /data/dockerdata/server/enc:/var/lib/zabbix/enc - /data/dockerdata/server/ssh_keys:/var/lib/zabbix/ssh_keys - /data/dockerdata/server/ssl/certs:/var/lib/zabbix/ssl/certs - /data/dockerdata/server/ssl/keys:/var/lib/zabbix/ssl/keys - /data/dockerdata/server/ssl/ssl_ca:/var/lib/zabbix/ssl/ssl_ca - /data/dockerdata/server/snmptraps:/var/lib/zabbix/snmptraps - /data/dockerdata/server/mibs:/var/lib/zabbix/mibs - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime web: image: zabbix/zabbix-web-nginx-mysql:centos-4.0.3 restart: unless-stopped environment: DB_SERVER_HOST: db MYSQL_USER: root MYSQL_PASSWORD: MysqlPasswd ZBX_SERVER_HOST: server PHP_TZ: “Asia/Shanghai” ZBX_SERVER_NAME: aws ports: - 8081:80 volumes: - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime ...

March 6, 2019 · 1 min · jiezi

实战生产环境:kubeadmin安装1.13.3最新版k8s集群教程

2019年3月6日:出版安装kubeadmin部署k8s集群教程本次安装采用kubeadmin !安装的k8s版本为1.13.3版,是当前最新版本!本篇文章,所使用的任何镜像和yaml都会在我的github上找到!github:https://github.com/heyangguang有任何问题可以直接联系我的Email:heyangev@cn.ibm.com主机列表:系统采用的是Centos7.5主机名IP地址角色k8smaster9.186.137.114masterk8snode-19.186.137.115nodek8snode-29.186.137.116Node基础环境准备:关闭防火墙、关闭selinux,关闭swap,关闭网卡管理器。k8smaster:[root@k8smaster ~]# systemctl stop firewalld[root@k8smaster ~]# systemctl disable firewalld[root@k8smaster ~]# systemctl stop NetworkManager ; systemctl disable NetworkManager[root@k8smaster ~]# vim /etc/selinux/config[root@k8smaster ~]# scp /etc/selinux/config root@k8snode-1:/etc/selinux/configconfig 100% 546 1.1MB/s 00:00[root@k8smaster ~]# scp /etc/selinux/config root@k8snode-2:/etc/selinux/configconfig 100% 546 1.3MB/s 00:00[root@k8smaster ~]# swapoff -a[root@k8smaster ~]# vim /etc/fstab[root@k8smaster ~]# cat /etc/fstab## /etc/fstab# Created by anaconda on Mon Mar 4 17:23:04 2019## Accessible filesystems, by reference, are maintained under ‘/dev/disk’# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info#/dev/mapper/centos-root / xfs defaults 0 0UUID=3dd5660e-0905-4f1e-9fa3-9ce664d6eb94 /boot xfs defaults 0 0/dev/mapper/centos-home /home xfs defaults 0 0#/dev/mapper/centos-swap swap swap defaults 0 0k8snode-1:[root@k8snode-1 ~]# systemctl stop firewalld[root@k8snode-1 ~]# systemctl disable firewalldRemoved symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.[root@k8snode-1 ~]# systemctl stop NetworkManager ; systemctl disable NetworkManager[root@k8snode-1 ~]# swapoff -a[root@k8snode-1 ~]# vim /etc/fstabk8snode-2:[root@k8snode-2 ~]# systemctl stop firewalld[root@k8snode-2 ~]# systemctl disable firewalldRemoved symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.[root@k8snode-2 ~]# swapoff -a[root@k8snode-2 ~]# systemctl stop NetworkManager ; systemctl disable NetworkManager[root@k8snode-2 ~]# vim /etc/fstabyum源配置:需要k8snode-1和k8snode-2先操作,在把yum源copy过去。k8smaster:[root@k8smaster yum.repos.d]# rm -rf [root@k8smaster yum.repos.d]# ll总用量 12-rw-r–r– 1 root root 2206 3月 5 18:50 CentOS-Base.repo-rw-r–r– 1 root root 923 3月 5 18:50 epel.repo-rw-r–r– 1 root root 276 3月 5 18:50 k8s.repo[root@k8smaster yum.repos.d]# scp * k8snode-1:/etc/yum.scp: /etc/yum.: No such file or directory[root@k8smaster yum.repos.d]# scp * k8snode-1:/etc/yum.repos.d/CentOS-Base.repo 100% 2206 352.0KB/s 00:00epel.repo 100% 923 160.8KB/s 00:00k8s.repo 100% 276 48.2KB/s 00:00[root@k8smaster yum.repos.d]# scp * k8snode-2:/etc/yum.repos.d/CentOS-Base.repo 100% 2206 216.3KB/s 00:00epel.repo 100% 923 157.1KB/s 00:00k8s.repo 100% 276 47.5KB/s 00:00k8snode-1:[root@k8snode-1 ~]# cd /etc/yum.repos.d/[root@k8snode-1 yum.repos.d]# rm -rf k8snode-2:[root@k8snode-2 ~]# rm -rf /etc/yum.repos.d/安装Docker引擎:k8smaster:[root@k8smaster yum.repos.d]# yum -y install docker[root@k8smaster yum.repos.d]# systemctl start docker ; systemctl enable dockerCreated symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.k8snode-1:[root@k8snode-1 yum.repos.d]# yum -y install docker[root@k8snode-1 yum.repos.d]# systemctl start docker ; systemctl enable dockerCreated symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.k8snode-2:[root@k8snode-2 ~]# yum -y install docker[root@k8snode-2 ~]# systemctl start docker ; systemctl enable dockerCreated symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.设置k8s相关系统内核参数:k8smaster:[root@k8smaster ~]# cat << EOF > /etc/sysctl.d/k8s.conf> net.bridge.bridege-nf-call-iptables = 1> net.bridge.bridege-nf-call-ip6tables = 1> EOF[root@k8smaster ~]# sysctl -pk8snode-1:[root@k8snode-1 ~]# cat << EOF > /etc/sysctl.d/k8s.conf> net.bridge.bridege-nf-call-iptables = 1> net.bridge.bridege-nf-call-ip6tables = 1> EOF[root@k8snode-1 ~]# sysctl -pk8snode-2:[root@k8snode-2 ~]# cat << EOF > /etc/sysctl.d/k8s.conf> net.bridge.bridege-nf-call-iptables = 1> net.bridge.bridege-nf-call-ip6tables = 1> EOF[root@k8snode-2 ~]# sysctl -p安装kubernetes相关组件:k8smaster:[root@k8smaster ~]# yum install -y kubelet-1.13.3 kubeadm-1.11.1 kubectl-1.13.3 –disableexcludes=kubernetes[root@k8smaster ~]# systemctl start kubelet ; systemctl enable kubeletCreated symlink from /etc/systemd/system/multi-user.target.wants/kubelet.service to /etc/systemd/system/kubelet.service.k8snode-1:[root@k8snode-1 ~]# yum install -y kubelet-1.13.3 kubeadm-1.11.1 kubectl-1.13.3 –disableexcludes=kubernetes[root@k8snode-1 ~]# systemctl start kubelet ; systemctl enable kubeletCreated symlink from /etc/systemd/system/multi-user.target.wants/kubelet.service to /etc/systemd/system/kubelet.service.k8snode-2:[root@k8snode-2 ~]# yum install -y kubelet-1.13.3 kubeadm-1.11.1 kubectl-1.13.3 –disableexcludes=kubernetes[root@k8snode-2 ~]# systemctl start kubelet ; systemctl enable kubeletCreated symlink from /etc/systemd/system/multi-user.target.wants/kubelet.service to /etc/systemd/system/kubelet.service.导入所需要的镜像:为什么要导入镜像呢?因为kubeadmin会去谷歌的镜像源上面下载,大家都懂得!所以我下载下来,使用的时候直接导入就好了。所需要的镜像:k8s.gcr.io/kube-apiserver:v1.13.3k8s.gcr.io/kube-controller-manager:v1.13.3k8s.gcr.io/kube-scheduler:v1.13.3k8s.gcr.io/kube-proxy:v1.13.3k8s.gcr.io/etcd:3.2.24k8s.gcr.io/coredns:1.2.6k8s.gcr.io/pause:3.1这里所用到的镜像,我会全部打包到我的github上,需要的可以去下载。导入方式相同,只需要全部导入即可!k8smaster:[root@k8smaster ~]# lsanaconda-ks.cfg images.tar[root@k8smaster ~]# ll总用量 1834184-rw——-. 1 root root 1245 3月 4 17:27 anaconda-ks.cfg-rw-r–r–. 1 root root 1878197248 3月 5 18:31 images.tar[root@k8smaster ~]# docker load < images.tark8snode-1:[root@k8snode-1 ~]# lsanaconda-ks.cfg images.tar[root@k8snode-1 ~]# docker load < images.tark8snode-2:[root@k8snode-2 ~]# lsanaconda-ks.cfg images.tar[root@k8snode-2 ~]# docker load < images.tarmaster上初始化kubeadmin生成node token:使用kubeadm init 初始化环境,–kubernetes-version指定版本,-pod-network-cidr指定虚拟网络的网段,可以随便指定任何网段![root@k8smaster ~]# kubeadm init –kubernetes-version=v1.13.3 –pod-network-cidr=10.244.0.0/16[init] using Kubernetes version: v1.13.3[preflight] running pre-flight checks [WARNING KubernetesVersion]: kubernetes version is greater than kubeadm version. Please consider to upgrade kubeadm. kubernetes version: 1.13.3. Kubeadm version: 1.11.xI0305 19:49:27.250624 5373 kernel_validator.go:81] Validating kernel versionI0305 19:49:27.250718 5373 kernel_validator.go:96] Validating kernel config[preflight/images] Pulling images required for setting up a Kubernetes cluster[preflight/images] This might take a minute or two, depending on the speed of your internet connection[preflight/images] You can also perform this action in beforehand using ‘kubeadm config images pull’[kubelet] Writing kubelet environment file with flags to file “/var/lib/kubelet/kubeadm-flags.env”[kubelet] Writing kubelet configuration to file “/var/lib/kubelet/config.yaml”[preflight] Activating the kubelet service[certificates] Generated ca certificate and key.[certificates] Generated apiserver certificate and key.[certificates] apiserver serving cert is signed for DNS names [k8smaster kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 9.186.137.114][certificates] Generated apiserver-kubelet-client certificate and key.[certificates] Generated sa key and public key.[certificates] Generated front-proxy-ca certificate and key.[certificates] Generated front-proxy-client certificate and key.[certificates] Generated etcd/ca certificate and key.[certificates] Generated etcd/server certificate and key.[certificates] etcd/server serving cert is signed for DNS names [k8smaster localhost] and IPs [127.0.0.1 ::1][certificates] Generated etcd/peer certificate and key.[certificates] etcd/peer serving cert is signed for DNS names [k8smaster localhost] and IPs [9.186.137.114 127.0.0.1 ::1][certificates] Generated etcd/healthcheck-client certificate and key.[certificates] Generated apiserver-etcd-client certificate and key.[certificates] valid certificates and keys now exist in “/etc/kubernetes/pki”[kubeconfig] Wrote KubeConfig file to disk: “/etc/kubernetes/admin.conf”[kubeconfig] Wrote KubeConfig file to disk: “/etc/kubernetes/kubelet.conf”[kubeconfig] Wrote KubeConfig file to disk: “/etc/kubernetes/controller-manager.conf”[kubeconfig] Wrote KubeConfig file to disk: “/etc/kubernetes/scheduler.conf”[controlplane] wrote Static Pod manifest for component kube-apiserver to “/etc/kubernetes/manifests/kube-apiserver.yaml”[controlplane] wrote Static Pod manifest for component kube-controller-manager to “/etc/kubernetes/manifests/kube-controller-manager.yaml”[controlplane] wrote Static Pod manifest for component kube-scheduler to “/etc/kubernetes/manifests/kube-scheduler.yaml”[etcd] Wrote Static Pod manifest for a local etcd instance to “/etc/kubernetes/manifests/etcd.yaml”[init] waiting for the kubelet to boot up the control plane as Static Pods from directory “/etc/kubernetes/manifests”[init] this might take a minute or longer if the control plane images have to be pulled[apiclient] All control plane components are healthy after 19.502118 seconds[uploadconfig] storing the configuration used in ConfigMap “kubeadm-config” in the “kube-system” Namespace[kubelet] Creating a ConfigMap “kubelet-config-1.13” in namespace kube-system with the configuration for the kubelets in the cluster[markmaster] Marking the node k8smaster as master by adding the label “node-role.kubernetes.io/master=’’"[markmaster] Marking the node k8smaster as master by adding the taints [node-role.kubernetes.io/master:NoSchedule][patchnode] Uploading the CRI Socket information “/var/run/dockershim.sock” to the Node API object “k8smaster” as an annotation[bootstraptoken] using token: xjzf96.nv0qhqwj9j47r1tv[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster[bootstraptoken] creating the “cluster-info” ConfigMap in the “kube-public” namespace[addons] Applied essential addon: CoreDNS[addons] Applied essential addon: kube-proxyYour Kubernetes master has initialized successfully!To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/configYou should now deploy a pod network to the cluster.Run “kubectl apply -f [podnetwork].yaml” with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/You can now join any number of machines by running the following on each nodeas root: kubeadm join 9.186.137.114:6443 –token xjzf96.nv0qhqwj9j47r1tv –discovery-token-ca-cert-hash sha256:e386175a5cae597dec6bfeb7c92d01bc5fe052313b50dc48e419057c8c3f824c [root@k8smaster ~]# mkdir -p $HOME/.kube[root@k8smaster ~]# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config[root@k8smaster ~]# sudo chown $(id -u):$(id -g) $HOME/.kube/confignode节点执行kubeadm join加入集群:注意!一定要时间同步,否则就会出问题!报这个错误:[discovery] Failed to request cluster info, will try again: [Get https://9.186.137.114:6443/api/v1/namespaces/kube-public/configmaps/cluster-info: x509: certificate has expired or is not yet valid]k8snode-1:[root@k8snode-1 ~]# kubeadm join 9.186.137.114:6443 –token xjzf96.nv0qhqwj9j47r1tv –discovery-token-ca-cert-hash sha256:e386175a5cae597dec6bfeb7c92d01bc5fe052313b50dc48e419057c8c3f824c[preflight] running pre-flight checks [WARNING RequiredIPVSKernelModulesAvailable]: the IPVS proxier will not be used, because the following required kernel modules are not loaded: [ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh] or no builtin kernel ipvs support: map[ip_vs:{} ip_vs_rr:{} ip_vs_wrr:{} ip_vs_sh:{} nf_conntrack_ipv4:{}]you can solve this problem with following methods: 1. Run ‘modprobe – ’ to load missing kernel modules;2. Provide the missing builtin kernel ipvs supportI0305 20:02:24.453910 5983 kernel_validator.go:81] Validating kernel versionI0305 20:02:24.454026 5983 kernel_validator.go:96] Validating kernel config[discovery] Trying to connect to API Server “9.186.137.114:6443”[discovery] Created cluster-info discovery client, requesting info from “https://9.186.137.114:6443”[discovery] Requesting info from “https://9.186.137.114:6443” again to validate TLS against the pinned public key[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server “9.186.137.114:6443”[discovery] Successfully established connection with API Server “9.186.137.114:6443”[kubelet] Downloading configuration for the kubelet from the “kubelet-config-1.13” ConfigMap in the kube-system namespace[kubelet] Writing kubelet configuration to file “/var/lib/kubelet/config.yaml”[kubelet] Writing kubelet environment file with flags to file “/var/lib/kubelet/kubeadm-flags.env”[preflight] Activating the kubelet service[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap…[patchnode] Uploading the CRI Socket information “/var/run/dockershim.sock” to the Node API object “k8snode-1” as an annotationThis node has joined the cluster: Certificate signing request was sent to master and a response was received. The Kubelet was informed of the new secure connection details.Run ‘kubectl get nodes’ on the master to see this node join the cluster.k8snode-2:[root@k8snode-2 ~]# kubeadm join 9.186.137.114:6443 –token xjzf96.nv0qhqwj9j47r1tv –discovery-token-ca-cert-hash sha256:e386175a5cae597dec6bfeb7c92d01bc5fe052313b50dc48e419057c8c3f824c[preflight] running pre-flight checks [WARNING RequiredIPVSKernelModulesAvailable]: the IPVS proxier will not be used, because the following required kernel modules are not loaded: [ip_vs_rr ip_vs_wrr ip_vs_sh ip_vs] or no builtin kernel ipvs support: map[ip_vs:{} ip_vs_rr:{} ip_vs_wrr:{} ip_vs_sh:{} nf_conntrack_ipv4:{}]you can solve this problem with following methods: 1. Run ‘modprobe – ’ to load missing kernel modules;2. Provide the missing builtin kernel ipvs supportI0305 19:51:39.452856 5036 kernel_validator.go:81] Validating kernel versionI0305 19:51:39.452954 5036 kernel_validator.go:96] Validating kernel config[discovery] Trying to connect to API Server “9.186.137.114:6443”[discovery] Created cluster-info discovery client, requesting info from “https://9.186.137.114:6443”[discovery] Requesting info from “https://9.186.137.114:6443” again to validate TLS against the pinned public key[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server “9.186.137.114:6443”[discovery] Successfully established connection with API Server “9.186.137.114:6443”[kubelet] Downloading configuration for the kubelet from the “kubelet-config-1.13” ConfigMap in the kube-system namespace[kubelet] Writing kubelet configuration to file “/var/lib/kubelet/config.yaml”[kubelet] Writing kubelet environment file with flags to file “/var/lib/kubelet/kubeadm-flags.env”[preflight] Activating the kubelet service[tlsbootstrap] Waiting for the kubelet to perform the TLS Bootstrap…[patchnode] Uploading the CRI Socket information “/var/run/dockershim.sock” to the Node API object “k8snode-2” as an annotationThis node has joined the cluster: Certificate signing request was sent to master and a response was received.* The Kubelet was informed of the new secure connection details.Run ‘kubectl get nodes’ on the master to see this node join the cluster.安装Flannel网络:k8smaster:[root@k8smaster ~]# kubectl apply -f kube-flannel.ymlclusterrole.rbac.authorization.k8s.io/flannel createdclusterrolebinding.rbac.authorization.k8s.io/flannel createdserviceaccount/flannel createdconfigmap/kube-flannel-cfg createddaemonset.extensions/kube-flannel-ds-amd64 createddaemonset.extensions/kube-flannel-ds-arm64 createddaemonset.extensions/kube-flannel-ds-arm createddaemonset.extensions/kube-flannel-ds-ppc64le createddaemonset.extensions/kube-flannel-ds-s390x created查看K8S集群状态:k8smaster:[root@k8smaster ~]# kubectl get pods -n kube-systemNAME READY STATUS RESTARTS AGEcoredns-86c58d9df4-kmfct 1/1 Running 0 8m26scoredns-86c58d9df4-qn2k2 1/1 Running 0 8m26setcd-k8smaster 1/1 Running 0 8m35skube-apiserver-k8smaster 1/1 Running 1 8m10skube-controller-manager-k8smaster 1/1 Running 0 7m43skube-flannel-ds-amd64-9rmfz 1/1 Running 0 5m9skube-flannel-ds-amd64-vnwtf 1/1 Running 0 12skube-flannel-ds-amd64-x7q4s 1/1 Running 0 51skube-proxy-7zl9n 1/1 Running 0 7m31skube-proxy-t2sx9 1/1 Running 0 8m27skube-proxy-txsfr 1/1 Running 0 7m27skube-scheduler-k8smaster 1/1 Running 0 8m56s到这里kubeadmin就部署完毕k8s集群了!希望大家可以给我指出问题,我们一起前进!谢谢大家! ...

March 6, 2019 · 8 min · jiezi

docker 里几个基本概念的简单类比

这篇转载别人的,类比的相当不错,有助于理解docker首先说明一下,这是一位 docker 新手对于 docker 的粗浅理解。如有不对还请谅解。我很早之前就尝试过使用 docker,然而由于术语的差异,导致我每次运行东西时都傻乎乎地创建了一个新的容器……现在感觉用法终于是弄对了,所以整理一下,将其类比到 Linux 上的普通软件的概念上。image相当于软件分发中的软件(安装)包。Dockerfile跟 PKGBUILD 类似,是用于制作一个 image 的打包脚本。用 docker build -t name:tag . 就可以制作。container(容器)一个容器就像是一个安装好了的软件包。该软件已经准备好,随时可以运行了。docker run「安装」指定的 image。也就是从 image 制作出容器来,顺带着进行首次运行。如果反复使用,会把同一个软件给安装多次。docker start就像是「运行」一个已经安装好的软件,容器跑起来了。之前容器的状态(文件的修改)也会生效。docker ps列出运行中或者已安装(带 -a 参数)的软件们。前者和 UNIX 命令 ps 类似,后者则没什么相似之处了。docker exec在正在运行的软件的环境内执行命令。有点类似于 ssh。repository跟 Linux 的包含众多软件的软件源并不一样。这个东西跟软件名类似,用于标识为特定功能的 image 集。发布出来的 repository 名的格式通常是 owner/name,跟 GitHub 差不多的。tag软件的版本,跟什么 lite、pro、beta 之类区分类似。它并不是用于分类的标签,也不是 git 中对于指定版本的不变的称呼。它更像是 git 的分支在某些情况下的作用,比如 latest tag 就跟 git 仓库的 master 分支一样,总是指向最新的版本。我经过以上这样的映射之后,docker 理解起来就容易多了,行为也更符合预期。

March 5, 2019 · 1 min · jiezi

Docker相关环境全套安装文档兼小技能

以下环境皆为ubuntu16.04,主要安装docker,docker-compose,docker仓库等。Docker安装参考官方A: 有源安装sudo apt-get remove docker docker-engine docker.iosudo apt-get updatesudo apt-get install \ apt-transport-https \ ca-certificates \ curl \ software-properties-commoncurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -sudo apt-key fingerprint 0EBFCD88sudo apt-get updateapt-cache madison docker-cesudo apt-get install docker-ce=docker-ce=18.03.0ce-0ubuntusudo docker run hello-world你也可以使用这个脚本安装:sudo curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet | sh -B: 无源安装先下载已编译包。wget https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/docker-ce_18.06.1~ce~3-0~ubuntu_amd64.debsudo dpkg -i docker-ce_18.06.1ce3-0~ubuntu_amd64.debsudo docker run hello-world在2017年的3月1号之后,Docker的版本命名开始发生变化,同时将CE版本和EE版本进行分开, 18.03表示18年3月发布。离线安装命名前docker(docker-engine depends on libltdl7 (>= 2.4.6);):wget https://apt.dockerproject.org/repo/pool/main/d/docker-engine/docker-engine_1.12.1-0~xenial_amd64.debwget http://archive.ubuntu.com/ubuntu/pool/main/libt/libtool/libltdl7_2.4.6-4_amd64.debdpkg -i *.debDocker-compose安装我们可以使用docker-compose来对多个容器进行管理。离线安装:wget https://github.com/docker/compose/releases/download/1.8.1/docker-compose-`uname -s-uname -m` mv docker-compose-Linux-x86_64 /usr/local/bin/docker-composechmod +x /usr/local/bin/docker-composeDocker Hub安装VMware公司开源的企业级的Docker Registry管理项目:harbor。有了仓库,我们可以直接push镜像上去,然后从其他地方拉,不用借助U盘。安装参考: 官方,文章环境依赖较新的docker1.10+和docker-compose1.60+和python2.7,我们选择离线安装方式:wget https://storage.googleapis.com/harbor-releases/release-1.6.0/harbor-offline-installer-v1.6.0-rc3.tgztar xvf harbor-offline-installer-v1.6.0-rc3.tgzcd harbor编辑docker-compose.yml: proxy: image: goharbor/nginx-photon:v1.6.0 container_name: nginx restart: always volumes: - ./common/config/nginx:/etc/nginx:z networks: - harbor ports: - 8888:80 - 1443:443 - 4443:4443修改common/templates/registry/config.yml文件加入8888端口:vim common/templates/registry/config.ymlauth: token: issuer: harbor-token-issuer realm: $public_url:8888/service/token rootcertbundle: /etc/registry/root.crt service: harbor-registry编辑harbor.cfg:hostname = 192.168.152.12harbor_admin_password = admin启动并登陆:sudo suufw allow 8888./preparedocker-compose up -d打开:http://192.168.152.12:8888,账号|密码:adminDocker配置你可以配置某些仓库地址(第一个是阿里云加速仓库地址,第二个忽略https安全)sudo mkdir -p /etc/dockersudo tee /etc/docker/daemon.json <<-‘EOF’{ “registry-mirrors”: [“https://ztndgg1k.mirror.aliyuncs.com”], “insecure-registries”: [“192.168.0.88:8888”]}EOFsudo systemctl daemon-reloadsudo systemctl restart docker然后登录推送:sudo docker login http://192.168.0.88:8888sudo docker tag mysql:5.7 192.168.0.88:8888/public/mysql:5.7docker push 192.168.0.88:8888/public/mysql:5.7Docker特定场景使用离线镜像如果不能访问外网,那么可以用save和load来保存和加载镜像docker save xxx:1.0 > /root/api1.0.tardocker load < /root/api1.0.tardocker images ...

March 5, 2019 · 1 min · jiezi

docker时区设置

前几天用docker部署mysql,在web服务将数据写入mysql时,发现时间相差了8个小时,随后在网上查找解决办法。若用docker run 命令来运行docker的话,可以直接通过-v,将宿主机的时间与本地时间绑定到容器中,这样时间就会跟宿主机一样,例子:docker run -v /etc/timezone:/etc/timezone -v /etc/localtime:/etc/localtime -ti nginx bash然后运行date,查看docker的时间;也可以通过定义Dockerfile的ENV,设置TZ为Asia/shanghai,代码块如下:RUN apk add –no-cache tzdataENV TZ=Asia/Shanghai另外docker-compose.yml文件来定义的话,跟Dockerfile是一样,在environment添加,代码块如下:environment: TZ: Asia/Shanghai其中tzdata这个依赖是必须的,没有这个,即使设置了ENV TZ=Asia/Shanghai,有些镜像的时间也不会变为北京时间,我用golang官方的镜像来构建时就遇到这个问题。据了解是因为有些容器里没有/usr/share/zoneinfo目录。参考:https://github.com/gliderlabs…https://stackoverflow.com/que...https://blog.csdn.net/dounine...https://www.cnblogs.com/linux…

March 4, 2019 · 1 min · jiezi

安装docker和docker-compose

环境:centos7,参考官方文档:https://docs.docker.com/insta…第一步:删除旧版本和相关依赖,运行命令:yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine第二步,安装依赖,命令:yum install -y yum-utils \ device-mapper-persistent-data \ lvm2第三步,配置稳定的repositories,命令:yum-config-manager \ –add-repo \ https://download.docker.com/linux/centos/docker-ce.repo第四步,安装docker,命令:yum install docker-ce docker-ce-cli containerd.io完成后通过docker version命令看到docker信息:启动:systemctl start docker开机启动:systemctl enable docker接下来安装docker-compose,运行命令:curl -L “https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)” -o /usr/local/bin/docker-compose给docker-composem,运行命令:chmod +x /usr/local/bin/docker-compose检查,运行docker-compose –version,如下:至此,docker和docker-compose安装成功;docker参考文档:https://docs.docker.com/get-s…docker-compose文档:https://docs.docker.com/compo…

March 4, 2019 · 1 min · jiezi

使用 TensorFlow Serving 和 Docker 快速部署机器学习服务

从实验到生产,简单快速部署机器学习模型一直是一个挑战。这个过程要做的就是将训练好的模型对外提供预测服务。在生产中,这个过程需要可重现,隔离和安全。这里,我们使用基于Docker的TensorFlow Serving来简单地完成这个过程。TensorFlow 从1.8版本开始支持Docker部署,包括CPU和GPU,非常方便。获得训练好的模型获取模型的第一步当然是训练一个模型,但是这不是本篇的重点,所以我们使用一个已经训练好的模型,比如ResNet。TensorFlow Serving 使用SavedModel这种格式来保存其模型,SavedModel是一种独立于语言的,可恢复,密集的序列化格式,支持使用更高级别的系统和工具来生成,使用和转换TensorFlow模型。这里我们直接下载一个预训练好的模型:$ mkdir /tmp/resnet$ curl -s https://storage.googleapis.com/download.tensorflow.org/models/official/20181001_resnet/savedmodels/resnet_v2_fp32_savedmodel_NHWC_jpg.tar.gz | tar –strip-components=2 -C /tmp/resnet -xvz如果是使用其他框架比如Keras生成的模型,则需要将模型转换为SavedModel格式,比如:from keras.models import Sequentialfrom keras import backend as Kimport tensorflow as tfmodel = Sequential()# 中间省略模型构建# 模型转换为SavedModelsignature = tf.saved_model.signature_def_utils.predict_signature_def( inputs={‘input_param’: model.input}, outputs={’type’: model.output})builder = tf.saved_model.builder.SavedModelBuilder(’/tmp/output_model_path/1/’)builder.add_meta_graph_and_variables( sess=K.get_session(), tags=[tf.saved_model.tag_constants.SERVING], signature_def_map={ tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: signature })builder.save()下载完成后,文件目录树为:$ tree /tmp/resnet/tmp/resnet└── 1538687457 ├── saved_model.pb └── variables ├── variables.data-00000-of-00001 └── variables.index部署模型使用Docker部署模型服务:$ docker pull tensorflow/serving$ docker run -p 8500:8500 -p 8501:8501 –name tfserving_resnet --mount type=bind,source=/tmp/resnet,target=/models/resnet -e MODEL_NAME=resnet -t tensorflow/serving其中,8500端口对于TensorFlow Serving提供的gRPC端口,8501为REST API服务端口。-e MODEL_NAME=resnet指出TensorFlow Serving需要加载的模型名称,这里为resnet。上述命令输出为2019-03-04 02:52:26.610387: I tensorflow_serving/model_servers/server.cc:82] Building single TensorFlow model file config: model_name: resnet model_base_path: /models/resnet2019-03-04 02:52:26.618200: I tensorflow_serving/model_servers/server_core.cc:461] Adding/updating models.2019-03-04 02:52:26.618628: I tensorflow_serving/model_servers/server_core.cc:558] (Re-)adding model: resnet2019-03-04 02:52:26.745813: I tensorflow_serving/core/basic_manager.cc:739] Successfully reserved resources to load servable {name: resnet version: 1538687457}2019-03-04 02:52:26.745901: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: resnet version: 1538687457}2019-03-04 02:52:26.745935: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: resnet version: 1538687457}2019-03-04 02:52:26.747590: I external/org_tensorflow/tensorflow/contrib/session_bundle/bundle_shim.cc:363] Attempting to load native SavedModelBundle in bundle-shim from: /models/resnet/15386874572019-03-04 02:52:26.747705: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:31] Reading SavedModel from: /models/resnet/15386874572019-03-04 02:52:26.795363: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:54] Reading meta graph with tags { serve }2019-03-04 02:52:26.828614: I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA2019-03-04 02:52:26.923902: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:162] Restoring SavedModel bundle.2019-03-04 02:52:28.098479: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:138] Running MainOp with key saved_model_main_op on SavedModel bundle.2019-03-04 02:52:28.144510: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:259] SavedModel load for tags { serve }; Status: success. Took 1396689 microseconds.2019-03-04 02:52:28.146646: I tensorflow_serving/servables/tensorflow/saved_model_warmup.cc:83] No warmup data file found at /models/resnet/1538687457/assets.extra/tf_serving_warmup_requests2019-03-04 02:52:28.168063: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: resnet version: 1538687457}2019-03-04 02:52:28.174902: I tensorflow_serving/model_servers/server.cc:286] Running gRPC ModelServer at 0.0.0.0:8500 …[warn] getaddrinfo: address family for nodename not supported2019-03-04 02:52:28.186724: I tensorflow_serving/model_servers/server.cc:302] Exporting HTTP/REST API at:localhost:8501 …[evhttp_server.cc : 237] RAW: Entering the event loop …我们可以看到,TensorFlow Serving使用1538687457作为模型的版本号。我们使用curl命令来查看一下启动的服务状态,也可以看到提供服务的模型版本以及模型状态。$ curl http://localhost:8501/v1/models/resnet{ “model_version_status”: [ { “version”: “1538687457”, “state”: “AVAILABLE”, “status”: { “error_code”: “OK”, “error_message”: "" } } ]}查看模型输入输出很多时候我们需要查看模型的输出和输出参数的具体形式,TensorFlow提供了一个saved_model_cli命令来查看模型的输入和输出参数:$ saved_model_cli show –dir /tmp/resnet/1538687457/ –allMetaGraphDef with tag-set: ‘serve’ contains the following SignatureDefs:signature_def[‘predict’]: The given SavedModel SignatureDef contains the following input(s): inputs[‘image_bytes’] tensor_info: dtype: DT_STRING shape: (-1) name: input_tensor:0 The given SavedModel SignatureDef contains the following output(s): outputs[‘classes’] tensor_info: dtype: DT_INT64 shape: (-1) name: ArgMax:0 outputs[‘probabilities’] tensor_info: dtype: DT_FLOAT shape: (-1, 1001) name: softmax_tensor:0 Method name is: tensorflow/serving/predictsignature_def[‘serving_default’]: The given SavedModel SignatureDef contains the following input(s): inputs[‘image_bytes’] tensor_info: dtype: DT_STRING shape: (-1) name: input_tensor:0 The given SavedModel SignatureDef contains the following output(s): outputs[‘classes’] tensor_info: dtype: DT_INT64 shape: (-1) name: ArgMax:0 outputs[‘probabilities’] tensor_info: dtype: DT_FLOAT shape: (-1, 1001) name: softmax_tensor:0 Method name is: tensorflow/serving/predict注意到signature_def,inputs的名称,类型和输出,这些参数在接下来的模型预测请求中需要。使用模型接口预测:REST和gRPCTensorFlow Serving提供REST API和gRPC两种请求方式,接下来将具体这两种方式。REST我们下载一个客户端脚本,这个脚本会下载一张猫的图片,同时使用这张图片来计算服务请求时间。$ curl -o /tmp/resnet/resnet_client.py https://raw.githubusercontent.com/tensorflow/serving/master/tensorflow_serving/example/resnet_client.py以下脚本使用requests库来请求接口,使用图片的base64编码字符串作为请求内容,返回图片分类,并计算了平均处理时间。from future import print_functionimport base64import requests# The server URL specifies the endpoint of your server running the ResNet# model with the name “resnet” and using the predict interface.SERVER_URL = ‘http://localhost:8501/v1/models/resnet:predict’# The image URL is the location of the image we should send to the serverIMAGE_URL = ‘https://tensorflow.org/images/blogs/serving/cat.jpg'def main(): # Download the image dl_request = requests.get(IMAGE_URL, stream=True) dl_request.raise_for_status() # Compose a JSON Predict request (send JPEG image in base64). jpeg_bytes = base64.b64encode(dl_request.content).decode(‘utf-8’) predict_request = ‘{“instances” : [{“b64”: “%s”}]}’ % jpeg_bytes # Send few requests to warm-up the model. for _ in range(3): response = requests.post(SERVER_URL, data=predict_request) response.raise_for_status() # Send few actual requests and report average latency. total_time = 0 num_requests = 10 for _ in range(num_requests): response = requests.post(SERVER_URL, data=predict_request) response.raise_for_status() total_time += response.elapsed.total_seconds() prediction = response.json()[‘predictions’][0] print(‘Prediction class: {}, avg latency: {} ms’.format( prediction[‘classes’], (total_time*1000)/num_requests))if name == ‘main’: main()输出结果为$ python resnet_client.pyPrediction class: 286, avg latency: 210.12310000000002 msgRPC让我们下载另一个客户端脚本,这个脚本使用gRPC作为服务,传入图片并获取输出结果。这个脚本需要安装tensorflow-serving-api这个库。$ curl -o /tmp/resnet/resnet_client_grpc.py https://raw.githubusercontent.com/tensorflow/serving/master/tensorflow_serving/example/resnet_client_grpc.py$ pip install tensorflow-serving-api脚本内容:from future import print_function# This is a placeholder for a Google-internal import.import grpcimport requestsimport tensorflow as tffrom tensorflow_serving.apis import predict_pb2from tensorflow_serving.apis import prediction_service_pb2_grpc# The image URL is the location of the image we should send to the serverIMAGE_URL = ‘https://tensorflow.org/images/blogs/serving/cat.jpg'tf.app.flags.DEFINE_string('server', ’localhost:8500’, ‘PredictionService host:port’)tf.app.flags.DEFINE_string(‘image’, ‘’, ‘path to image in JPEG format’)FLAGS = tf.app.flags.FLAGSdef main(_): if FLAGS.image: with open(FLAGS.image, ‘rb’) as f: data = f.read() else: # Download the image since we weren’t given one dl_request = requests.get(IMAGE_URL, stream=True) dl_request.raise_for_status() data = dl_request.content channel = grpc.insecure_channel(FLAGS.server) stub = prediction_service_pb2_grpc.PredictionServiceStub(channel) # Send request # See prediction_service.proto for gRPC request/response details. request = predict_pb2.PredictRequest() request.model_spec.name = ‘resnet’ request.model_spec.signature_name = ‘serving_default’ request.inputs[‘image_bytes’].CopyFrom( tf.contrib.util.make_tensor_proto(data, shape=[1])) result = stub.Predict(request, 10.0) # 10 secs timeout print(result)if name == ‘main’: tf.app.run()输出的结果可以看到图片的分类,概率和使用的模型信息:$ python resnet_client_grpc.pyoutputs { key: “classes” value { dtype: DT_INT64 tensor_shape { dim { size: 1 } } int64_val: 286 }}outputs { key: “probabilities” value { dtype: DT_FLOAT tensor_shape { dim { size: 1 } dim { size: 1001 } } float_val: 2.4162832232832443e-06 float_val: 1.9012182974620373e-06 float_val: 2.7247710022493266e-05 float_val: 4.426385658007348e-07 …(中间省略) float_val: 1.4636580090154894e-05 float_val: 5.812107133351674e-07 float_val: 6.599806511076167e-05 float_val: 0.0012952701654285192 }}model_spec { name: “resnet” version { value: 1538687457 } signature_name: “serving_default”}性能通过编译优化的TensorFlow Serving二进制来提高性能TensorFlows serving有时会有输出如下的日志:Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMATensorFlow Serving已发布Docker镜像旨在尽可能多地使用CPU架构,因此省略了一些优化以最大限度地提高兼容性。如果你没有看到此消息,则你的二进制文件可能已针对你的CPU进行了优化。根据你的模型执行的操作,这些优化可能会对你的服务性能产生重大影响。幸运的是,编译优化的TensorFlow Serving二进制非常简单。官方已经提供了自动化脚本,分以下两部进行:# 1. 编译开发版本$ docker build -t $USER/tensorflow-serving-devel -f Dockerfile.devel https://github.com/tensorflow/serving.git#:tensorflow_serving/tools/docker# 2. 生产新的镜像$ docker build -t $USER/tensorflow-serving –build-arg TF_SERVING_BUILD_IMAGE=$USER/tensorflow-serving-devel https://github.com/tensorflow/serving.git#:tensorflow_serving/tools/docker之后,使用新编译的$USER/tensorflow-serving重新启动服务即可。总结上面我们快速实践了使用TensorFlow Serving和Docker部署机器学习服务的过程,可以看到,TensorFlow Serving提供了非常方便和高效的模型管理,配合Docker,可以快速搭建起机器学习服务。参考Serving ML Quickly with TensorFlow Serving and DockerTrain and serve a TensorFlow model with TensorFlow ServingGitHub repo: qiwihui/blogFollow me: @qiwihuiSite: QIWIHUI ...

March 4, 2019 · 4 min · jiezi

初试drone1.0+gitea—docker安装

最近研究了一下drone,尝试将drone和gitea集合在一起,做CI服务;drone官方是有说明文档,支持gitea的,见drone server的安装文档;但是我个人觉得不够详细,在这里记下安装过程。先上docker-compose.yaml文件:version: “3.6"services: mysql: image: mysql:${MYSQL_VERSION} container_name: mysql ports: - “${MYSQL_HOST_PORT}:3306” volumes: - ${MYSQL_CONF_FILE}:/etc/mysql/conf.d/mysql.cnf:ro - ${MYSQL_DATA_DIR}:/var/lib/mysql/:rw restart: always networks: - default environment: MYSQL_ROOT_PASSWORD: “${MYSQL_ROOT_PASSWORD}” TZ: Asia/Shanghai gitea: image: gitea/gitea:latest container_name: gitea environment: TZ: Asia/Shanghai volumes: - /var/lib/gitea:/data restart: always networks: - default ports: - “10022:22” - “10080:3000” drone-server: image: drone/drone:latest container_name: drone-server ports: - “8080:80” - “8000:8000” - “9000:9000” volumes: - /var/run/docker.sock:/var/run/docker.sock - /dnmp/drone/:/var/lib/drone/:rw restart: always environment: - DRONE_GITEA_SERVER=http://192.168.88.190:10080 #- DRONE_GITEA_SERVER=http://127.0.0.1:10080 - DRONE_GIT_ALWAYS_AUTH=false - DRONE_RUNNER_CAPACITY=2 - DRONE_SERVER_HOST=192.168.88.190 - DRONE_SERVER_PROTO=http - DRONE_RPC_SECRET=9c3921e3e748aff725d2e16ef31fbc42 - DRONE_TLS_AUTOCERT=false - DRONE_LOGS_DEBUG=true - TZ=Asia/Shanghai restart: always networks: - default drone-agent: image: drone/agent:latest container_name: drone-agent command: agent restart: always depends_on: - drone-server volumes: - /var/run/docker.sock:/var/run/docker.sock environment: #- DRONE_RPC_SERVER=http://192.168.88.190:9000 - DRONE_RPC_SERVER=drone-server:9000 - DRONE_RPC_SECRET=9c3921e3e748aff725d2e16ef31fbc42 - DRONE_RUNNER_CAPACITY=2 - DRONE_RUNNER_NAME=192.168.88.190 - DRONE_LOGS_DEBUG=true - TZ=Asia/Shanghai restart: always #detach: true networks: - defaultnetworks: default:我的环境是本地虚拟机centos7,上面IP地址是虚拟机的ip地址。因为gitea是采用mysql数据库,所以要启动一个msyql的容器服务;通过命名docker-compose -f docker-compose.yml build命令先将镜像构建,其实都是用官方的镜像,构建这一步是很快的,只是做一个检查;然后docker-compose -f docker-compose.yml up -d 将容器运行起来,通过docker ps -a可以查看到容器的状态:status这一栏都是正常的,表明服务就可以使用了。先要配置gitea,通过http://192.168.88.190:10080/进入gitea的web页面,点击登录后,就会出现安装向导,填写信息,点击安装,等几分钟就ok了。安装的细节稍微要注意填写的地址和端口,容器内部要填写3000,对外开放的是10080。记住设置的管理员账号和密码,后面drone登录时也要用到。然后通过http://192.168.88.190:8080/进入drone的web页面,如下:输入刚才在gitea设置的管理员账号和密码,就可以登录成功,就能看到如下页面:刚开始repositories是空的,点击右上方的sync按钮,等一分钟就会自动同步gitea项目过来;同步过来的gitea仓库,默认状态是没开启的。可以点击setting页面,进入设置,如下:填写配置文件名字,默认是.drone.yml。点击save,就会开启。点击save的时候,drone会将配置信息同步给你的gitea,gitea项目那边会响应,然后配置一个webhook,这里有个坑,后面再讲。顺利的话,你在gitea项目添加.drone.yal后,就会触发构建,我在gitea仓库test下建的.drone.yml文件:workspace: base: /go path: src/web_apipipeline: build: image: golang environment: TZ: Asia/Shanghai commands: - pwd - go env - date - go version - go build - ls -a提交gitea后,查看drone的状态:第一步是clone代码,第二步是build,因为我的.drone.yml超级简单,没有后面的步骤,只是测试用的。第二步build的结果:看到正常运行,drone+gitea的服务就初步搭建起来了。后面通过在.drone.yml添加内容和服务,就能达到快速CI/CD的效果。顺便记录一下踩的坑,刚开始我按照drone集成gitea的安装文档,将docker-compose.yml文件写好,运行docker-compose -f docker-compose.yml up -d 将容器运行起来,通过docker ps -a看到drone-server容器怎样都起不来,docker logs drone-server也看不出报错。在网上找到drone+gitea的贴子,大部分都是drone0.8版本,鉴于此,我将drone的latest改为0.8,安装0.8版本的配置,重新运行,竟然跑起来了。跑起来后,我就摸索drone的配置和服务,后面发现1.0版本有一些配置跟0.8不一样,官方有说明,但不明显。见:1.0版本跑不起来的配置,是因为drone-server的服务,80端口没对外开放,据我目前的了解,1.0版本默认是开放80端口,我当时按照0.8版本的端口,配置了"8000:8000"和"9000:9000”,就差80端口,所以服务一直不正常。这里也会涉及到gitea的webhook,上面说的在drone将gitea设置为开启状态时,这两者会交互的,我们看下gitea的webhook:drone关联gitea项目时,就会有添加这个,但是端口不是8080,默认是80,就是没有端口的,类似:http://192.168.88.190/hook?se…。点击修改,加上前面设置的端口8080,点击保存,然后test 一下webhook,就能看到效果。至此,drone1.0+gitea,docker部署安装的步骤就完成了。drone更多细节,还有待继续研究。 ...

March 4, 2019 · 1 min · jiezi

用Docker搭建Laravel和Vue项目的开发环境

在这篇文章中我们将通过Docker在个人本地电脑上构建一个快速、轻量级、不依赖本地电脑所安装的任何开发套件的可复制的Laravel和Vue项目的开发环境(开发环境的所有依赖都安装在Docker构建容器里),加入Vue只是因为有的项目里会在Laravel项目中使用Vue做前后端分离开发,开发环境中需要安装前端开发需要的工具集,当然前后端也可以分成两个项目开发,这个话题不在本篇文章的讨论范围内。所以我们的目标是:不在本地安装Mamp/Wamp这样的软件不使用类似Vagrant这样的虚拟机不在本地电脑全局安装PHP开发所需要的工具集不在本地电脑全局安装前端开发所需要的工具集不在本地电脑全局安装Mysql和Nginx开始前你需要先去安装一个Docker客户端,Docker的官网中有详细的安装方法。第一步:获取Laravel的源码包因为我们电脑上不安装Composer,所以就不能使用Composer来创建Laravel项目了, 这里我使用cURL直接从github上下载了最新的Laravel源码包,你也可以使用wget或者git clone 来获取源码包。curl -L -O https://github.com/laravel/laravel/archive/v5.5.0.tar.gz /&& tar -zxvf v5.5.0.tar.gz /&& rm v5.5.0.tar.gz上面的命令在curl下载完源码包后会解压源码压缩包,解压完成后在把源码压缩包v5.5.0.tar.gz删掉,执行完后你会看到一个laravel-5.5.0的项目目录。第二步:添加docker-compose.yml在项目中创建docker-compose.yml文件。Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。在这里我们会用到四个容器分别将PHP、Mysql、Nginx放在四个不同的容器中,通过compose`将四个应用容器关联到一起组成项目。编排文件的开头如下:version: ‘2’services: # our services will go here在编排文件中,把每个容器叫做一个服务,services下定义整个应用中用到的所有服务(即容器)。App服务APP服务的容器将执行我们项目中的代码。app: build: context: ./ dockerfile: app.dockerfile working_dir: /var/www volumes: - ./:/var/www environment: - “DB_PORT=3306” - “DB_HOST=database"Notes:我们使用app.dockerfile这个镜像文件来构建我们的App容器,在镜像文件中我们会对项目中用到的PHP模块镜像配置,也会额外安装NPM用来构建前端代码。working_dir: /var/www把工作目录设置成了/var/www,在容器中项目代码将会被放在/var/www目录下面,包括使用docker exec app执行的命令也都是以/var/www为当前工作目录的。volumes是容器内数据卷所挂载路径设置,在这里我们只定义一个数据卷,把宿主机项目目录挂到在容器中的/var/www上,这样我们在本地电脑对项目代码进行的更改就会马上同步到容器中去,反过来也是一样,容器中对代码做的更改也会及时反馈到本地电脑的项目中。environment设置环境变量名,这里我们设置了DB_PORT和DB_HOST 这样就不用修改项目中的.env文件里关于这两项的值了,当然任何你需要在开发环境单独设置的环境变量都可以写到这里,Laravel读取配置使用的DotEnv会检测是否系统有指定环境变量的设置,有的话就不会在去读取.env文件了。现在我们需要创建上面build环节中提到的app.dockerfile这个文件了,具体内容如下:FROM php:7.1.22-fpm# Update packagesRUN apt-get update# Install PHP and composer dependenciesRUN apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev# Clear out the local repository of retrieved package files# RUN apt-get clean# Install needed extensions# Here you can install any other extension that you need during the test and deployment processRUN apt-get clean; docker-php-ext-install pdo pdo_mysql mcrypt zip gd pcntl opcache bcmath# Installs Composer to easily manage your PHP dependencies.RUN curl –silent –show-error https://getcomposer.org/installer | php – –install-dir=/usr/local/bin –filename=composer# Install NodeRUN apt-get update &&\ apt-get install -y –no-install-recommends gnupg &&\ curl -sL https://deb.nodesource.com/setup_10.x | bash - &&\ apt-get update &&\ apt-get install -y –no-install-recommends nodejs &&\ npm config set registry https://registry.npm.taobao.org –global &&\ npm install –global gulp-cliCMD php-fpmNotes:我在这里先将NPM和Composer装到了app容器中,因为在开发时经常需要执行他们,如果发布到生产环境,一般是使用单独的composer对项目代码进行构建而不是放在运行应用的容器里,容器的核心思想之一就是保持单一,这样才能做到快速增加相同角色的容器。Web服务接下来,我们需要配置一个Web服务器用,我们把这个容器在编排文件中命名成webweb: build: context: ./ dockerfile: web.dockerfile working_dir: /var/www volumes_from: - app ports: - 8080:80Notes:volumes_from用来复用在app服务中定义的数据卷路径通过ports将本地电脑的8080端口映射到web容器的80端口,这样在开发环境中我们就不用设置hosts文件,直接通过IP加端口就能访问服务了。Web服务器选用nginx,所以我们需要用一个nginx镜像文件来构建这个容器,在这之前我们需要在nginx镜像的基础上再设置一下项目中用到的vhost,所以我们需要一个web.dockerfile文件,它的定义如下:FROM nginx:1.10ADD vhost.conf /etc/nginx/conf.d/default.conf根据镜像文件的定义,我们把项目中的vhost.conf复制到了容器的/etc/nginx/conf.d/default.conf中,这样基本的nginx配置就配置好了,vhost.conf中的定义如下:server { listen 80; index index.php index.html; root /var/www/public; location / { try_files $uri /index.php?$args; } location ~ .php$ { fastcgi_split_path_info ^(.+.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; }}Notes:因为是开发环境我们就只进行最简单的配置,不做调优考虑了。fastcgi_pass app:9000; nginx将对PHP的请求通过fastcgi传递给了app服务的9000端口,docker-compose会自动把services中定义的容器服务连接起来,各个服务相互之间使用服务名称引用。Mysql服务接下来我们将配置Mysql服务,与上面两个服务有点不一样的是,在PHP-FPM和Nginx的容器中,我们配置本地电脑的文件可以同步到容器中供容器访问,这让我们开发时对文件作的更改能够快速的在容器中得到反馈加快我们的开发过程。但是在数据库容器中我们希望容器中创建的文件能够持久化(默认容器销毁时,容器内创建的文件也会被销毁),我们可以通过Docker的数据卷来实现上述功能,只不过这次不用再把本地电脑的文件挂在到数据卷上了,Docker客户端会管理创建的数据卷的在本地电脑上具体存储的位置。下面是编排文件中对database服务的设置version: ‘2’services: database: image: mysql:5.7 volumes: - dbdata:/var/lib/mysql environment: - “MYSQL_DATABASE=homestead” - “MYSQL_USER=homestead” - “MYSQL_PASSWORD=secret” - “MYSQL_ROOT_PASSWORD=secret” ports: - “33061:3306"volumes: dbdata:Notes:在文件的最下面我们通过volumes命令创建了一个名为dbdata的数据卷(dbdata后面的冒号是有意写上去的,这是YML文件的一个语法限制,不用太关心)定义完数据卷后,在上面我们使用<name>:<dir>的格式,通知Docker,将dbdata数据卷挂在到容器中的/var/lib/mysql目录上environments中设置的是Mysql的docker镜像需要的四个必要参数。ports端口映射中,我们将本地电脑的33061端口映射到容器的3306端口,这样我们就能通过电脑上的数据库工具连接到docker内的Mysql了。将所有服务编排到一起下面是完整的docker-compose.yml文件,通过编排文件我们将三个应用容器关联在一起组成了项目的服务端version: ‘2’services: # The Application app: build: context: ./ dockerfile: app.dockerfile working_dir: /var/www volumes: - ./:/var/www environment: - “DB_PORT=3306” - “DB_HOST=database” # The Web Server web: build: context: ./ dockerfile: web.dockerfile working_dir: /var/www volumes_from: - app ports: - 8080:80 # The Database database: image: mysql:5.6 volumes: - dbdata:/var/lib/mysql environment: - “MYSQL_DATABASE=homestead” - “MYSQL_USER=homestead” - “MYSQL_PASSWORD=secret” - “MYSQL_ROOT_PASSWORD=secret” ports: - “33061:3306"volumes: dbdata:启动服务按照上面的步骤配置好编排文件还有指定的docker镜像文件后,我们就可以通过下面的命令启动服务了,执行完后会启动上面文件里定义的三个服务。docker-compose up -d 第一次启动时,由于docker客户端要下载上面提到的三个镜像并且构建服务所以启动速度会慢一些,等到下载完镜像并构建完成后,以后的启动都会非常快。初始化Laravel项目启动完服务后我们可以初始化Laravel项目了,步骤跟官方文档里介绍的一样,但是需要在启动的app服务的容器里执行:docker-compose exec app composer installdocker-compose exec app npm install // 如果包含前端项目的话再执行相关命令docker-compose exec app cp .env.example .envdocker-compose exec app php artisan key:generatedocker-compose exec app php artisan optimizedocker-compose exec app php artisan migrate –seeddocker-compose exec app php artisan make:controller MyControllerNotes:docker-compose exec 将命令发送到指定的容器中去执行app是定义在docker-compose.yml中的一个服务,它是一个运行着php-fpm的容器php artisan migrate 是要在容器里执行的命令查看nginx日志的方法:docker ps 找到nginx服务的container iddocker exec -it <contianer id> /bin/bash 进入nginx容器nginx日志的具体路径请查看项目中的vhost.conf执行完上面的命令后你就能通过http://127.0.0.1:8080/访问到项目啦。在我的Github gist有一组参考文件方便同学们参考https://gist.github.com/kevin…gist里的文件稍微旧一些,后来在使用的过程中又加入些新的PHP模块和Node,之前composer也单独放到了一个容器中,不过相信聪明的你看到这里应该已经会根据需求更改这些文件啦。 ...

March 4, 2019 · 2 min · jiezi

如何制作可以在 MaxCompute 上使用的 crcmod

之前我们介绍过在 PyODPS DataFrame 中使用三方包。对于二进制包而言,MaxCompute 要求使用包名包含 cp27-cp27m 的 Wheel 包。但对于部分长时间未更新的包,例如 oss2 依赖的 crcmod,PyPI 并未提供 Wheel 包,因而需要自行打包。本文介绍了如何使用 quay.io/pypa/manylinux1_x86_64 镜像制作可在 MaxCompute 上使用的 Wheel 包。本文参考 https://github.com/pypa/manylinux ,quay.io/pypa/manylinux1_x86_64 镜像也是目前绝大多数 Python 项目在 Travis CI 上打包的标准工具,如有进一步的问题可研究该项目。1. 准备依赖项不少包都有依赖项,例如 devel rpm 包或者其他 Python 包,在打包前需要了解该包的依赖,通常可以在 Github 中找到安装或者打包的相关信息。对于 crcmod,除 gcc 外不再有别的依赖,因而此步可略去。2. 修改 setup.py 并验证(建议在 Mac OS 或者 Linux 下)较旧的 Python 包通常不支持制作 Wheel 包。具体表现为在使用 python setup.py bdist_wheel 打包时报错。如果需要制作 Wheel 包,需要修改 setup.py 以支持 Wheel 包的制作。对于一部分包,可以简单地将 distutils 中的 setup 函数替换为 setuptools 中的 setup 函数。而对于部分自定义操作较多的 setup.py,需要详细分析打包过程,这一项工作可能会很复杂,本文就不讨论了。例如,对于 crcmod,修改 setup.py 中的from distutils.core import setup为from setuptools import setup即可。修改完成后,在项目根目录执行python setup.py bdist_wheel如果没有报错且生成的 Wheel 包可在本地使用,说明 setup.py 已可以使用。3. 准备打包脚本在项目中新建 bin 目录,并在其中创建 build-wheel.sh:mkdir bin && vim bin/build-wheel.sh在其中填入以下内容:#!/bin/bash# modified from https://github.com/pypa/python-manylinux-demo/blob/master/travis/build-wheels.shset -e -x# Install a system package required by our library# 将这里修改为安装依赖项的命令# Compile wheelsPYBIN=/opt/python/cp27-cp27m/bin# 如果包根目录下有 dev-requirements.txt,取消下面的注释# “${PYBIN}/pip” install -r /io/dev-requirements.txt"${PYBIN}/pip" wheel /io/ -w wheelhouse/# Bundle external shared libraries into the wheelsfor whl in wheelhouse/*.whl; do auditwheel repair “$whl” -w /io/wheelhouse/done将第一步获知的依赖项安装脚本填入此脚本,在使用 python 或 pip 时,注意使用 /opt/python/cp27-cp27m/bin 中的版本。最后,设置执行权限chmod a+x bin/build-wheel.sh4. 打包使用 Docker 下载所需的镜像(本步需要使用 Docker,请提前安装),此后在项目根目录下打包:docker pull quay.io/pypa/manylinux1_x86_64docker run –rm -v pwd:/io quay.io/pypa/manylinux1_x86_64 /io/bin/build-wheel.sh完成的 Wheel 包位于项目根目录下的 wheelhouse 目录下。本文作者:继盛阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 1, 2019 · 1 min · jiezi

恭喜 containerd 毕业

今年的第一篇文章更新,带来一个重大的消息。CNCF(云原生计算基金会)在美国时间 2019 年 2 月 28 日宣布 containerd 今天正式毕业了。这是 CNCF 中毕业的第 5 个项目,之前已经毕业的项目为 Kubernetes、Prometheus、Envoy 和 CoreDNS 。containerd 2014 年从 Docker 孵化出来,最初是作为 Docker 引擎的底层管理器;在 2017 年 3 月被 CNCF 接受后,containerd 几乎成为了行业容器运行引擎的标准,它专注于简单,健壮和可移植性,任何人都可以使用它来构建自己的容器引擎/平台。“When Docker contributed containerd to the community, our goal was to share a robust and extensible runtime that millions of users and tens of thousands of organizations have already standardized on as part of Docker Engine,” said Michael Crosby, containerd maintainer and Docker engineer.截至目前,containerd 的 GitHub 项目有 3533 个 Star ;221 个 Watch 和 726 个 Fork,贡献者超过了 166 位。相信在之后也会发展的更好。下面附上 containerd 的架构图,以后会更新关于 containerd 相关原理的文章。再次恭喜 containerd 毕业。可以通过下面二维码订阅我的文章公众号【MoeLove】 ...

March 1, 2019 · 1 min · jiezi

sorl实现快速搜索

Docker安装Solr服务拉取 Solr 镜像:docker pull solr:7.4.0启动 Solr 容器docker run –name taotao-solr -d -p 8983:8983 -t solr:7.4.0访问 http://ip:8983/ Solr界面功

February 28, 2019 · 1 min · jiezi

Win7安装Docker

安装环境我本机的配置情况:win7-64位操作系统、内存8G、处理器intel(R) Core(TM) i5-6500 CPU(4核)安装步骤一.下载安装包win7系统对应的docker安装包,需要使用 Docker Toolbox,这个是官网下载地址: Get Docker Toolbox for Windows二.安装Docker Toolbox 1.双击安装文件: 2.选择你想安装的盘符:3.点击下一步进行安装:4.安装完成,桌面多了三个小图标三.双击验证1.双击击“Docker Quickstart Terminal”启动一个终端窗口2.验证Helloworld$ docker run hello-world3.查看是否安装成功$ docker run hello-world

February 28, 2019 · 1 min · jiezi

一步步学会用docker部署应用(nodejs版)

docker是一种虚拟化技术,可以在内核层隔离资源。因此对于上层应用而言,采用docker技术可以达到类似于虚拟机的沙盒环境。这大大简化了应用部署,让运维人员无需陷入无止境繁琐的依赖环境及系统配置中;另一方面,容器技术也可以充分利用硬件资源,做到资源共享。本文将采用docker技术部署一个简单的nodejs应用,它包括一个简单的前置网关nginx、redis服务器以及业务服务器。同时使用dockerfile配置特定镜像,采用docker-compose进行容器编排,解决依赖、网络等问题。docker基础本文默认机器已安装docker环境,即可以使用docker和docker-compose服务,如果本地没有安装,则参考:安装docker及docker-compose,可参考 Install Docker Composedocker compose 技术可以查看官方文档 Docker Composedocker源默认docker采用官方镜像,国内用户下载镜像速度较慢,为了更好的体验,建议切换源。OSX系统通过添加 ~/.docker/daemon.json文件,{ “registry-mirrors”: [“http://f1361db2.m.daocloud.io/”]}即可,镜像源地址可替换,随后重启docker服务即可。linux系统通过修改 /etc/docker/daemon.josn文件,一样可以替换源。docker简单操作源切换完毕之后,就可以尝试简单的容器操作。首先,运行一个简单的容器:docker run -it node:8-slim noderun命令,根据某个版本的node镜像运行容器,同时执行 “node”命令,进入node命令行交互模式。docker run -d node:8-slim node执行 -d 选项,让容器以daemon进程运行,同时返回容器的hash值。根据该hash值,我们可以通过命令行进入运行的容器查看相关状态:docker exec -it hashcode bashhashcode可以通过docker ps -l找到对应容器的hashcode关于镜像的选择以及版本的确定,可以通过访问官方 https://hub.docker.com/ 搜索,根据结果寻找 official image使用,当然也可根据下载量和star数量进行选择。对于镜像的tag,则根据业务需求进行判断是否需要完整版的系统。如nodejs镜像,仅仅需要node基础环境而不需要其他的系统预装命令,因此选择了 node:<version>-slim 版本。Dockerfile从源下载的镜像大多数不满足实际的使用需求,因此需要定制镜像。镜像定制可以通过运行容器安装环境,最后提交为镜像:docker run -it node:8-slim bashroot@ff05391b4cf8:/# echo helloworld > /home/textroot@ff05391b4cf8:/# exitdocker commit ff05391b4cf8 node-hello然后运行该镜像即可。另一种镜像定制可以通过Dockerfile的形式完成。Dockerfile是容器运行的配置文件,每次执行命令都会生成一个镜像,直到所有环境都已设置完毕。Dockerfile文件中可以执行命令定制化镜像,如 “FROM、COPY、ADD、ENV、EXPOSE、RUN、CMD”等,具体dockerfile的配置可参考相关文档。Dockerfile完成后,进行构建镜像:docker build -t node:custom:v1 .镜像构建成功后即可运行容器。docker-compose关于docker-compose,将在下文示例中进行说明。示例:搭建nodejs应用docker-compose.yml在docker-compose.yml中配置相关服务节点,同时在每个服务节点中配置相关的镜像、网络、环境、磁盘映射等元信息,也可指定具体Dockerfile文件构建镜像使用。version: ‘3’services: nginx: image: nginx:latest ports: - 80:80 restart: always volumes: - ./nginx/conf.d:/etc/nginx/conf.d - /tmp/logs:/var/log/nginx redis-server: image: redis:latest ports: - 6479:6379 restart: always app: build: ./ volumes: - ./:/usr/local/app restart: always working_dir: /usr/local/app ports: - 8090:8090 command: node server/server.js depends_on: - redis-server links: - redis-server:rdredis服务器首先搭建一个单节点缓存服务,采用官方提供的redis最新版镜像,无需构建。version: ‘3’services: redis-server: image: redis:latest ports: - 6479:6379 restart: always关于version具体信息,可参考Compose and Docker compatibility matrix找到对应docker引擎匹配的版本格式。在services下,创建了一个名为 redis-server 的服务,它采用最新的redis官方镜像,并通过宿主机的6479端口向外提供服务。并设置自动重启功能。此时,在宿主机上可以通过6479端口使用该缓存服务。web应用使用node.js的koa、koa-router可快速搭建web服务器。在本节中,创建一个8090端口的服务器,同时提供两个功能:1. 简单查询单个key的缓存 2. 流水线查询多个key的缓存docker-compose.ymlservices: app: build: ./ volumes: - ./:/usr/local/app restart: always working_dir: /usr/local/app ports: - 8090:8090 command: node server/server.js depends_on: - redis-server links: - redis-server:rd此处创建一个app服务,它使用当前目录下的Dockerfile构建后的镜像,同时通过 volumes 配置磁盘映射,将当前目录下所有文件映射至容器的/usr/local/app,并制定为运行时目录;同时映射宿主机的8090端口,最后执行node server/server.js命令运行服务器。通过depends_on设置app服务的依赖,等待 redis-server 服务启动后再启动app服务;通过links设置容器间网络连接,在app服务中,可通过别名 rd 访问redis-server。DockerfileFROM node:8-slimCOPY ./ /usr/local/appWORKDIR /usr/local/appRUN npm i –registry=https://registry.npm.taobao.orgENV NODE_ENV devEXPOSE 8090 指定的Dockerfile则做了初始化npm的操作。web-server sourcecodeconst Koa = require(‘koa’);const Router = require(‘koa-router’);const redis = require(‘redis’);const { promisify } = require(‘util’);let app = new Koa();let router = new Router();let redisClient = createRedisClient({ // ip为docker-compose.yml配置的redis-server别名 rd,可在应用所在容器查看dns配置 ip: ‘rd’, port: 6379, prefix: ‘’, db: 1, password: null});function createRedisClient({port, ip, prefix, db}) { let client = redis.createClient(port, ip, { prefix, db, no_ready_check: true }); client.on(‘reconnecting’, (err)=>{ console.warn(redis client reconnecting, delay ${err.delay}ms and attempt ${err.attempt}); }); client.on(’error’, function (err) { console.error(‘Redis error!’,err); }); client.on(‘ready’, function() { console.info(redis初始化完成,就绪: ${ip}:${port}/${db}); }); return client;}function execReturnPromise(cmd, args) { return new Promise((res,rej)=>{ redisClient.send_command(cmd, args, (e,reply)=>{ if(e){ rej(e); }else{ res(reply); } }); });}function batchReturnPromise() { return new Promise((res,rej)=>{ let b = redisClient.batch(); b.exec = promisify(b.exec); res(b); });}router.get(’/’, async (ctx, next) => { await execReturnPromise(‘set’,[’testkey’,‘helloworld’]); let ret = await execReturnPromise(‘get’,[’testkey’]); ctx.body = { status: ‘ok’, result: ret, };});router.get(’/batch’, async (ctx, next) => { await execReturnPromise(‘set’,[’testkey’,‘helloworld, batch!’]); let batch = await batchReturnPromise(); for(let i=0;i < 10;i++){ batch.get(’testkey’); } let ret = await batch.exec(); ctx.body = { status: ‘ok’, result: ret, };});app .use(router.routes()) .use(router.allowedMethods()) .listen(8090);需要注意的是,在web服务所在的容器中,通过别名 rd 访问缓存服务。此时,运行命令 docker-compose up后,即可通过 http://127.0.0.1:8090/ http://127.0.0.1:8090/batch 访问这两个缓存服务。转发目前可以通过宿主机的8090端口访问服务,为了此后web服务的可扩展性,需要在前端加入转发层。实例中使用nginx进行转发:services: nginx: image: nginx:latest ports: - 80:80 restart: always volumes: - ./nginx/conf.d:/etc/nginx/conf.d - /tmp/logs:/var/log/nginx采用最新版的nginx官方镜像,向宿主机暴露80端口,通过在本地配置nginx的抓发规则文件,映射至容器的nginx配置目录下实现快速高效的测试。运行与扩展默认单节点下,直接运行docker-compose up -d即可运行服务。如果服务节点需要扩展,可通过docker-compose up -d –scale app=3扩展为3个web服务器,同时nginx转发规则需要修改:upstream app_server { # 设置server集群,负载均衡关键指令 server docker-web-examples_app_1:8090; # 设置具体server, server docker-web-examples_app_2:8090; server docker-web-examples_app_3:8090;}server { listen 80; charset utf-8; location / { proxy_pass http://app_server; proxy_set_header Host $host:$server_port; proxy_set_header X-Forwarded-Host $server_name; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}app_server内部的各个服务器名称为docker-web-examples_app_1,format为“${path}${service}${number}”,即第一部分为 docker-compose.yml所在目录名称,如果在根目录则为应用名称;第二部分为扩展的服务名;第三部分为扩展序号通过设置nginx的配置的log_format中upstream_addr变量,可观察到负载均衡已生效。http{ log_format main ‘$remote_addr:$upstream_addr - $remote_user [$time_local] “$request” ’ ‘$status $body_bytes_sent “$http_referer” ’ ‘"$http_user_agent" “$http_x_forwarded_for”’;}参考docker官方文档docker-compose.yml 配置文件编写详解Dockerfile实践 ...

February 28, 2019 · 2 min · jiezi

docker环境下,使用phpstorm进行debug

在上一篇文章中,阐述了在phpstorm安装xdebug。实际的开发过程中,由于历史项目的存在,不同框架的存在,我们需要借助docker来快速的实现开发环境的部署与统一,本文将阐述如何在docker下实现项目的xdebug。理论概述与在本机直接开发不同,docker环境下,大体是这样。宿主机,即我们开发用的电脑。我们看到,有以下几点不同:PHP环境不同本机开发时。项目的运行环境与IDEA上的CLI,都是本机环境,相统一。docker开发时,本机环境与dokcer环境可能不统一。项目的路径不同本机开发时,xdebug反馈的项目路径,就是开发机的实际路径。docker环境下,xdebug将docker主机的路径发送给宿主机,但宿主机接收到,在本机上的此路径上,找不到对应的文件。当然,也就没有办法进行正确的信息显示。远程地址不同本机开发时,远程主机地址就是本机地址,所以是127.0.0.1。docker开发时,远程主机地址,应该是宿主机地址。实施总结出上面的几点不同后,开始实施也变得简单了。统一PHPSTORM与docker的PHP环境配置 -> Languages .. -> PHPCLI Interpreter … -> + -> From Docker Vagrant如果不清楚PHP的路径的话,进行DOCKER容器,使用whereis php。如果在service找不到docker,需要进入配置 -> docker -> + -> 确定,则phpstorm会自动为我们找到当前存在的容器。映射路径配置 -> Languages .. -> PHP -> SERVICES -> +前面提本地项目路径,后面对应docker项目路径。注意:为了保持统一,在docker容器配置时,应该使用volumes来进行磁盘映射。修改远程地址修改docker容器中的,php.ini中的xdebug.remote_host=host.docker.internalhost.docker.internal代表:宿主机添加项目配置信息总结弄懂原理,善于看日志才是解决问题的捷径。

February 27, 2019 · 1 min · jiezi

docker笔记2-镜像与容器

docker镜像概述操作系统分为内核kernel和用户空间。对于Linux而言,内核(bootfs)启动后会挂载root文件系统为其提供用户空间支持。而docker镜像,就相当于是一个root文件系统(rootfs)。bootfs:用于系统引导的文件系统,包括bootloader和kernel,容器启动完成后会被卸载以节约内存资源rootfs:位于bootfs之上,表现为docker容器的根文件系统镜像原理镜像的root文件系统被设计为分层存储的架构。镜像在构建时,会一层一层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只会发生在自己这一层。分层存储的特征使得镜像的复用,定制变得更加容易。可以使用构建好的镜像作为基础层,再进一步的添加新的层,以定制自己所需的内容,构建新的镜像。简单来说,镜像是:文件和metedata的集合(rootfs)镜像是分层存储的,并且每一层都可以添加改变删除文件,成为一个新的镜像不同镜像可以共享相同的layer(层)镜像本身是read-only的镜像获取镜像的获取方式:从镜像仓库(registry)获取通过commit命令将容器保存为镜像通过Dockerfile定制容器(推荐使用)通过rootfs压缩包导入docker save和docker load命令镜像管理从Registry拉取镜像在docker hub上有大量高质量的镜像可以使用,从镜像仓库拉取镜像的命令格式是:docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]镜像的名称格式Docker镜像仓库地址:<域名/IP>[:端口号],默认地址是docker hub仓库名:<用户名>/<软件名>,对于docker hub,如果不给出用户名,默认是library,也就是官方镜像标签:标签一般是镜像的版本信息,不指定标签默认是latest例如:docker pull centos$ docker pull centosUsing default tag: latestlatest: Pulling from library/centosa02a4930cb5d: Pull complete Digest: sha256:184e5f35598e333bfa7de10d8fb1cebb5ee4df5bc0f970bf2b1e7c7345136426Status: Downloaded newer image for centos:latest这条命令没有给出镜像仓库地址,默认从Docker Hub上获取。而镜像名称是centos,因此会获取官方镜像library/centos中标签为latest的镜像配置镜像加速器因为docker hub地址是在国外,从国内拉取镜像仓库中的镜像会比较慢,此时可以配置镜像加速器。目前Docker官方和国内的云服务商都有提供国内加速服务。Docker官方提供的中国加速器阿里云加速器配置阿里云加速器为例:环境说明系统环境:centos7docker版本:Docker version 18.03.0-ce创建目录文件$ mkdir -p /etc/docker$ vim /etc/docker/daemon.json添加配置内容,配置内容在阿里云容器镜像服务中可以获取,每个阿里云账号都有自己的镜像加速器。{ “registry-mirrors”: [“https://这里的配置每个人都有.mirror.aliyuncs.com”]}然后就是重新加载文件和重启docker,就可以了$ systemctl daemon-reload$ systemctl restart docker列出镜像列出本地镜像的命令是:$ docker image ls或者$ docker images删除镜像删除本地镜像的命令:$ docker image rm [选项] <镜像1> [<镜像2> …]镜像可以是镜像短ID,长ID,镜像名或者镜像摘要。下面看$ docker images列出的镜像信息。$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEcentos latest 1e1148e4cc2c 2 months ago 202MBbusybox latest 59788edf1f3e 4 months ago 1.15MBdjango-compose_web latest cfd70f0cb009 5 months ago 969MBpostgres latest ac25c2bac3c4 5 months ago 228MBmy-compose_web latest 4867e7c35cc9 5 months ago 86.5MB其中REPOSITORY+TAG称为镜像名,IMAGE ID是镜像ID,取前几位就是镜像短ID。比如用镜像名删除centos镜像:$ docker image rm centos:latestUntagged: centos:latestUntagged: centos@sha256:184e5f35598e333bfa7de10d8fb1cebb5ee4df5bc0f970bf2b1e7c7345136426Deleted: sha256:1e1148e4cc2c148c6890a18e3b2d2dde41a6745ceb4e5fe94a923d811bf82ddbDeleted: sha256:071d8bd765171080d01682844524be57ac9883e53079b6ac66707e192ea25956使用镜像短ID删除镜像:$ docker image rm 59788edUntagged: busybox:latestUntagged: busybox@sha256:2a03a6059f21e150ae84b0973863609494aad70f0a80eaeb64bddd8d92465812Deleted: sha256:59788edf1f3e78cd0ebe6ce1446e9d10788225db3dedcfd1a59f764bad2b2690Deleted: sha256:8a788232037eaf17794408ff3df6b922a1aedf9ef8de36afdae3ed0b0381907b可以看到busybox这个镜像已经被删除。也可以类似管道一样,结合其他命令的结果来删除镜像,比如docker image ls -q,先看看这个命令的执行结果。$ docker image ls -q python449d3495be0e82514113452882514113452840792d8a2d6d这个命令执行返回本地镜像中所有python镜像的镜像ID,有了ID就可以结合docker image rm来删除了。docker image rm $(docker image ls -q)这条命令对想要成批删除镜像很有帮助。给镜像打标签给镜像打标签的命令是docker tag 原镜像 新镜像名:标签,注意,打标签会新生成一个镜像,而且这个新的镜像ID和原镜像一样。注意:镜像的唯一标识是其ID和摘要,一个镜像可以有多个标签。当删除镜像的时候,实际上是删除某个标签的镜像。$ docker tag busybox busybox:version1$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEbusybox latest d8233ab899d4 12 days ago 1.2MBbusybox version1 d8233ab899d4 12 days ago 1.2MB启动镜像为容器启动镜像为容器的命令,列举常用的一条命令:docker run -itd (镜像名/ID/镜像摘要)$ docker run -itd busyboxaa6ef78ae7b93704cbf9f99e184c2e2cb53924d693ca67c370fa74f52ff38d15上面示例启动了busybox镜像为容器,不添加标签说明默认启动busybox:latest。选项说明:-i表示让容器的标准输入打开,-t表示分配一个伪终端,-d表示后台启动。要把-i -t -d 放到镜像名字前面。查看运行状态的容器查看容器运行状态使用docker ps只能查看运行中的容器,-a选项查看全部容器,包括未运行的容器。$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESaa6ef78ae7b9 busybox “sh” 23 minutes ago Up 23 minutes thirsty_brattainee0034f44bc6 wordpress:latest “docker-entrypoint.s…” 5 months ago Up About an hour 0.0.0.0:8000->80/tcp wordpress-compose_wordpress_1cbcb7baa5b2f mysql:5.7 “docker-entrypoint.s…” 5 months ago Up About an hour 3306/tcp, 33060/tcp wordpress-compose_db_1$ docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESaa6ef78ae7b9 busybox “sh” 23 minutes ago Up 23 minutes thirsty_brattain341ea8ff6424 django-compose_web “python3 manage.py r…” 5 months ago Exited (137) 5 months ago django-compose_web_1bffde084f040 django-compose_web “django-admin.py sta…” 5 months ago Exited (0) 5 months ago django-compose_web_run_10f68cb4ccae0 postgres “docker-entrypoint.s…” 5 months ago Exited (0) 5 months ago django-compose_db_1ee0034f44bc6 wordpress:latest “docker-entrypoint.s…” 5 months ago Up About an hour 0.0.0.0:8000->80/tcp wordpress-compose_wordpress_1使用commit构建镜像镜像是容器的基础,每次执行 docker run 的时候都会指定哪个镜像作为容器运行的基础。镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。因此,在运行的容器对容器进行添加修改操作,也就是修改容器的存储层,docker提供了commit命令可以将容器存储层保存下来称为镜像。用例子看一下吧:通过压缩包导入创建镜像镜像构建之Dockerfile ...

February 27, 2019 · 2 min · jiezi

稳定与非稳定版本软件的Docker Image构建策略

原文Image tag是不稳定的Docker image的tag是不稳定的,这句话的意思是就算tag不变,其所代表的image并非一成不变,例如openjdk:8在去年代表jdk 8u161今年则代表jdk 8u191。就算你使用openjdk:8u181也不能保证这个image是不变的,为什么这么说?一个Docker image大致是由4部分组成的:其依赖的基础镜像,由Dockerfile的FROM指令所指定其所包含的软件,在这个例子里就是 openjdk 8u181Dockerfile的其他脚本启动入口,比如docker-entrypoint.sh就算软件不发生变化,另外3个也是有可能发生变化的,而构建的新image的tag依然是openjdk:8u181。而且要注意到一般采用的是软件的版本号作为tag,而不是commit、构建日期作为tag。如果你是Java程序员,可以类比docker image tag为maven的SNAPSHOT。那这意味着什么?从docker image使用方角度,每次启动之前都需要pull一下,确保使用了新的image从docker image提供方角度,就算你的软件版本已经冻结,你仍然需要定期构建image并发布仓库上针对稳定与非稳定版本的构建策略和Maven的版本定义一样,你的软件应该分为两种:stable版,即一旦发布其版本号对应的代码不会再做修改snapshot版,又称nightly-build版,即该版本号对应的代码是不稳定的对于stable版,你应该定期对其构建image。比如你有版本1.0、1.1、1.2,那你应该定期从软件仓库中下载这三个版本的构建物,然后对为它们构建image。以Maven举例,定期从Maven仓库下载它们的Jar,然后为它们构建image。记得确保docker build添加了–pull选项。对于snapshot版,你应该将构建image的过程融入到软件的构建过程中。以Maven为例,使用spotify-dockerfile-plugin,mvn clean install dockerfile:build dockerfile:push。不论是stable版还是snapshot版,都应该利用CI/CD工具(如Jenkins)将image构建工作自动化。

February 27, 2019 · 1 min · jiezi

Redis实现广告缓存

Docker安装官方Redis参考文章:Docker安装官方Redis镜像并启用密码认证拉取最新版的redis镜像:docker pull redis:latest启动容器并带密码:docker run –name redis-test -p 6379:6379 -d –restart=always redis:latest redis-server –appendonly yes –requirepass “your passwd"查看容器、注意看id: docker ps查看进程: ps -ef|grep redis进入容器执行redis客户端:docker exec -it a126ec987cfe redis-cli -h yourhost -p 6379 -a ‘your passwd'127.0.0.1:6379> pingPONG127.0.0.1:6379> info…能ping通即可正常使用,这个例子比起原先方式,省去了编译、配置、开机启动服务一大堆麻烦。docker就是好,docker就是棒,docker顶呱呱。

February 26, 2019 · 1 min · jiezi

NATS--NATS Streaming持久化

前言最近项目中需要使用到一个消息队列,主要用来将原来一些操作异步化。根据自己的使用场景和熟悉程度,选择了NATS Streaming。之所以,选择NATS Streaming。一,因为我选型一些中间件,我会优先选取一些自己熟悉的语言编写的,这样方便排查问题和进一步的深究。二,因为自己一直做k8s等云原生这块,偏向于cncf基金会管理的项目,毕竟这些项目从一开始就考虑了如何部署在k8s当中。三,是评估项目在不断发展过程中,引入的组件是否能够依旧满足需求。消息队列的使用场景如果问为什么这么做,需要说一下消息队列的使用场景。之前看知乎的时候,看到一些回答比较认同,暂时拿过来,更能形象表达。感谢ScienJus同学的精彩解答。消息队列的主要特点是异步处理,主要目的是减少请求响应时间和解耦。所以主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时由于使用了消息队列,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦和。使用场景的话,举个例子:假设用户在你的软件中注册,服务端收到用户的注册请求后,它会做这些操作:校验用户名等信息,如果没问题会在数据库中添加一个用户记录如果是用邮箱注册会给你发送一封注册成功的邮件,手机注册则会发送一条短信分析用户的个人信息,以便将来向他推荐一些志同道合的人,或向那些人推荐他发送给用户一个包含操作指南的系统通知等等……但是对于用户来说,注册功能实际只需要第一步,只要服务端将他的账户信息存到数据库中他便可以登录上去做他想做的事情了。至于其他的事情,非要在这一次请求中全部完成么?值得用户浪费时间等你处理这些对他来说无关紧要的事情么?所以实际当第一步做完后,服务端就可以把其他的操作放入对应的消息队列中然后马上返回用户结果,由消息队列异步的进行这些操作。或者还有一种情况,同时有大量用户注册你的软件,再高并发情况下注册请求开始出现一些问题,例如邮件接口承受不住,或是分析信息时的大量计算使cpu满载,这将会出现虽然用户数据记录很快的添加到数据库中了,但是却卡在发邮件或分析信息时的情况,导致请求的响应时间大幅增长,甚至出现超时,这就有点不划算了。面对这种情况一般也是将这些操作放入消息队列(生产者消费者模型),消息队列慢慢的进行处理,同时可以很快的完成注册请求,不会影响用户使用其他功能。所以在软件的正常功能开发中,并不需要去刻意的寻找消息队列的使用场景,而是当出现性能瓶颈时,去查看业务逻辑是否存在可以异步处理的耗时操作,如果存在的话便可以引入消息队列来解决。否则盲目的使用消息队列可能会增加维护和开发的成本却无法得到可观的性能提升,那就得不偿失了。其实,总结一下消息队列的作用削峰,形象点的话,可以比喻为蓄水池。比如elk日志收集系统中的kafka,主要在日志高峰期的时候,在牺牲实时性的同时,保证了整个系统的安全。同步系统异构化。原先一个同步操作里的诸多步骤,可以考虑将一些不影响主线发展的步骤,通过消息队列异步处理。比如,电商行业,一个订单完成之后,一般除了直接返回给客户购买成功的消息,还要通知账户组进行扣费,通知处理库存变化,通知物流进行派送等,通知一些用户组做一些增加会员积分等操作等。NATS Streaming 简介NATS Streaming是一个由NATS驱动的数据流系统,用Go编程语言编写。 NATS Streaming服务器的可执行文件名是nats-streaming-server。 NATS Streaming与核心NATS平台无缝嵌入,扩展和互操作。 NATS Streaming服务器作为Apache-2.0许可下的开源软件提供。 Synadia积极维护和支持NATS Streaming服务器。特点除了核心NATS平台的功能外,NATS Streaming还提供以下功能:增强消息协议NATS Streaming使用谷歌协议缓冲区实现自己的增强型消息格式。这些消息通过二进制数据流在NATS核心平台进行传播,因此不需要改变NATS的基本协议。NATS Streaming信息包含以下字段: - 序列 - 一个全局顺序序列号为主题的通道 - 主题 - 是NATS Streaming 交付对象 - 答复内容 - 对应"reply-to"对应的对象内容 - 数据 - 真是数据内容 - 时间戳 - 接收的时间戳,单位是纳秒 - 重复发送 - 标志这条数据是否需要服务再次发送 - CRC32 - 一个循环冗余数据校验选项,在数据存储和数据通讯领域里,为了保证数据的正确性所采用的检错手段,这里使用的是 IEEE CRC32 算法 - 消息/事件的持久性 NATS Streaming提供了可配置的消息持久化,持久目的地可以为内存或者文件。另外,对应的存储子系统使用了一个公共接口允许我们开发自己自定义实现来持久化对应的消息 - 至少一次的发送 NATS Streaming提供了发布者和服务器之间的消息确认(发布操作) 和订阅者和服务器之间的消息确认(确认消息发送)。其中消息被保存在服务器端内存或者辅助存储(或其他外部存储器)用来为需要重新接受消息的订阅者进行重发消息。 - 发布者发送速率限定 NATS Streaming提供了一个连接选项叫 MaxPubAcksInFlight,它能有效的限制一个发布者可能随意的在任何时候发送的未被确认的消息。当达到这个配置的最大数量时,异步发送调用接口将会被阻塞,直到未确认消息降到指定数量之下。- 每个订阅者的速率匹配/限制 NATS Streaming运行指定的订阅中设置一个参数为 MaxInFlight,它用来指定已确认但未消费的最大数据量,当达到这个限制时,NATS Streaming 将暂停发送消息给订阅者,直到未确认的数据量小于设定的量为止以主题重发的历史数据 新订阅的可以在已经存储起来的订阅的主题频道指定起始位置消息流。通过使用这个选项,消息就可以开始发送传递了: 1. 订阅的主题存储的最早的信息 2. 与当前订阅主题之前的最近存储的数据,这通常被认为是 “最后的值” 或 “初值” 对应的缓存 3. 一个以纳秒为基准的 日期/时间 4. 一个历史的起始位置相对当前服务的 日期/时间,例如:最后30秒 5. 一个特定的消息序列号持久订阅 订阅也可以指定一个“持久化的名称”可以在客户端重启时不受影响。持久订阅会使得对应服务跟踪客户端最后确认消息的序列号和持久名称。当这个客户端重启或者重新订阅的时候,使用相同的客户端ID 和 持久化的名称,对应的服务将会从最早的未被确认的消息处恢复。docker 运行NATS Streaming在运行之前,前面已经讲过NATS Streaming 相比nats,多了持久化的一个future。所以我们在接下来的demo演示中,会重点说这点。运行基于memory的持久化示例:docker run -ti -p 4222:4222 -p 8222:8222 nats-streaming:0.12.0你将会看到如下的输出:[1] 2019/02/26 08:13:01.769734 [INF] STREAM: Starting nats-streaming-server[test-cluster] version 0.12.0[1] 2019/02/26 08:13:01.769811 [INF] STREAM: ServerID: arfYGWPtu7Cn8Ojcb1yko3[1] 2019/02/26 08:13:01.769826 [INF] STREAM: Go version: go1.11.5[1] 2019/02/26 08:13:01.770363 [INF] Starting nats-server version 1.4.1[1] 2019/02/26 08:13:01.770398 [INF] Git commit [not set][4] 2019/02/26 08:13:01.770492 [INF] Starting http monitor on 0.0.0.0:8222[1] 2019/02/26 08:13:01.770555 [INF] Listening for client connections on 0.0.0.0:4222[1] 2019/02/26 08:13:01.770581 [INF] Server is ready[1] 2019/02/26 08:13:01.799435 [INF] STREAM: Recovering the state…[1] 2019/02/26 08:13:01.799461 [INF] STREAM: No recovered state[1] 2019/02/26 08:13:02.052460 [INF] STREAM: Message store is MEMORY[1] 2019/02/26 08:13:02.052552 [INF] STREAM: ———- Store Limits ———-[1] 2019/02/26 08:13:02.052574 [INF] STREAM: Channels: 100 *[1] 2019/02/26 08:13:02.052586 [INF] STREAM: ——— Channels Limits ——–[1] 2019/02/26 08:13:02.052601 [INF] STREAM: Subscriptions: 1000 *[1] 2019/02/26 08:13:02.052613 [INF] STREAM: Messages : 1000000 *[1] 2019/02/26 08:13:02.052624 [INF] STREAM: Bytes : 976.56 MB *[1] 2019/02/26 08:13:02.052635 [INF] STREAM: Age : unlimited *[1] 2019/02/26 08:13:02.052649 [INF] STREAM: Inactivity : unlimited *[1] 2019/02/26 08:13:02.052697 [INF] STREAM: ———————————-可以看出默认的是基于内存的持久化。运行基于file的持久化示例:docker run -ti -v /Users/gao/test/mq:/datastore -p 4222:4222 -p 8222:8222 nats-streaming:0.12.0 -store file –dir /datastore -m 8222你将会看到如下的输出:[1] 2019/02/26 08:16:07.641972 [INF] STREAM: Starting nats-streaming-server[test-cluster] version 0.12.0[1] 2019/02/26 08:16:07.642038 [INF] STREAM: ServerID: 9d4H6GAFPibpZv282KY9QM[1] 2019/02/26 08:16:07.642099 [INF] STREAM: Go version: go1.11.5[1] 2019/02/26 08:16:07.643733 [INF] Starting nats-server version 1.4.1[1] 2019/02/26 08:16:07.643762 [INF] Git commit [not set][5] 2019/02/26 08:16:07.643894 [INF] Listening for client connections on 0.0.0.0:4222[1] 2019/02/26 08:16:07.643932 [INF] Server is ready[1] 2019/02/26 08:16:07.672145 [INF] STREAM: Recovering the state…[1] 2019/02/26 08:16:07.679327 [INF] STREAM: No recovered state[1] 2019/02/26 08:16:07.933519 [INF] STREAM: Message store is FILE[1] 2019/02/26 08:16:07.933570 [INF] STREAM: Store location: /datastore[1] 2019/02/26 08:16:07.933633 [INF] STREAM: ———- Store Limits ———-[1] 2019/02/26 08:16:07.933679 [INF] STREAM: Channels: 100 *[1] 2019/02/26 08:16:07.933697 [INF] STREAM: ——— Channels Limits ——–[1] 2019/02/26 08:16:07.933711 [INF] STREAM: Subscriptions: 1000 *[1] 2019/02/26 08:16:07.933749 [INF] STREAM: Messages : 1000000 *[1] 2019/02/26 08:16:07.933793 [INF] STREAM: Bytes : 976.56 MB *[1] 2019/02/26 08:16:07.933837 [INF] STREAM: Age : unlimited *[1] 2019/02/26 08:16:07.933857 [INF] STREAM: Inactivity : unlimited *[1] 2019/02/26 08:16:07.933885 [INF] STREAM: ———————————-PS如果部署在k8s当中,那么就可以采取基于file的持久化,通过挂载一个块存储来保证,数据可靠。比如,aws的ebs或是ceph的rbd。4222为客户端连接的端口。8222为监控端口。启动以后访问:localhost:8222,可以看到如下的网页:启动参数解析Streaming Server Options: -cid, –cluster_id <string> Cluster ID (default: test-cluster) -st, –store <string> Store type: MEMORY|FILE|SQL (default: MEMORY) –dir <string> For FILE store type, this is the root directory -mc, –max_channels <int> Max number of channels (0 for unlimited) -msu, –max_subs <int> Max number of subscriptions per channel (0 for unlimited) -mm, –max_msgs <int> Max number of messages per channel (0 for unlimited) -mb, –max_bytes <size> Max messages total size per channel (0 for unlimited) -ma, –max_age <duration> Max duration a message can be stored (“0s” for unlimited) -mi, –max_inactivity <duration> Max inactivity (no new message, no subscription) after which a channel can be garbage collected (0 for unlimited) -ns, –nats_server <string> Connect to this external NATS Server URL (embedded otherwise) -sc, –stan_config <string> Streaming server configuration file -hbi, –hb_interval <duration> Interval at which server sends heartbeat to a client -hbt, –hb_timeout <duration> How long server waits for a heartbeat response -hbf, –hb_fail_count <int> Number of failed heartbeats before server closes the client connection –ft_group <string> Name of the FT Group. A group can be 2 or more servers with a single active server and all sharing the same datastore -sl, –signal <signal>[=<pid>] Send signal to nats-streaming-server process (stop, quit, reopen) –encrypt <bool> Specify if server should use encryption at rest –encryption_cipher <string> Cipher to use for encryption. Currently support AES and CHAHA (ChaChaPoly). Defaults to AES –encryption_key <sting> Encryption Key. It is recommended to specify it through the NATS_STREAMING_ENCRYPTION_KEY environment variable insteadStreaming Server Clustering Options: –clustered <bool> Run the server in a clustered configuration (default: false) –cluster_node_id <string> ID of the node within the cluster if there is no stored ID (default: random UUID) –cluster_bootstrap <bool> Bootstrap the cluster if there is no existing state by electing self as leader (default: false) –cluster_peers <string> List of cluster peer node IDs to bootstrap cluster state. –cluster_log_path <string> Directory to store log replication data –cluster_log_cache_size <int> Number of log entries to cache in memory to reduce disk IO (default: 512) –cluster_log_snapshots <int> Number of log snapshots to retain (default: 2) –cluster_trailing_logs <int> Number of log entries to leave after a snapshot and compaction –cluster_sync <bool> Do a file sync after every write to the replication log and message store –cluster_raft_logging <bool> Enable logging from the Raft library (disabled by default)Streaming Server File Store Options: –file_compact_enabled <bool> Enable file compaction –file_compact_frag <int> File fragmentation threshold for compaction –file_compact_interval <int> Minimum interval (in seconds) between file compactions –file_compact_min_size <size> Minimum file size for compaction –file_buffer_size <size> File buffer size (in bytes) –file_crc <bool> Enable file CRC-32 checksum –file_crc_poly <int> Polynomial used to make the table used for CRC-32 checksum –file_sync <bool> Enable File.Sync on Flush –file_slice_max_msgs <int> Maximum number of messages per file slice (subject to channel limits) –file_slice_max_bytes <size> Maximum file slice size - including index file (subject to channel limits) –file_slice_max_age <duration> Maximum file slice duration starting when the first message is stored (subject to channel limits) –file_slice_archive_script <string> Path to script to use if you want to archive a file slice being removed –file_fds_limit <int> Store will try to use no more file descriptors than this given limit –file_parallel_recovery <int> On startup, number of channels that can be recovered in parallel –file_truncate_bad_eof <bool> Truncate files for which there is an unexpected EOF on recovery, dataloss may occurStreaming Server SQL Store Options: –sql_driver <string> Name of the SQL Driver (“mysql” or “postgres”) –sql_source <string> Datasource used when opening an SQL connection to the database –sql_no_caching <bool> Enable/Disable caching for improved performance –sql_max_open_conns <int> Maximum number of opened connections to the databaseStreaming Server TLS Options: -secure <bool> Use a TLS connection to the NATS server without verification; weaker than specifying certificates. -tls_client_key <string> Client key for the streaming server -tls_client_cert <string> Client certificate for the streaming server -tls_client_cacert <string> Client certificate CA for the streaming serverStreaming Server Logging Options: -SD, –stan_debug=<bool> Enable STAN debugging output -SV, –stan_trace=<bool> Trace the raw STAN protocol -SDV Debug and trace STAN –syslog_name On Windows, when running several servers as a service, use this name for the event source (See additional NATS logging options below)Embedded NATS Server Options: -a, –addr <string> Bind to host address (default: 0.0.0.0) -p, –port <int> Use port for clients (default: 4222) -P, –pid <string> File to store PID -m, –http_port <int> Use port for http monitoring -ms,–https_port <int> Use port for https monitoring -c, –config <string> Configuration fileLogging Options: -l, –log <string> File to redirect log output -T, –logtime=<bool> Timestamp log entries (default: true) -s, –syslog <string> Enable syslog as log method -r, –remote_syslog <string> Syslog server addr (udp://localhost:514) -D, –debug=<bool> Enable debugging output -V, –trace=<bool> Trace the raw protocol -DV Debug and traceAuthorization Options: –user <string> User required for connections –pass <string> Password required for connections –auth <string> Authorization token required for connectionsTLS Options: –tls=<bool> Enable TLS, do not verify clients (default: false) –tlscert <string> Server certificate file –tlskey <string> Private key for server certificate –tlsverify=<bool> Enable TLS, verify client certificates –tlscacert <string> Client certificate CA for verificationNATS Clustering Options: –routes <string, …> Routes to solicit and connect –cluster <string> Cluster URL for solicited routesCommon Options: -h, –help Show this message -v, –version Show version –help_tls TLS help.源码简单分析NATS Streaming 持久化目前NATS Streaming支持以下4种持久化方式:MEMORYFILESQLRAFT其实看源码可以知道:NATS Streaming的store基于接口实现,很容易扩展到更多的持久化方式。具体的接口如下:// Store is the storage interface for NATS Streaming servers.//// If an implementation has a Store constructor with StoreLimits, it should be// noted that the limits don’t apply to any state being recovered, for Store// implementations supporting recovery.//type Store interface { // GetExclusiveLock is an advisory lock to prevent concurrent // access to the store from multiple instances. // This is not to protect individual API calls, instead, it // is meant to protect the store for the entire duration the // store is being used. This is why there is no Unlock API. // The lock should be released when the store is closed. // // If an exclusive lock can be immediately acquired (that is, // it should not block waiting for the lock to be acquired), // this call will return true with no error. Once a store // instance has acquired an exclusive lock, calling this // function has no effect and true with no error will again // be returned. // // If the lock cannot be acquired, this call will return // false with no error: the caller can try again later. // // If, however, the lock cannot be acquired due to a fatal // error, this call should return false and the error. // // It is important to note that the implementation should // make an effort to distinguish error conditions deemed // fatal (and therefore trying again would invariably result // in the same error) and those deemed transient, in which // case no error should be returned to indicate that the // caller could try later. // // Implementations that do not support exclusive locks should // return false and ErrNotSupported. GetExclusiveLock() (bool, error) // Init can be used to initialize the store with server’s information. Init(info *spb.ServerInfo) error // Name returns the name type of this store (e.g: MEMORY, FILESTORE, etc…). Name() string // Recover returns the recovered state. // Implementations that do not persist state and therefore cannot // recover from a previous run MUST return nil, not an error. // However, an error must be returned for implementations that are // attempting to recover the state but fail to do so. Recover() (*RecoveredState, error) // SetLimits sets limits for this store. The action is not expected // to be retroactive. // The store implementation should make a deep copy as to not change // the content of the structure passed by the caller. // This call may return an error due to limits validation errors. SetLimits(limits *StoreLimits) error // GetChannelLimits returns the limit for this channel. If the channel // does not exist, returns nil. GetChannelLimits(name string) *ChannelLimits // CreateChannel creates a Channel. // Implementations should return ErrAlreadyExists if the channel was // already created. // Limits defined for this channel in StoreLimits.PeChannel map, if present, // will apply. Otherwise, the global limits in StoreLimits will apply. CreateChannel(channel string) (*Channel, error) // DeleteChannel deletes a Channel. // Implementations should make sure that if no error is returned, the // channel would not be recovered after a restart, unless CreateChannel() // with the same channel is invoked. // If processing is expecting to be time consuming, work should be done // in the background as long as the above condition is guaranteed. // It is also acceptable for an implementation to have CreateChannel() // return an error if background deletion is still happening for a // channel of the same name. DeleteChannel(channel string) error // AddClient stores information about the client identified by clientID. AddClient(info *spb.ClientInfo) (*Client, error) // DeleteClient removes the client identified by clientID from the store. DeleteClient(clientID string) error // Close closes this store (including all MsgStore and SubStore). // If an exclusive lock was acquired, the lock shall be released. Close() error}官方也提供了mysql和pgsql两种数据的支持:postgres.db.sqlCREATE TABLE IF NOT EXISTS ServerInfo (uniquerow INTEGER DEFAULT 1, id VARCHAR(1024), proto BYTEA, version INTEGER, PRIMARY KEY (uniquerow));CREATE TABLE IF NOT EXISTS Clients (id VARCHAR(1024), hbinbox TEXT, PRIMARY KEY (id));CREATE TABLE IF NOT EXISTS Channels (id INTEGER, name VARCHAR(1024) NOT NULL, maxseq BIGINT DEFAULT 0, maxmsgs INTEGER DEFAULT 0, maxbytes BIGINT DEFAULT 0, maxage BIGINT DEFAULT 0, deleted BOOL DEFAULT FALSE, PRIMARY KEY (id));CREATE INDEX Idx_ChannelsName ON Channels (name(256));CREATE TABLE IF NOT EXISTS Messages (id INTEGER, seq BIGINT, timestamp BIGINT, size INTEGER, data BYTEA, CONSTRAINT PK_MsgKey PRIMARY KEY(id, seq));CREATE INDEX Idx_MsgsTimestamp ON Messages (timestamp);CREATE TABLE IF NOT EXISTS Subscriptions (id INTEGER, subid BIGINT, lastsent BIGINT DEFAULT 0, proto BYTEA, deleted BOOL DEFAULT FALSE, CONSTRAINT PK_SubKey PRIMARY KEY(id, subid));CREATE TABLE IF NOT EXISTS SubsPending (subid BIGINT, row BIGINT, seq BIGINT DEFAULT 0, lastsent BIGINT DEFAULT 0, pending BYTEA, acks BYTEA, CONSTRAINT PK_MsgPendingKey PRIMARY KEY(subid, row));CREATE INDEX Idx_SubsPendingSeq ON SubsPending (seq);CREATE TABLE IF NOT EXISTS StoreLock (id VARCHAR(30), tick BIGINT DEFAULT 0);– Updates for 0.10.0ALTER TABLE Clients ADD proto BYTEA;mysql.db.sqlCREATE TABLE IF NOT EXISTS ServerInfo (uniquerow INT DEFAULT 1, id VARCHAR(1024), proto BLOB, version INTEGER, PRIMARY KEY (uniquerow));CREATE TABLE IF NOT EXISTS Clients (id VARCHAR(1024), hbinbox TEXT, PRIMARY KEY (id(256)));CREATE TABLE IF NOT EXISTS Channels (id INTEGER, name VARCHAR(1024) NOT NULL, maxseq BIGINT UNSIGNED DEFAULT 0, maxmsgs INTEGER DEFAULT 0, maxbytes BIGINT DEFAULT 0, maxage BIGINT DEFAULT 0, deleted BOOL DEFAULT FALSE, PRIMARY KEY (id), INDEX Idx_ChannelsName (name(256)));CREATE TABLE IF NOT EXISTS Messages (id INTEGER, seq BIGINT UNSIGNED, timestamp BIGINT, size INTEGER, data BLOB, CONSTRAINT PK_MsgKey PRIMARY KEY(id, seq), INDEX Idx_MsgsTimestamp (timestamp));CREATE TABLE IF NOT EXISTS Subscriptions (id INTEGER, subid BIGINT UNSIGNED, lastsent BIGINT UNSIGNED DEFAULT 0, proto BLOB, deleted BOOL DEFAULT FALSE, CONSTRAINT PK_SubKey PRIMARY KEY(id, subid));CREATE TABLE IF NOT EXISTS SubsPending (subid BIGINT UNSIGNED, row BIGINT UNSIGNED, seq BIGINT UNSIGNED DEFAULT 0, lastsent BIGINT UNSIGNED DEFAULT 0, pending BLOB, acks BLOB, CONSTRAINT PK_MsgPendingKey PRIMARY KEY(subid, row), INDEX Idx_SubsPendingSeq(seq));CREATE TABLE IF NOT EXISTS StoreLock (id VARCHAR(30), tick BIGINT UNSIGNED DEFAULT 0);# Updates for 0.10.0ALTER TABLE Clients ADD proto BLOB;总结后续会详细解读一下代码实现和一些集群部署。当然肯定少不了如何部署高可用的集群在k8s当中。参阅文章:NATS Streaming详解 ...

February 26, 2019 · 11 min · jiezi

重拾golang - go目录结构说明

go 目录结构说明 golang集多编程范式之大成者,使开发者能够快速的开发、测试、部署程序,支持全平台静态编译。go具有优秀的依赖管理,高效的运行效率,庞大的第三方库支持以及在国内持续的增长势头。 作为开发者的我们也将不得不重视这门语言的兴起。首先向大家讲解一下go语言开发环境的目录结构,让我们更清楚的认识它。一、goroot开发包目录 当我们安装好后,会在安装目录出现一个go/文件夹,如果是windows目录应在再C:/go下(默认),如果是unix/linux一般会在/usr/local/go下,这个目录是unix software resource的含义。# liunx上目录位置chao@chao-PC:/usr/local/go$ pwd/usr/local/go# 主要目录包含如下图,分别进行说明:1、api文件夹 存放Go API检查器的辅助文件。其中,go1.1.txt、go1.2.txt、go1.3.txt和go1.txt文件分别罗列了不同版本的Go语言的全部API特征;except.txt文件中罗列了一些(在不破坏兼容性的前提下)可能会消失的API特性;next.txt文件则列出了可能在下一个版本中添加的新API特性。2、bin文件夹 存放所有由官方提供的Go语言相关工具的可执行文件。默认情况下,该目录会包含go和gofmt这两个工具。3、doc文件夹 存放Go语言几乎全部的HTML格式的官方文档和说明,方便开发者在离线时查看。4、misc文件夹 存放各类编辑器或IDE(集成开发环境)软件的插件,辅助它们查看和编写Go代码。有经验的软件开发者定会在该文件夹中看到很多熟悉的工具。查看:chao@chao-PC:/usr/local/go/misc$ lsandroid benchcmp chrome git linkcheck sortac tourarm cgo editors ios nacl swig trace5、pkg文件夹 用于在构建安装后,保存Go语言标准库的所有归档文件。pkg文件夹包含一个与Go安装平台相关的子目录,我们称之为“平台相关目录”。例如,在针对Linux 32bit操作系统的二进制安装包中,平台相关目录的名字就是linux_386;而在针对Windows 64bit操作系统的安装包中,平台相关目录的名字则为windows_amd64。 Go源码文件对应于以“.a”为结尾的归档文件,它们就存储在pkg文件夹下的平台相关目录中。 值得一提的是,pkg文件夹下有一个名叫tool的子文件夹,该子文件夹下也有一个平台相关目录,其中存放了很多可执行文件。关于这些可执行文件的用途,读者可参见附属于本书的Go命令教程。查看:chao@chao-PC:/usr/local/go/pkg$ lsinclude linux_amd64_dynlink linux_amd64_shared toollinux_amd64 linux_amd64_race linux_amd64_testcshared_shared6、src文件夹 存放所有标准库、Go语言工具,以及相关底层库(C语言实现)的源码。通过查看这个文件夹,可以了解到Go语言的方方面面。查看:chao@chao-PC:/usr/local/go/src$ lsall.bash clean.bat errors iostest.bash os sortall.bat clean.rc expvar log path strconvall.rc cmd flag make.bash plugin stringsandroidtest.bash cmp.bash fmt make.bat race.bash syncarchive compress go Make.dist race.bat syscallbootstrap.bash container hash make.rc reflect testingbufio context html math regexp textbuildall.bash crypto image mime run.bash timebuiltin database index naclmake.bash run.bat unicodebytes debug internal nacltest.bash run.rc unsafeclean.bash encoding io net runtime vendor7、test文件夹 存放测试Go语言自身代码的文件。通过阅读这些测试文件,可大致了解Go语言的一些特性和使用方法。二、gopath工作区目录结构 在环境变量中除了$GOPATH这样的显式变量外,Go语言还有两个隐含的环境变量——GOOS和GOARCH。 GOOS代表程序构建环境的目标操作系统,可笼统地理解为Go语言安装到的那个操作系统的标识,其值可以是darwin、freebsd、linux或windows。 GOARCH则代表程序构建环境的目标计算架构,可笼统地理解为Go语言安装到的那台计算机的计算架构的标识,其值可以是386、amd64或arm。工作区有3个子目录:src目录、pkg目录和bin目录。1、src目录 用于以代码包的形式组织并保存Go源码文件。这里的代码包,与src下的子目录一一对应。例如,若一个源码文件被声明为属于代码包logging,那么它就应当被保存在src目录下名为logging的子目录中。当然,我们也可以把Go源码文件直接放于src目录下,但这样的Go源码文件就只能被声明为属于main代码包了。除非用于临时测试或演示,一般还是建议把Go源码文件放入特定的代码包中。Go语言的源码文件分为3类:Go库源码文件、Go命令源码文件和Go测试源码文件。2、pkg目录 用于存放经由go install命令构建安装后的代码包(包含Go库源码文件)的“.a”归档文件。该目录与GOROOT目录下的pkg功能类似。区别在于,工作区中的pkg目录专门用来存放用户(也就是程序开发者)代码的归档文件。构建和安装用户源码的过程一般会以代码包为单位进行,比如logging包被编译安装后,将生成一个名为logging.a的归档文件,并存放在当前工作区的pkg目录下的平台相关目录中。3、bin目录 与pkg目录类似,在通过go install命令完成安装后,保存由Go命令源码文件生成的可执行文件。在Linux操作系统下,这个可执行文件一般是一个与源码文件同名的文件。而在Windows操作系统下,这个可执行文件的名称是源码文件名称加.exe后缀。注意: 这里有必要明确一下Go语言的命令源码文件和库源码文件的区别。所谓命令源码文件,就是声明为属于main代码包,并且包含无参数声明和结果声明的main函数的源码文件。 这类源码文件可以独立运行(使用go run命令),也可被go build或go install命令转换为可执行文件。而库源码文件则是指存在于某个代码包中的普通源码文件。三、go编译时,目录查找顺序go工程包含依赖包管理,GOROOT,GOPATH三类目录来查找编译需要的库。他们的顺序如下:从工程项目的root目录查找vendor目录中的依赖库。从用户环境变量$GOPATH/src中查找依赖库。从用户环境变量$GOROOT/src中查找依赖库。未找到,抛出异常,编译终止。总结: 通过对golang的目录结构的了解和编译时查找依赖库的顺序,对这门语言有一个初步的认识,接下来我们将通过go的内部命令深入了解一下它。 ...

February 26, 2019 · 1 min · jiezi

尝试 Docker + Nginx 部署单页应用

开发到部署,亲力亲为当我们开发一个单页面应用时,执行完构建后npm run build会生成一个 index.html 在 dist 目录,那怎么把这个 index.html 部署到服务器上呢?目录结构dist/:前端构建完的静态文件docker/:镜像所需的配置文件配置 Nginx挑几点配置讲讲,先是 Gzip 压缩资源,以节省带宽和提高浏览器加载速度虽然 Webpack 已经支持在构建时就生成 .gz 压缩包,但也可以通过 Nginx 来启用gzip on;gzip_disable “msie6”;# 0-9 等级,级别越高,压缩包越小,但对服务器性能要求也高gzip_comp_level 9;gzip_min_length 100;# Gzip 不支持压缩图片,我们只需要压缩前端资源gzip_types text/css application/javascript;再就是服务端口的配置,将 API 反向代理到后端服务server { listen 8080; server_name www.frontend.com; root /usr/share/nginx/html/; location / { index index.html index.htm; try_files $uri $uri/ /index.html; # 禁止缓存 HTML,以保证引用最新的 CSS 和 JS 资源 expires -1; } location /api/v1 { proxy_pass http://backend.com; }}完整配置长这样worker_processes 1;events { worker_connections 1024; }http { ## # Basic Settings ## sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; ## # Logging Settings ## access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; ## # Gzip Settings ## gzip on; gzip_disable “msie6”; gzip_comp_level 9; gzip_min_length 100; gzip_types text/css application/javascript; server { listen 8080; server_name www.frontend.com; root /usr/share/nginx/html/; location / { index index.html index.htm; try_files $uri $uri/ /index.html; expires -1; } location /api/v1 { proxy_pass http://backend.com; } }}配置 Docker这里简单一点,基于基础镜像,拷贝我们写好的 nginx.conf 和 index.html 到镜像内FROM nginx:alpineCOPY nginx.conf /etc/nginx/nginx.confCOPY dist /usr/share/nginx/html编写 Makefile完成了上面的准备,就可以编写命令来执行镜像的打包了先给镜像取个名称和端口号APP_NAME = spa_nginx_dockerPORT = 8080通过 build 来打包镜像build: cp docker/Dockerfile . cp docker/nginx.conf . docker build -t $(APP_NAME) . rm Dockerfile rm nginx.conf通过 deploy 来启动镜像deploy: docker run -d -it -p=$(PORT):$(PORT) –name="$(APP_NAME)" $(APP_NAME)最后还有个 stop 来停止和清理镜像stop: docker stop $(APP_NAME) docker rm $(APP_NAME) docker rmi $(APP_NAME)完整配置长这样APP_NAME = spa_nginx_dockerPORT = 8080build: cp docker/Dockerfile . cp docker/nginx.conf . docker build -t $(APP_NAME) . rm Dockerfile rm nginx.confdeploy: docker run -d -it -p=$(PORT):$(PORT) –name="$(APP_NAME)" $(APP_NAME)stop: docker stop $(APP_NAME) docker rm $(APP_NAME) docker rmi $(APP_NAME)完整命令长这样# 静态资源构建npm run build# 镜像打包make build# 停止并删除旧镜像(首次可忽略)make stop# 镜像启动make deploy总结目前的部署方法相对简单,后续会加入基础镜像和镜像仓库的使用,先去前面探探路 ...

February 25, 2019 · 2 min · jiezi

个人博客四|注册登录退出功能后台开发

声明:本博客的注册登录退出功能将使用django-allauth,参考资源如下:django-allauth文档django-allauth教程1、安装django-allauthpip install django-allauth2、配置信息安装后设置blog/settings.py,将allauth相关APP加入到INSTALLED_APP里去。INSTALLED_APPS = [ ‘django.contrib.admin’, ‘django.contrib.auth’, ‘django.contrib.contenttypes’, ‘django.contrib.sessions’, ‘django.contrib.messages’, ‘django.contrib.staticfiles’, # <添加storm相关应用> ‘storm’, # <storm–end—> # <添加allauth相关应用> ‘django.contrib.sites’, ‘allauth’, ‘allauth.account’, ‘allauth.socialaccount’, ‘allauth.socialaccount.providers.github’, # <allauth–end—>]注意:allauth对于站点设置django.contrib.sites有依赖,你必需也把它加入进去,同时设置SITE_IDSITE_ID没必要深入了解,目前不涉及多站点。目前能涉及到的是当出现"SocialApp matching query does not exist"这种报错的时需要更换SITE_ID值3、allauth 基本设置# 多站点框架:# 位于django.contrib.sites的site。# SITE_ID指定与特定配置文件相关联的site对象之数据库的ID。# 当出现"SocialApp matching query does not exist",这种报错的时候就需要更换这个IDSITE_ID = 1# 设置登录和注册成功后重定向的页面,默认是/accounts/profile/LOGIN_REDIRECT_URL = “/”# Email setting# 禁用注册邮箱验证ACCOUNT_EMAIL_VERIFICATION = ’none’# 登录方式,选择用户名或者邮箱登录ACCOUNT_AUTHENTICATION_METHOD = “username_email”# 设置用户注册的时候必须填写邮箱地址ACCOUNT_EMAIL_REQUIRED = True# 登出直接退出,不用确认ACCOUNT_LOGOUT_ON_GET = True4、django-allauth常见设置选项你也可以添加其它设置选项来实现你所想要的功能, 比如设置邮件确认过期时间,限制用户使用错误密码登录的持续时间。# 指定要使用的登录方法(用户名、电子邮件地址或两者之一)ACCOUNT_AUTHENTICATION_METHOD (=“username” | “email” | “username_email”)# 邮件确认邮件的截止日期(天数)ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS (=3)# 注册中邮件验证方法:“强制(mandatory)”,“可选(optional)”或“否(none)”之一ACCOUNT_EMAIL_VERIFICATION (=“optional”)# 邮件发送后的冷却时间(以秒为单位)ACCOUNT_EMAIL_CONFIRMATION_COOLDOWN (=180)# 登录尝试失败的次数ACCOUNT_LOGIN_ATTEMPTS_LIMIT (=5)# 从上次失败的登录尝试,用户被禁止尝试登录的持续时间ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT (=300)# 更改为True,用户一旦确认他们的电子邮件地址,就会自动登录ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION (=False)# 更改或设置密码后是否自动退出ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE (=False)# 更改为True,用户将在重置密码后自动登录ACCOUNT_LOGIN_ON_PASSWORD_RESET (=False)# 控制会话的生命周期,可选项还有:False,TrueACCOUNT_SESSION_REMEMBER (=None)# 用户注册时是否需要输入邮箱两遍ACCOUNT_SIGNUP_EMAIL_ENTER_TWICE (=False)# 用户注册时是否需要用户输入两遍密码ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE (=True)# 用户不能使用的用户名列表ACCOUNT_USERNAME_BLACKLIST (=[])# 加强电子邮件地址的唯一性ACCOUNT_UNIQUE_EMAIL (=True)# 用户名允许的最小长度的整数ACCOUNT_USERNAME_MIN_LENGTH (=1)# 使用从社会帐户提供者检索的字段(如用户名、邮件)来绕过注册表单SOCIALACCOUNT_AUTO_SIGNUP (=True)# 设置登录后跳转链接LOGIN_REDIRECT_URL (="/") # 设置退出登录后跳转链接ACCOUNT_LOGOUT_REDIRECT_URL (="/") 5、配置allauth路由urlpatterns = [ url(r’^admin/’, admin.site.urls), # allauth url(r’^accounts/’, include(‘allauth.urls’)), # storm url(’’, include(‘storm.urls’, namespace=‘blog’)), # blog]6、运行效果可以访问哪个路由,取决于,blog/settings.py中allauth设置信息注册http://127.0.0.1:8080/accounts/signup/登录http://127.0.0.1:8080/accounts/login/7、django-allauth全部路由下面是django_allauth所有内置的URLs,均可以访问的。可以去allauth/account/urls.py查看# 登录/accounts/login/# 注册/accounts/signup/# 重置密码/accounts/password/reset/# 退出登录/accounts/logout/# 设置密码 /accounts/password/set/# 改变密码(需登录)/accounts/password/change/# 用户可以添加和移除email,并验证/accounts/email/# 管理第三方账户/accounts/social/connections/用户详细信息是没有的/accounts/profile/如果我希望用户在注册时提供更多信息(比如公司名、电话、住址等)如果用户在注册后需要修改个人信息怎么办?由于每个开发者对用户所需提供的额外信息需求是不一样的,所以没有提供这个视图和URL。因此django-allauth并没有提供用户详情应用用户详情请参考:[个人博客五|用户个人资料Profile扩展] ...

February 22, 2019 · 1 min · jiezi

使用Envoy 作Sidecar Proxy的微服务模式-1.熔断

本博客是深入研究Envoy Proxy和Istio.io 以及它如何实现更优雅的方式来连接和管理微服务系列文章的一部分。这是接下来几个部分的想法(将在发布时更新链接):断路器(第一部分)重试/超时(第二部分)分布式跟踪(第三部分)Prometheus的指标收集(第四部分)服务发现(第五部分)第一部分 - 使用envoy proxy 熔断这篇第一篇博文向您介绍了Envoy Proxy实现的熔断功能。有意进行一些简单的演示,因此我可以单独说明模式和用法。请下载此演示的源代码并按照说明进行操作!该演示由一个客户端和一个服务组成。客户端是一个Java http应用程序,模拟对“上游”服务进行http调用(注意,我们在这里使用Envoys术语,并贯穿整个repo)。客户端打包在docker.io/ceposta/http-envoy-client:latest的Docker镜像中。除了http-client Java应用程序之外,还有Envoy Proxy的一个实例。在此部署模型中,Envoy被部署为服务的sidercar(在本例中为http客户端)。当http-client进行出站调用(到“上游”服务)时,所有调用都通过Envoy Proxy sidercar。这些示例的“上游”服务是httpbin.org。 httpbin.org允许我们轻松模拟HTTP服务行为。它很棒,所以如果你没有看到它,请查看它。这个熔断器演示有自己的envoy.json配置文件。我绝对建议您查看配置文件每个部分的参考文档,以帮助理解完整配置。 datawire.io的优秀人员也为Envoy及其配置提供了一个很好的介绍,你也应该检查一下。运行 circuit-breaker demo运行熔断器演示,请熟悉演示框架,然后运行:./docker-run.sh -d circuit-breaker熔断器的Envoy配置如下所示(请参阅此处的完整配置):“circuit_breakers”: { “default”: { “max_connections”: 1, “max_pending_requests”: 1, “max_retries”: 3 }}该配置文件允许我们实现下面的功能:限制我们对上游集群的HTTP / 1连接的数量,如果我们超过设定限制则将它们短路。限制排队/等待连接可用的请求数量,如果我们超过设定限制则将它们短路。限制在任何给定时间的总并发重试次数(假设重试策略已到位)有效地实施重试配额。我们来看看每个配置。我们现在将忽略最大重试次数设置有两个原因:我们的设置并没有多大意义;我们不能有3个并发重试,因为我们只允许1个HTTP连接和1个排队请求。我们实际上没有为此演示制定任何重试政策;我们可以在重试演示中看到重试。无论如何,此处的重试设置允许我们避免大型重试风暴 - 在大多数情况下,这可能会在处理与群集中所有实例的连接时出现问题。这是一个重要的设置,我们将回到重试演示。max_connections让我们看看当应用程序中有太多线程试图与上游集群建立太多并发连接时,envoy会做什么。回想一下我们的上游httbin集群的熔断设置如下所示(请参阅此处的完整配置):“circuit_breakers”: { “default”: { “max_connections”: 1, “max_pending_requests”: 1, “max_retries”: 3 }}如果我们查看./circuit-breaker/http-client.env设置文件,我们将看到最初我们将开始运行单个线程,该线程创建一个连接并进行五次调用并关闭。NUM_THREADS=1DELAY_BETWEEN_CALLS=0NUM_CALLS_PER_CLIENT=5URL_UNDER_TEST=http://localhost:15001/getMIX_RESPONSE_TIMES=false我们来验证一下。运行演示:./docker-run.sh -d circuit-breaker这将启动了客户端应用程序,并启动了Envoy Proxy。我们将直接向Envoy Proxy发送流量,以使其帮帮助处理熔断。让我们调用我们的服务:docker exec -it client bash -c ‘java -jar http-client.jar’我们将看到以下的输出:using num threads: 1Starting pool-1-thread-1 with numCalls=5 delayBetweenCalls=0 url=http://localhost:15001/get mixedRespTimes=falsepool-1-thread-1: successes=[5], failures=[0], duration=[545ms]我们也能看到我们五次的调用成功了。让我们看一下,Envoy为我们收集的metrics指标:./get-envoy-stats.shEnvoy为我们采集了很多的追踪指标!让我们通过以下方式查看:/get-envoy-stats.sh | grep cluster.httpbin_service这将显示我们配置的名为httpbin_service的上游群集的度量标准。快速浏览一下这些统计数据,并在Envoy文档中查找它们的含义。需要注意的重要事项在这里提到:cluster.httpbin_service.upstream_cx_http1_total: 1cluster.httpbin_service.upstream_rq_total: 5cluster.httpbin_service.upstream_rq_200: 5cluster.httpbin_service.upstream_rq_2xx: 5cluster.httpbin_service.upstream_rq_pending_overflow: 0cluster.httpbin_service.upstream_rq_retry: 0这告诉我们我们有1个http / 1连接,有5个请求(总数),其中5个以HTTP 2xx(甚至200个)结束。大!但是如果我们尝试使用两个并发连接会发生什么?首先,让我们重置统计数据:./reset-envoy-stats.shOK让我们用2个线程发起这些调用:docker exec -it client bash -c ‘NUM_THREADS=2; java -jar http-client.jar’我们应该可以看到如下的输出:using num threads: 2Starting pool-1-thread-1 with numCalls=5 delayBetweenCalls=0 url=http://localhost:15001/get mixedRespTimes=falseStarting pool-1-thread-2 with numCalls=5 delayBetweenCalls=0 url=http://localhost:15001/get mixedRespTimes=falsepool-1-thread-1: successes=[0], failures=[5], duration=[123ms]pool-1-thread-2: successes=[5], failures=[0], duration=[513ms]我们启动的一个线程中有5个成功,但其中另外一个线程一个都没有成功!该线程的所有5个请求都失败了!让我们再看看envoy的统计数据:./get-envoy-stats.sh | grep cluster.httpbin_service我们将看到如下的输出:cluster.httpbin_service.upstream_cx_http1_total: 1cluster.httpbin_service.upstream_rq_total: 5cluster.httpbin_service.upstream_rq_200: 5cluster.httpbin_service.upstream_rq_2xx: 5cluster.httpbin_service.upstream_rq_503: 5cluster.httpbin_service.upstream_rq_5xx: 5cluster.httpbin_service.upstream_rq_pending_overflow: 5cluster.httpbin_service.upstream_rq_retry: 0从这个输出中我们可以看到只有一个连接成功!我们最终得到5个请求,导致HTTP 200和5个请求以HTTP 503结束。我们还看到upstream_rq_pending_overflow已经增加到5.这表明断路器在这里完成了它的工作。它会使任何与我们的配置设置不匹配的调用短路。我们将max_connections人为设置为一个小点的数字,在这种情况下为1,为了说明Envoy的断路功能。这不是一个现实的设置,但希望有助于说明这一点。max_pending_requests让我们运行一些类似的测试来运行max_pending_requests设置。回想一下我们的上游httbin集群的熔断设置如下所示(请参阅此处的完整配置):“circuit_breakers”: { “default”: { “max_connections”: 1, “max_pending_requests”: 1, “max_retries”: 3 }}我们想要做的是模拟在单个HTTP连接上同时发生的多个请求(因为我们只允许max_connections为1)。我们期望请求排队,但是Envoy应该拒绝排队的消息,因为我们将max_pending_requests设置为1。我们想要设置队列深度的上限,目的不允许重试风暴,恶意下游请求,DoS和我们系统中的bug。继续上一节,让我们重置特使的统计数据:./reset-envoy-stats.shOK让我们启动1个线程(即1个HTTP连接)调用客户端,但是并行发送我们的请求(默认情况下是5个批次)。我们还希望随机化我们发送的延迟,以便事情可以排队:docker exec -it client bash -c ‘NUM_THREADS=1 && PARALLEL_SENDS=true && MIX_RESPONSE_TIMES=true; java -jar http-client.jar’我们应该看到如下的输出:Starting pool-1-thread-1 with numCalls=5 parallelSends=true delayBetweenCalls=0 url=http://localhost:15001/get mixedRespTimes=truepool-2-thread-3: using delay of : 3pool-2-thread-2: using delay of : 0pool-2-thread-1: using delay of : 2pool-2-thread-4: using delay of : 4pool-2-thread-5: using delay of : 0finished batch 0pool-1-thread-1: successes=[1], failures=[4], duration=[4242ms]我们的四个要求失败了……让我们查看envoy的统计数据:./get-envoy-stats.sh | grep cluster.httpbin_service | grep pending果然,我们看到我们的4个请求被短路了:cluster.httpbin_service.upstream_rq_pending_active: 0cluster.httpbin_service.upstream_rq_pending_failure_eject: 0cluster.httpbin_service.upstream_rq_pending_overflow: 4cluster.httpbin_service.upstream_rq_pending_total: 1什么时候服务完全停止?我们已经看到了Envoy对集群的短路和批量处理线程有什么断路设施,但是如果集群中的节点完全崩溃(或者似乎下降)怎么办?Envoy具有“离群值检测”设置,可以检测群集中的主机何时不可靠,并且可以完全从群集摘掉它们(一段时间)。需要了解的一个有趣现象是,默认情况下,Envoy会根据负载平衡算法,最多摘除某一数量的不可靠的主机。如果太多(即> 50%)的主机被认为是不健康的,那么Envoy的负载均衡器算法将检测到一个恐慌阈值,并且只会对所有主机进行负载均衡。此恐慌阈值是可配置的,并且为了获得断电功能,可以在严重中断期间为所有主机提供负载(一段时间),您可以配置异常值检测设置。在我们的示例断路器)envoy.json配置中,您可以看到以下内容:outlier_detection" : { “consecutive_5xx”: 5, “max_ejection_percent”: 100, “interval_ms”: 3 } 让我们测试一下这个案例,看看会发生什么。首先,重置统计数据:./reset-envoy-stats.shOK接下来,让我们使用一个URL来调用我们的客户端,该URL将返回HTTP 500结果。我们将发起十次调用,因为我们的异常检测将检查5个连续的5xx响应,因此我们将要发起多于5次的调用。docker exec -it client bash -c ‘URL_UNDER_TEST=http://localhost:15001/status/500 && NUM_CALLS_PER_CLIENT=10; java -jar http-client.jar’我们应该看到这样的响应,其中所有调用都失败了(正如我们所期望的那样:其中至少有5个会返回HTTP 500):using num threads: 1Starting pool-1-thread-1 with numCalls=10 parallelSends=false delayBetweenCalls=0 url=http://localhost:15001/status/500 mixedRespTimes=falsepool-1-thread-1: successes=[0], failures=[10], duration=[929ms]现在让我们检查一下Envoy的统计数据,看看究竟发生了什么:./get-envoy-stats.sh | grep cluster.httpbin_service | grep outliercluster.httpbin_service.outlier_detection.ejections_active: 0cluster.httpbin_service.outlier_detection.ejections_consecutive_5xx: 1cluster.httpbin_service.outlier_detection.ejections_overflow: 0cluster.httpbin_service.outlier_detection.ejections_success_rate: 0cluster.httpbin_service.outlier_detection.ejections_total: 1我们可以看到我们断电了连续5xx检测!我们还从负载均衡组中删除了该主机。 ...

February 22, 2019 · 2 min · jiezi

为何把日志打印到控制台很慢?

原文在容器打印日志到控制台阻塞的排障的时候看到一个观点:把日志打印到控制台要比打印到文件慢,而且是非常慢。log4j2和logback的两个issue官方也提到了这一点(见LOG4J2-2239、LOGBACK-1422)。那么为何输出到控制台慢?有何办法加速呢?问题要从三个角度来分别回答:linux的stdout角度Java程序角度docker容器角度stdout角度写到控制台其实就是写到stdout,更严格的说应该是fd/1。Linux操作系统将fd/0、fd/1和fd/2分别对应stdin、stdout和stdout。那么问题就变成为何写到stdout慢,有何优化办法?造成stdout慢的原因有两个:你使用的终端会拖累stdout的输出效率stdout的缓冲机制在SO的这个问题中:Why is printing to stdout so slow? Can it be sped up?,这回答提到打印到stdout慢是因为终端的关系,换一个快速的终端就能提升。这解释了第一个原因。stdout本身的缓冲机制是怎样的?Stdout Buffering介绍了glibc对于stdout缓冲的做法:当stdout指向的是终端的时候,那么它的缓冲行为是line-buffered,意思是如果缓冲满了或者遇到了newline字符,那么就flush。当stdout没有指向终端的时候,那么它的缓冲行为是fully-buffered,意思是只有当缓冲满了的时候,才会flush。其中缓冲区大小是4k。下面是一个总结的表格“GNU libc (glibc) uses the following rules for buffering”:StreamTypeBehaviorstdininputline-bufferedstdout (TTY)outputline-bufferedstdout (not a TTY)outputfully-bufferedstderroutputunbuffered那也就是说当stdout指向一个终端的时候,它采用的是line-buffered策略,而终端的处理速度直接影响到了性能。同时也给了我们另一个思路,不将stdout指向终端,那么就能够用到fully-buffered,比起line-buffered能够带来更大提速效果(想想极端情况下每行只有一个字符)。我写了一段小代码来做测试(gist)。先试一下stdout指向终端的情况:$ javac ConsolePrint.java$ java ConsolePrint 100000…lines: 100,000System.out.println: 1,270 msfile: 72 ms/dev/stdout: 1,153 ms代码测试了三种用法:System.out.println 指的是使用System.out.println所花费的时间file 指的是用4k BufferedOutputStream 写到一个文件所花费的时间/dev/stdout 则是同样适用4k BufferedOutputStream 直接写到/dev/stdout所花费的时间发现写到文件花费速度最快,用System.out.println和写到/dev/stdout所花时间在一个数量级上。如果我们将输出重定向到文件:$ java ConsolePrint 100000 > a$ tail -n 5 a…System.out.println: 920 msfile: 76 ms/dev/stdout: 31 ms则会发现/dev/stdout速度提升到file一个档次,而System.out.println并没有提升多少。之前不是说stdout不指向终端能够带来性能提升吗,为何System.out.println没有变化呢?这就要Java对于System.out的实现说起了。Java程序角度下面是System的源码:public final static PrintStream out = null;…private static void initializeSystemClass() { FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out); setOut0(newPrintStream(fdOut, props.getProperty(“sun.stdout.encoding”)));}…private static native void setOut0(PrintStream out);…private static PrintStream newPrintStream(FileOutputStream fos, String enc) { … return new PrintStream(new BufferedOutputStream(fos, 128), true);}可以看到System.out是PrintStream类型,下面是PrintStream的源码:private void write(String s) { try { synchronized (this) { ensureOpen(); textOut.write(s); textOut.flushBuffer(); charOut.flushBuffer(); if (autoFlush && (s.indexOf(’\n’) >= 0)) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; }}可以看到:System.out使用的缓冲大小仅为128字节。大部分情况下够用。System.out开启了autoFlush,即每次write都会立即flush。这保证了输出的及时性。PrintStream的所有方法加了同步块。这避免了多线程打印内容重叠的问题。PrintStream如果遇到了newline符,也会立即flush(相当于line-buffered)。同样保证了输出的及时性。这解释了为何System.out慢的原因,同时也告诉了我们就算把System.out包到BufferedOutputStream里也不会有性能提升。Docker容器角度那么把测试代码放到Docker容器内运行会怎样呢?把gist里的Dockerfile和ConsolePrint.java放到同一个目录里然后这样运行:$ docker build -t console-print .$ docker run -d –name console-print console-print 100000$ docker logs –tail 5 console-print…lines: 100,000System.out.println: 2,563 msfile: 27 ms/dev/stdout: 2,685 ms可以发现System.out.println和/dev/stdout的速度又变回一样慢了。因此可以怀疑stdout使用的是line-buffered模式。为何容器内的stdout不使用fully-buffered模式呢?下面是我的两个猜测:不论你是docker run -t分配tty启动,还是docker run -d不非配tty启动,docker都会给容器内的stdout分配一个tty。因为docker的logging driver都是以“行”为单位收集日志的,那么这个tty必须是line-buffered。虽然System.out.println很慢,但是其吞吐量也能够达到~40,000 lines/sec,对于大多数程序来说这不会造成瓶颈。参考文档Standard output (stdout)Stdout Buffering ...

February 22, 2019 · 1 min · jiezi

docker swarm mode 下容器重启IP引发的 CLOSE_WAIT 问题

问题问题简述如下图. server docker restart后, client端写入的日志丢失, 并且无报错.因为不支持时序图, 把时序图代码嵌入在代码里.sequenceclient-&gt;server: log_dataclient-&gt;server: log_dataserver-&gt;server: docker restartserver-&gt;client: finclient-&gt;server: log_data loss without errortcp state diagram问题定位过程为什么卡在CLOSE_WAIT.看tcp状态转换图, 可以看到client收到了fin, 一直没有recv, 一直卡在CLOSE_WAIT. 和实际的代码是吻合的. 那么, 为什么在server docker restart 引发CLOSE_WAIT后, client发消息仍然不报错呢?因为:tcp协议允许client在收到fin后, 继续发送消息.server 在docker restart后 ip 改变, client还是往原来的ip发送消息, 没有主机通知client rst, 导致消息在系统buffer里积压.积压信息如下:root@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0tcp 1 402 10.0.0.186:62281 10.0.0.16:27017 CLOSE_WAIT 4308/serverroot@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0tcp 1 70125 10.0.0.186:62281 10.0.0.16:27017 CLOSE_WAIT 4308/server此时, 在elixir socket接口层面来看, 不管socket的状态, 还是发送, 都是ok的.iex(client@client.)25> socket |> :inet.port{:ok, 57395}iex(client@client.)26> socket |> :gen_tcp.send(“aaa”):ok如果主动close, 则会进入LAST_ACK状态iex(client@client.)27> socket |> :gen_tcp.close() :okroot@9eeaefa7fe57:/# netstat -nap | grep 27017 | grep 10.0.0tcp 1 70126 10.0.0.186:62281 10.0.0.16:27017 LAST_ACK - CLOSE_WAIT的恢复如果代码还是只发不收. 是检测不到CLOSE_WAIT的. 显然, 应用层心跳是一个解决方案. 那么, 不使用心跳, 只发不收的情况下, 什么时候才能检测到错误呢?send buffer 满todo 深究tcp keepalive, 不使用 keepalive情况下的 tcp 最大链接空闲时间. ...

February 22, 2019 · 1 min · jiezi

Docker容器如何获得自己的名字

原文本文介绍的方法是通过环境变量把容器自己的名字传递进去,仅支持以下两种部署方式:docker service createdocker stack deploydocker service createdocker service create -e MY_NAME="{{.Task.Name}}" -d –name abc tomcat:8.5-alpine这样容器里的MY_NAME环境变量就是容器自己的名字,比如:abc.1.rik8xgc0b9i2r7odnm6vnhnqgdocker stack deploydocker-compose file:version: ‘3.7’services: webapp: image: tomcat:8.5-alpine environment: MY_NAME: “{{.Task.Name}}“同样地将容器名传到环境变量MY_NAME里。参考资料Docker logging best practice,在这个文章里提到了可以用{{.Task.Name}}做template expansion来设置变量。上述两种方式都用到了go template,Format command and log output 列举了几种template expansion的使用方式。Inject chosen container name in container,这个issue提出要能够在容器内获得自己的名字,但是此issue没有被解决,依然在讨论中。

February 22, 2019 · 1 min · jiezi

个人博客-首页后台开发(一)

声明:本渣渣部分代码参考自TendCode其实有很多代码是不需要自己一行行码出来,生产力是第一位。只有研究型人才需要生产代码,作为一名渣渣拿来用是最高效的做法。程序员都有一个开源的精神,码出来的代码本身是希望更多的人用到,应用到生产中。参考编写博客的 Model 与首页面整合资源也应该是一个码农的必备能力,在实际生产中可以更高效的工作项目环境windows10python==3.6.6django==1.11.1首先本渣渣想对django项目结构做一下梳理,如果有的读者对django不太熟悉,可能阅读有障碍。对django基础做一下简单了解。推荐阅读:Django1.10 中文文档大牛翻译的django基础部分简单了解通用视图开始 Django 开发之前,需要掌握以下知识:• 创建 Django 工程,了解 Django 默认的工程目录结构• 创建 Django APP• 理解 Django 的MTV 模式,学会编写 Model、View、Template• Django 如何处理静态文件,即各种 CSS,JS,以及图片文件等推荐阅读:个人博客-创建项目Django中MVC与MVT设计模式Django的工作流程1、 用户通过浏览器输入相应的 URL 发起 HTTP 请求(一般是 GET/POST)用户在网站前台做的每次操作都是一次HTTP请求的触发,通过触发的URL,和后台进行交互2:Django后台接收到请求,检测 urls.py 文件,找到和用户请求的URL 相匹配的项,然后调用该 URL 对应的视图函数(view),在视图内进行逻辑呈现,然后渲染数据到模板展示给用户。推荐阅读:静态路由和动态路由视图函数被调用后,可能会访问数据库(Model)去查询用户想要请求的数据,并加载模板文件(Template),渲染完数据后打包成 HttpResponse 返回给浏览器(Http协议)从流程可以看出,开发者需要做的事情如下:• 编写相应的 url• 编写数据库(Model)• 编写处理 Http 请求的视图函数(View)• 编写需要渲染数据的模板(Template)下面遵循这样的开发流程继续进行我们个人博客的开发:虽然博客站点的django项目已经烂大街了,但是很多人还没意识到个人博客的价值,有时间我们可以谈谈这个问题。第一版选择使用Django通用视图开发,后续升级前后端分离开发(正在学习中)推荐阅读:Django通用视图ListView 和 DetailView在接触到django到现在看到的教程大都没有采用django的通用视图,但是为了更好的认识django,在这里本渣渣,想尝试尽量在开发博客主体时使用通用视图为什么选择使用通用视图1、 目前在看到的django教程中大部分都未使用通用视图,顶多是继承通用类。这样你就没办法真正了解django框架,django独特之处就在于构建Web应用程序的常用功能已经在框架内,而不是单独的库。通用视图是Django为解决建站过程中的常见的呈现模式而建立的通用视图使用原则代码越少越好永远不要重复代码view应当只呈现逻辑, 不应包含业务逻辑保持view逻辑清晰简单2、 锻炼快速高效开发一类网站的能力了解了通用视图你可以快速开发一类网站比如博客、论坛都有相似的需求3、 组合方便这里本渣渣的做法是扒取崔庆才个人博客前端源码,然后通过django实现网站后端开发,使用django 通用视图,可以缩短开发周期,而且能够具备使用django快速模仿一个网站的能力下面不废话开干按流程来,规范的流程少出错,严谨应该是一个码农的素养目前项目文件结构1、前端把抓取崔庆才个人博客网站前端源码一文中抓取的崔庆才博客前端源码放入个人博客-创建项目一文中创建的项目中https://www.jianshu.com/p/246…2、配置基本信息设置blog/settings.py添加 apps 目录 sys.path.insert(0, os.path.join(BASE_DIR, ‘apps’))添加app# Application definitionINSTALLED_APPS = [ ‘django.contrib.admin’, ‘django.contrib.auth’, ‘django.contrib.contenttypes’, ‘django.contrib.sessions’, ‘django.contrib.messages’, ‘django.contrib.staticfiles’, ‘storm’,]添加模板ROOT_URLCONF = ‘blog.urls’TEMPLATES = [ { ‘BACKEND’: ‘django.template.backends.django.DjangoTemplates’, ‘DIRS’: [os.path.join(BASE_DIR, ’templates’)], # 设置视图 ‘APP_DIRS’: True, ‘OPTIONS’: { ‘context_processors’: [ ‘django.template.context_processors.debug’, ‘django.template.context_processors.request’, ‘django.contrib.auth.context_processors.auth’, ‘django.contrib.messages.context_processors.messages’, ], }, },]连接数据库,首先要在本地数据库创建名为blog的数据库这里需要用到数据库连接包,请参考安装python包到虚拟环境安装包Name: mysqlclientVersion: 1.4.2.post1DATABASES = { ‘default’: { ‘ENGINE’: ‘django.db.backends.mysql’, ‘HOST’: ’localhost’, ‘PORT’: ‘3306’, ‘USER’: ‘root’, ‘PASSWORD’: ‘root’, ‘NAME’: ‘blog’, # 如果创建的数据库是uft-8编码,映射数据库时出现警告,添加此行解决问题 ‘OPTIONS’: { ‘init_command’: “SET sql_mode=‘STRICT_TRANS_TABLES’”, ‘charset’: ‘utf8mb4’, }, }}配置静态文件路径STATIC_URL = ‘/static/‘STATICFILES_DIRS = [ os.path.join(BASE_DIR, ‘static’)]model.py编写针对blog开发大体有这几个通用的models类文章、幻灯片、文章标签、文章分类、友情链接这里为了后期能够更好的给本站做SEO优化,这里添加了一个,文章关键词,死链文章、幻灯片、文章标签、文章分类、友情链接、关键词、死链由此可见,设计数据库结构就是编写 models,数据库中每一个实体对应的表在 django 中对用着 models.py 中的一个类,类的属性对应着数据库表的属性列。# 文章关键词,用来作为SEO中keywordsclass Keyword(models.Model): name = models.CharField(‘文章关键词’, max_length=20) class Meta: verbose_name = ‘关键词’ verbose_name_plural = verbose_name ordering = [’name’] def str(self): return self.name# 文章标签class Tag(models.Model): name = models.CharField(‘文章标签’, max_length=20) slug = models.SlugField(unique=True) description = models.TextField(‘描述’, max_length=240, default=settings.SITE_DESCRIPTION, help_text=‘用来作为SEO中description,长度参考SEO标准’) class Meta: verbose_name = ‘标签’ verbose_name_plural = verbose_name ordering = [‘id’] def str(self): return self.name def get_absolute_url(self): return reverse(‘blog:tag’, kwargs={‘slug’: self.slug}) def get_article_list(self): ‘‘‘返回当前标签下所有发表的文章列表’’’ return Article.objects.filter(tags=self)# 文章分类class Category(models.Model): name = models.CharField(‘文章分类’, max_length=20) slug = models.SlugField(unique=True) description = models.TextField(‘描述’, max_length=240, default=settings.SITE_DESCRIPTION, help_text=‘用来作为SEO中description,长度参考SEO标准’) class Meta: verbose_name = ‘分类’ verbose_name_plural = verbose_name ordering = [’name’] def str(self): return self.name def get_absolute_url(self): return reverse(‘blog:category’, kwargs={‘slug’: self.slug}) def get_article_list(self): return Article.objects.filter(category=self)# 文章class Article(models.Model): # 文章缩略图 IMG_LINK = ‘/static/img/summary.png’ author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=‘作者’) # 文章标题,CharField 表示对应数据库中数据表的列的数据类型,‘标题’是一个位置参数 #(verbose_name),主要用于 django 的后台系统。max_length 表示能存储的字符串 # 的最大长度 title = models.CharField(max_length=150, verbose_name=‘文章标题’) # 这里填的内容有助于后期SEO优化 summary = models.TextField(‘文章摘要’, max_length=230, default=‘文章摘要等同于网页description内容,请务必填写…’) # 文章正文,TextField 用来存储大文本字符 body = models.TextField(verbose_name=‘文章内容’) # 文章缩略图地址 img_link = models.CharField(‘图片地址’, default=IMG_LINK, max_length=255) # 文章创建时间,DateTimeField用于存储时间,设定auto_now_add参数为真,则在文章被创建时会自动添加创建时间 create_date = models.DateTimeField(verbose_name=‘创建时间’, auto_now_add=True) # 文章最后一次编辑时间,auto_now=True表示每次修改文章时自动添加修改的时间 update_date = models.DateTimeField(verbose_name=‘修改时间’, auto_now=True) # 阅览量,PositiveIntegerField存储非负整数 # views = models.PositiveIntegerField(‘阅览量’, default=0) views = models.IntegerField(‘阅览量’, default=0) slug = models.SlugField(unique=True)# 文章的分类,ForeignKey即数据库中的外键。外键的定义是:如果数据库中某个表的列的值是另外一个表的主键。外键定义了一个一对多的关系,这里即一篇文章对应一个分类,而一个分类下可能有多篇文章。详情参考django官方文档关于ForeinKey的说明,on_delete=models.SET_NULL表示删除某个分类(category)后该分类下所有的Article的外键设为null(空) category = models.ForeignKey(Category, verbose_name=‘文章分类’,on_delete=models.SET_NULL) tags = models.ManyToManyField(Tag, verbose_name=‘标签’) keywords = models.ManyToManyField(Keyword, verbose_name=‘文章关键词’, help_text=‘文章关键词,用来作为SEO中keywords,最好使用长尾词,3-4个足够’) def str(self): # 主要用于交互解释器显示表示该类的字符串 return self.title class Meta: verbose_name = ‘文章’ verbose_name_plural = verbose_name ordering = [’-create_date’]# 幻灯片class Carousel(models.Model): number = models.IntegerField(‘编号’, help_text=‘编号决定图片播放的顺序,图片不要多于5张’) title = models.CharField(‘标题’, max_length=20, blank=True, null=True, help_text=‘标题可以为空’) content = models.CharField(‘描述’, max_length=80) img_url = models.CharField(‘图片地址’, max_length=200) url = models.CharField(‘跳转链接’, max_length=200, default=’#’, help_text=‘图片跳转的超链接,默认#表示不跳转’) class Meta: verbose_name = ‘图片轮播’ verbose_name_plural = verbose_name # 编号越小越靠前,添加的时间越晚越靠前 ordering = [’number’, ‘-id’] def str(self): return self.content[:25]# 死链class Silian(models.Model): badurl = models.CharField(‘死链地址’, max_length=200, help_text=‘注意:地址是以http开头的完整链接格式’) remark = models.CharField(‘死链说明’, max_length=50, blank=True, null=True) add_date = models.DateTimeField(‘提交日期’, auto_now_add=True) class Meta: verbose_name = ‘死链’ verbose_name_plural = verbose_name ordering = [’-add_date’] def str(self): return self.badurlclass FriendLink(models.Model): name = models.CharField(‘网站名称’, max_length=50) description = models.CharField(‘网站描述’, max_length=100, blank=True) link = models.URLField(‘友链地址’, help_text=‘请填写http或https开头的完整形式地址’) logo = models.URLField(‘网站LOGO’, help_text=‘请填写http或https开头的完整形式地址’, blank=True) create_date = models.DateTimeField(‘创建时间’, auto_now_add=True) is_active = models.BooleanField(‘是否有效’, default=True) is_show = models.BooleanField(‘是否首页展示’, default=False) class Meta: verbose_name = ‘友情链接’ verbose_name_plural = verbose_name ordering = [‘create_date’] def str(self): return self.name def get_home_url(self): ‘‘‘提取友链的主页’’’ u = re.findall(r’(http|https://.?)/.?’, self.link) home_url = u[0] if u else self.link return home_url def active_to_false(self): self.is_active = False self.save(update_fields=[‘is_active’]) def show_to_false(self): self.is_show = True self.save(update_fields=[‘is_show’])model 定义完毕后,运行以下命令即可生成相应的数据库表:python manage.py makemigrationspython manage.py migrate针对models分别给出以下参考资料供学习:Models:官方文档Django1.10 中文文档大牛翻译推荐学习:自强学堂Django的ORM映射机制与数据库实战数据库模型(Models)对象映射表ORMfield(字段选项):官方文档个人技术博文ForeinKey(一对多)、ManyToMany(多对多)、OneToOne(一对一):官方文档Django的ORM映射机制与数据库实战View编写上面已经介绍了 django 应用的工作流程,数据库建立完毕后需要编写视图函数(view)来处理 Http 请求。同样先来看 django 的 view 代码是如何写的,这里数据库内没有文章先不做文章展示逻辑编写,待后续一一进行,这里只写一个视图让首页可以被访问。视图代码from django.views import genericfrom django.conf import settingsfrom .models import Article, Tag, Category, Timeline, Silian# Create your views here.class IndexView(generic.ListView): """ 首页视图,继承自ListVIew,用于展示从数据库中获取的文章列表 """ # 获取数据库中的文章列表 model = Article # template_name属性用于指定使用哪个模板进行渲染 template_name = ‘index.html’ # context_object_name属性用于给上下文变量取名(在模板中使用该名字) context_object_name = ‘articles’配置路由blog/urls.pyfrom django.conf.urls import url, includefrom django.contrib import adminurlpatterns = [ url(r’^admin/’, admin.site.urls), # allauth url(r’^accounts/’, include(‘allauth.urls’)), # storm url(’’, include(‘storm.urls’, namespace=‘blog’)), # blog]apps/storm/urls.py# —————————author = ‘stormsha’date = ‘2019/2/20 23:27’# —————————from django.conf.urls import url# from .views import goviewfrom .views import IndexViewurlpatterns = [ url(r’^$’, IndexView.as_view(), name=‘index’), # 主页,自然排序]运行效果现在首页流程基本走通之,差视图内的逻辑、和数据渲染的问题了,静待后续。总结本渣渣也是在繁忙的工作之余,抽出点时间想自己搞一下博客,但是不想草草了事,想从这个项目中实践如下知识抓取网站前端页面源码、Django通用视图、django rest framework、vue.js、redis数据缓存、docker容器部署、git、SEO技术钱途:想法——实现——部署——运营——维护——盈利有什么问题欢迎留言,或者关注微信公众号「stormsha」进行深度技术、思维碰撞交流。 ...

February 22, 2019 · 3 min · jiezi

全网最简单的k8s User JWT token管理器

kubernetes集群三步安装概述kubernetes server account的token很容易获取,但是User的token非常麻烦,本文给出一个极简的User token生成方式,让用户可以一个http请求就能获取到。token主要用来干啥官方dashboard登录时需要。 如果通过使用kubeconfig文件登录而文件中又没有token的话会失败,现在大部分文章都介绍使用service account的token来登录dashboard,能通,不过有问题:第一:绑定角色时要指定类型是service account:apiVersion: rbac.authorization.k8s.io/v1beta1kind: ClusterRoleBindingmetadata: name: kubernetes-dashboard labels: k8s-app: kubernetes-dashboardroleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-adminsubjects:- kind: ServiceAccount # 这里不是User类型 name: kubernetes-dashboard namespace: kube-system第二:要理解kubeconfig里是解析证书把CN作为用户名的,这时service account即便与CN一样那还是两个账户,绑定角色时还需要绑定两次,有点像把service account给"人"用, 所以把service account的token扔给某个开发人员去用往往不合适,service account token更多时候是给程序用的。想直接调用https的,没有token就会:[root@iZj6cegflzze2l7fpcqoerZ ssl]# curl https://172.31.12.61:6443/api/v1/namespaces/default/pods –insecure{ “kind”: “Status”, “apiVersion”: “v1”, “metadata”: { }, “status”: “Failure”, “message”: “pods is forbidden: User "system:anonymous" cannot list resource "pods" in API group "" in the namespace "default"”, “reason”: “Forbidden”, “details”: { “kind”: “pods” }, “code”: 403}因为没有任何认证信息,所以匿名(anonymous)用户没有任何权限加了token是这样的:[root@iZj6cegflzze2l7fpcqoerZ ssl]# curl -H “Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkNnYzRPVEV5TlRVM0VnWm5hWFJvZFdJIn0.eyJpc3MiOiJodHRwczovL2RleC5leGFtcGxlLmNvbTo4MDgwIiwic3ViIjoiQ2djNE9URXlOVFUzRWdabmFYUm9kV0kiLCJhdWQiOiJleGFtcGxlLWFwcCIsImV4cCI6MTU1MTA5NzkwNiwiaWF0IjoxNTUwNzM3OTA2LCJlbWFpbCI6ImZodGpvYkBob3RtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiZGV2Il0sIm5hbWUiOiJmYW51eCJ9.ZqKn461UW0aGtyjyqu2Dc5tiUzC-6eYLag542d3AvklUdZuw8i9XwyaUg_f1OAj0ZsEcOybOe9_PeGMaUYzU0OvlKPY-q2zbQVC-m6u6sQw6ZXx8pi0W8k4wQSJnMaOLddCfurlYufmr8kScDBQlnKapSR0F9mJzvpKkHD-XNshQKWhX3n03g7OfFgb4RuhLjKDNQnoGn7DfBNntibHlF9sPo0jC5JjqTZaGvoGmiRE4PAXwxA-RJifsWDNf_jW8lrDiY4NSO_3O081cia4N1GKht51q9W3eaNMvFDD9hje7abDdZoz9KPi2vc3zvgH7cNv0ExVHKaA0-dwAZgTx4g” -k https://172.31.12.61:6443/api/v1/namespaces/default/pods{ “kind”: “Status”, “apiVersion”: “v1”, “metadata”: { }, “status”: “Failure”, “message”: “pods is forbidden: User "https://dex.example.com:8080#fanux" cannot list resource "pods" in API group "" in the namespace "default"”, “reason”: “Forbidden”, “details”: { “kind”: “pods” }, “code”: 403}看,虽然还是403 但是已经有了用户信息,只要给该用户授权就可正常访问了,如何授权下文介绍token种类介绍token的生成方式有很多,主要分成三种:service account token 这个创建service account就有,存在secret里 获取比较简单,但是要区分好 User 和 service account区别普通的token,这种token就是个普通的字符串,一般是自己写一个认证的web hook, k8s认证时调用这个hook 查询token是否有效,比较low基于openid的jwt(josn web token) 这种token,认证中心把用户信息放在json里,用私钥加密,k8s拿到token后用公钥解密,只要解密成功token就是合法的而且能拿到用户信息,不需要再像认证中心请求基于openid的jwt是本文介绍的重点。社区用的比较多的就是dex,是一个比较完整的实现,但是对于不熟悉该技术的朋友来说还是有点门槛的,容易绕进去。 而且还存在一些使用不方便的问题。如依赖复杂,首先得需要一个真正的用户管理程序,如ldap 或者一个auth2服务端,这还可以接受,关键是认证时可能需要依赖浏览器进行跳转授权,这在十分多的场景里就变的十分尴尬,就比如我们的场景压根没有界面,这样生成token就成了一个大问题。 其次集成到别的系统中时往往用户已经登录过了,所以需要一个二次授权的过程才能拿到token,依赖过重导致系统难以设计。然而如果不是集成到别的系统中,比如从0开发一个完成的PaaS平台那使用dex还是一个完美的方案。所以我们实现了一个简单粗暴的方案,完全解放了这个过程, 只care最核心的东西。sealyun fist介绍我们想要啥?input:{ “User”: “fanux”, “Group”: [“sealyun”, “develop”]}output:eyJhbGciOiJSUzI1NiIsImtpZCI6IkNnYzRPVEV5TlRVM0VnWm5hWFJvZFdJIn0.eyJpc3MiOiJodHRwczovL2RleC5leGFtcGxlLmNvbTo4MDgwIiwic3ViIjoiQ2djNE9URXlOVFUzRWdabmFYUm9kV0kiLCJhdWQiOiJleGFtcGxlLWFwcCIsImV4cCI6MTU1MTA5NzkwNiwiaWF0IjoxNTUwNzM3OTA2LCJlbWFpbCI6ImZodGpvYkBob3RtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiZGV2Il0sIm5hbWUiOiJmYW51eCJ9.ZqKn461UW0aGtyjyqu2Dc5tiUzC-6eYLag542d3AvklUdZuw8i9XwyaUg_f1OAj0ZsEcOybOe9_PeGMaUYzU0OvlKPY-q2zbQVC-m6u6sQw6ZXx8pi0W8k4wQSJnMaOLddCfurlYufmr8kScDBQlnKapSR0F9mJzvpKkHD-XNshQKWhX3n03g7OfFgb4RuhLjKDNQnoGn7DfBNntibHlF9sPo0jC5JjqTZaGvoGmiRE4PAXwxA-RJifsWDNf_jW8lrDiY4NSO_3O081cia4N1GKht51q9W3eaNMvFDD9hje7abDdZoz9KPi2vc3zvgH7cNv0ExVHKaA0-dwAZgTx4g结束,多简单,别整那么多没用的。所以为了实现上面的功能,我们开发了 fist, fist的auth模块把dex里最核心的token生成功能以及jwt功能实现了。sealyun fist/auth 使用教程安装部署生成证书# mkdir /etc/kubernetes/pki/fist# cd /etc/kubernetes/pki/fist# sh gencert.sh # 脚本内容内代码启动fist auth模块kubectl create -f deploy/fist-auth.yaml修改k8s apiserver启动参数vim /etc/kubernetes/manifests/kube-apiserver.yaml - command: - kube-apiserver - –oidc-issuer-url=https://fist.sealyun.svc.cluster.local:8080 - –oidc-client-id=example-app - –oidc-ca-file=/etc/kubernetes/pki/fist/ca.pem - –oidc-username-claim=name - –oidc-groups-claim=groups获取及使用 token获取tokencurl https://fist.sealyun.svc.cluster.local:8080/token?user=fanux&group=sealyun,develop –cacert ca.pem使用token直接curl加bare token 见上文加入到kubeconfig中:kubectl config set-credentials –token=eyJhbGciOiJSUzI1NiIsImtpZCI6IkNnYzRPVEV5TlRVM0VnWm5hWFJvZFdJIn0.eyJpc3MiOiJodHRwczovL2RleC5leGFtcGxlLmNvbTo4MDgwIiwic3ViIjoiQ2djNE9URXlOVFUzRWdabmFYUm9kV0kiLCJhdWQiOiJleGFtcGxlLWFwcCIsImV4cCI6MTU1MTEwMDI5MywiaWF0IjoxNTUwNzQwMjkzLCJlbWFpbCI6ImZodGpvYkBob3RtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiZGV2Il0sIm5hbWUiOiJmYW51eCJ9.OAK4oIYqJszm1EACYW2neXTo738RW9kXFOIN5bOT4Z2CeKAvYqyOVKCWZf04xX45jwT78mATR3uas2YvRooDXlvxaD3K43ls4KBSG-Ofp-ynqlcVTpD3sUDqyux2iieNv4N6IyCv11smrU0lIlkrQC6oyxzTGae1FrJVGc5rHNsIRZHp2WrQvw83uLn_elHgUfSlsOq0cPtVONaAQWMAMi2DX-y5GCNpn1CDvudGJihqsTciPx7bj0AOXyiOznWhV186Ybk-Rgqn8h0eBaQhFMyNpwVt6oIP5pvJQs0uoODeRv6P3I3-AjKyuCllh9KDtlCVvSP4WtMUTfHQN4BigQ kubernetes-admin然后.kube/config 文件里的 user.client-certifacate-data 和 client-key-data就可以删了,再执行kubectl会:[root@iZj6cegflzze2l7fpcqoerZ ~]# kubectl get podError from server (Forbidden): pods is forbidden: User “https://dex.example.com:8080#fanux" cannot list resource “pods” in API group "” in the namespace “default"说明新用户成功了授权[root@iZj6cegflzze2l7fpcqoerZ ~]# cat rolebind.yamlkind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata: name: read-secrets-globalsubjects:- kind: User name: “https://dex.example.com:8080#fanux" # Name is case sensitive apiGroup: rbac.authorization.k8s.ioroleRef: kind: ClusterRole name: cluster-admin # 超级用户给他 apiGroup: rbac.authorization.k8s.io创建个role binding即可:[root@iZj6cegflzze2l7fpcqoerZ ~]# kubectl –kubeconfig /etc/kubernetes/admin.conf create -f rolebind.yaml # 用管理员的kubeconfigclusterrolebinding.rbac.authorization.k8s.io/read-secrets-global created[root@iZj6cegflzze2l7fpcqoerZ ~]# kubectl get pod # 有权限访问pod了No resources found.原理介绍jwt原理 https://fist.sealyun.cluster.local:8080k8s jwt server | /.well-known/openid-configuration | |————————————————>| k8s通过此url发现一些信息,最重要的就是用于校验token公钥的地址 | discover info | |<————————————————| | /keys | |————————————————>| 上一步拿到地址,这一步获取到公钥 | public keys | |<————————————————| | |discoer info 是个json:{“issuer”: “https://accounts.google.com”,“authorization_endpoint”: “https://accounts.google.com/o/oauth2/v2/auth","token_endpoint": “https://oauth2.googleapis.com/token","userinfo_endpoint": “https://openidconnect.googleapis.com/v1/userinfo","revocation_endpoint": “https://oauth2.googleapis.com/revoke","jwks_uri": “https://www.googleapis.com/oauth2/v3/certs","response_types_supported": [“code”,“token”,“id_token”,“code token”,“code id_token”,“token id_token”,“code token id_token”,“none”],…public keys也是个json 类似:{“keys”: [{“e”: “AQAB”,“kty”: “RSA”,“alg”: “RS256”,“n”: “3MdFK4pXPvehMipDL_COfqn6o9soHgSaq_V1o8U_5gTZ-j9DxO9PV7BVncXBgHFctnp3JQ1QTDF7txeHeuLOS4KziRw5r4ohaj2WoOTqXh7lqVMR2YDAcBK46asS177NpkQ1CqHIsy3kNfqhXLwTaKfdlwdA_XUfRbKORWbq0kDxV35egx35nHl5qJ6aP6fcpsnnPvHf7KWO0zkdvwuR-IX79HjqUAEg5UERd5FK4y06PRbxuXHjAgVhHu_sk4reNXNp1HRuTYtQ26DFbVaIjsWb8-nQC8-7FkTjlw9FteAwLVGOm9sTLFp73jAf0pWLh7sJ02pBxZKjsxLO1Lvg7w”,“use”: “sig”,“kid”: “7c309e3a1c1999cb0404ab7125ee40b7cdbcaf7d”},{“alg”: “RS256”,“n”: “2K7epoJWl_B68lRUi1txaa0kEuIK4WHiHpi1yC4kPyu48d046yLlrwuvbQMbog2YTOZdVoG1D4zlWKHuVY00O80U1ocFmBl3fKVrUMakvHru0C0mAcEUQo7ItyEX7rpOVYtxlrVk6G8PY4EK61EB-Xe35P0zb2AMZn7Tvm9-tLcccqYlrYBO4SWOwd5uBSqc_WcNJXgnQ-9sYEZ0JUMhKZelEMrpX72hslmduiz-LMsXCnbS7jDGcUuSjHXVLM9tb1SQynx5Xz9xyGeN4rQLnFIKvgwpiqnvLpbMo6grhJwrz67d1X6MwpKtAcqZ2V2v4rQsjbblNH7GzF8ZsfOaqw”,“use”: “sig”,“kid”: “7d680d8c70d44e947133cbd499ebc1a61c3d5abc”,“e”: “AQAB”,“kty”: “RSA”}]}所以fist只需要实现这两个url 和 用私钥匙加密用户信息生成token即可。创建密钥对: key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { log.Fatalf(“gen rsa key: %v”, err) } priv = jose.JSONWebKey{ Key: key, KeyID: “Cgc4OTEyNTU3EgZnaXRodWI”, Algorithm: “RS256”, Use: “sig”, } pub = jose.JSONWebKey{ Key: key.Public(), KeyID: “Cgc4OTEyNTU3EgZnaXRodWI”, Algorithm: “RS256”, Use: “sig”, }私钥加密: tok := idTokenClaims{ Issuer: “https://dex.example.com:8080”, Subject: “Cgc4OTEyNTU3EgZnaXRodWI”, Audience: “example-app”, Expiry: time.Now().Add(time.Hour * 100).Unix(), IssuedAt: time.Now().Unix(), Email: “fhtjob@hotmail.com”, EmailVerified: &ev, Groups: []string{“dev”}, Name: “fanux”, } payload, err := json.Marshal(&tok) if err != nil { return } var idToken string if idToken, err = signPayload(&Priv, signingAlg, payload); err != nil { return总结fist核心代码已经可用,不过为了更方便使用还需要进一步梳理,敬请期待。 鉴权仅是其其中一个功能,fist定位是一个极简的k8s管理平台。探讨可加QQ群:98488045公众号: ...

February 21, 2019 · 2 min · jiezi

容器打印日志到控制台阻塞的排障

原文今日生产环境发现有些容器停止响应了,但是容器没有死,docker exec -it <container-name> /bin/bash也能正常使用。在容器内部使用jstack <pid>发现log4j2的Console Appender一直处于运行状态:“AsyncAppender-asyncConsole” #21 daemon prio=5 os_prio=0 tid=0x00007fd968d07000 nid=0x1f runnable [0x00007fd91bffd000] java.lang.Thread.State: RUNNABLE at java.io.FileOutputStream.writeBytes(Native Method) at java.io.FileOutputStream.write(FileOutputStream.java:326) at java.io.BufferedOutputStream.write(BufferedOutputStream.java:122) - locked <0x00000000f002b408> (a java.io.BufferedOutputStream) at java.io.PrintStream.write(PrintStream.java:480) - locked <0x00000000f002b3e8> (a java.io.PrintStream) at org.apache.logging.log4j.core.util.CloseShieldOutputStream.write(CloseShieldOutputStream.java:53) at org.apache.logging.log4j.core.appender.OutputStreamManager.writeToDestination(OutputStreamManager.java:262) - locked <0x00000000f021d848> (a org.apache.logging.log4j.core.appender.OutputStreamManager) at org.apache.logging.log4j.core.appender.OutputStreamManager.flushBuffer(OutputStreamManager.java:294) - locked <0x00000000f021d848> (a org.apache.logging.log4j.core.appender.OutputStreamManager) at org.apache.logging.log4j.core.appender.OutputStreamManager.drain(OutputStreamManager.java:351) at org.apache.logging.log4j.core.layout.TextEncoderHelper.drainIfByteBufferFull(TextEncoderHelper.java:260) - locked <0x00000000f021d848> (a org.apache.logging.log4j.core.appender.OutputStreamManager) at org.apache.logging.log4j.core.layout.TextEncoderHelper.writeAndEncodeAsMuchAsPossible(TextEncoderHelper.java:199) at org.apache.logging.log4j.core.layout.TextEncoderHelper.encodeChunkedText(TextEncoderHelper.java:159) - locked <0x00000000f021d848> (a org.apache.logging.log4j.core.appender.OutputStreamManager) at org.apache.logging.log4j.core.layout.TextEncoderHelper.encodeText(TextEncoderHelper.java:58) at org.apache.logging.log4j.core.layout.StringBuilderEncoder.encode(StringBuilderEncoder.java:68) at org.apache.logging.log4j.core.layout.StringBuilderEncoder.encode(StringBuilderEncoder.java:32) at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:220) at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:58) at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:177) at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:170) at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:161) at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156) at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129) at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120) at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84) at org.apache.logging.log4j.core.appender.AsyncAppender$AsyncThread.callAppenders(AsyncAppender.java:459) at org.apache.logging.log4j.core.appender.AsyncAppender$AsyncThread.run(AsyncAppender.java:412)但用docker logs -f <container-name>没有发现有新的日志输出,且访问该应用肯定会输出日志的接口也是没有任何日志输出,因此怀疑log4j2阻塞住了。Google到有人在log4j提出了类似了问题LOG4J2-2239,官方给出的解释是问题出在log4j2之外。于是查一下logback是否也有类似问题,找到LOGBACK-1422,同样给出的解释是问题出在logback之外。两个问题的共通点都是用docker运行,于是把应用直接进程方式运行,没有出现问题。于是Google搜索docker logging to stdout hangs,找到SO的这个回答,以及这个issue,解决方案将Docker升级到18.06。查看生产环境的docker版本是18.03,升级到18.09后问题解决。 ...

February 21, 2019 · 1 min · jiezi

Swoft 系列教程:(1)使用 Docker 安装部署 Swoft

之前有写过一篇 Docker 安装部署 Swoft 的文章,但有些冗余混乱,故重写作为教程的开篇。要不读读看?Swoft项目:https://github.com/swoft-clou…Swoft文档:https://doc.swoft.org/Swoft镜像:https://hub.docker.com/r/swof…Swoft 简介首个基于 Swoole 原生协程的新时代 PHP 高性能协程全栈框架,内置协程网络服务器及常用的协程客户端,常驻内存,不依赖传统的 PHP-FPM,全异步非阻塞 IO 实现,以类似于同步客户端的写法实现异步客户端的使用,没有复杂的异步回调,没有繁琐的 yield, 有类似 Go 语言的协程、灵活的注解、强大的全局依赖注入容器、完善的服务治理、灵活强大的 AOP、标准的 PSR 规范实现等等,可以用于构建高性能的Web系统、API、中间件、基础服务等等。即异步非阻塞IO,EventLoop,事件驱动。cpu_num 个 worker 即可承载高并发请求,提供协程/异步IO客户端,数据库连接池,对象连接池,任务进程池。优雅的注解声明,IOC/DI容器,严格遵循PSR规范。Swoft 镜像的主要用途Swoft 官方提供了基于 Debine 的 Docker 镜像。镜像中已安装配置好运行 Swoft 的所需组件及依赖:PHP 7.0+ / Swoole / Composer / Pecl。虽然不使用镜像从头安装部署以上几项组件也不难,但镜像内置可以开箱即用,免去了这些略繁琐的工作,让我们尽可能快的投入到 Swoft 的开发中去。此外Swoft 镜像与开发的配合如果只是单纯的想快速体验 Swoft,使用 docker run -p 80:80 swoft/swoft 拉取创建容器访问即可。如何正确的在 Swoft 项目的开发中使用镜像呢?如果是要将镜像好好利用到开发工作中,则需要清楚一下几点。镜像内置完全安装的 Swoft 框架,但它只是用来快速演示的,并不是要你拿去修改,开发还是要对本地的 Swoft 项目开发。我们应该做的是将本地的 Swoft 框架 挂载到镜像的工作目录 /var/www/swoft 从而替换掉镜像自带的,这样启动 Swoft服务 就会启动映射到本地的 Swoft 项目了镜像的容器启动时默认会启动 Swoft 服务 作为前置进程,这就要求我们在挂载了本地 Swoft 项目时需要保证已完全安装了各项依赖(github 拉取下来的 Swoft 源码 并没有安装库依赖,需要使用 Composer install 一下)好像咬到尾巴了,为了开发需要挂载本地 Swoft 项目到镜像工作目录,因为容器启动时还会一并启动 Swoft 服务,所以要求挂载的本地 Swoft项目 必须使用 Composer 安装好依赖,嗯?这不还是得在本地装 PHP + Composer 嘛,镜像不是都提供了嘛,重复劳动了。修改 Swoft 镜像的 entrypoint 使得 Swoft 容器启动时不同时启动 Swoft 服务,这就不需要要求我们挂载的本地 Swoft 项目必须完全安装好依赖了。容器创建好后,登入容器 sh,使用镜像内置的 Composer 安装依赖启动 Swoft 服务这样就能充分利用镜像内置的环境和工具,快乐的开始 Swoft 的开发了工作了,下面给出具体的实例。Swoft 镜像的使用前面夸赞了那么多镜像的便利之处,下面如果不完全把镜像用到极致那就不太好了 O(∩_∩)O哈哈1、首先我们从 github 上拉取最新的 Swoft 源码到本地cd ~ && git clone git@github.com:swoft-cloud/swoft.git && cd swoft2、查看 swoft 镜像的 Dockerfile# 在文件尾设定了 entrypoint 命令为 启动 swoft服务ENTRYPOINT [“php”, “/var/www/swoft/bin/swoft”, “start”]entrypoint 就是我们后面需要改掉的参数3、直接使用镜像创建容器docker run -p 8081:80 \ #映射宿主机808-v $(pwd):/var/www/swoft #挂载本地 Swoft 项目到镜像工作目录-it -d \ #重要 开启 stdin tty 并以daemon模式运行–entrypoint="" #重要 覆盖掉镜像内设定的 entrypoint 参数–name my_swoft #容器命令–privileges=true #赋予权限swoft/swoft bash4、使用 docker-compose 更为简洁#编辑 docker-compose 编排文件vim docker-compose.yml#内容修改如下version: ‘3’services: swoft: image: swoft/swoft:latest container_name: my_swoft # 给容器自定义个名称便于管理 #build: ./ ports: - “8081:80” #端口映射 volumes: - ./:/var/www/swoft # 挂载本地swoft项目到镜像工作目录 stdin_open: true #打开标准输出 -i tty: true # 打开 tty 会话 -t privileged: true # 给与权限 比如创建文件夹之类的 #entrypoint: [“php”, “/var/www/swoft/bin/swoft”, “start”] # 入口启动命令 即启动 swoft 服务 entrypoint: [“bash”] 创建容器docker-compose up -d swoft ./5、登入容器,安装依赖,开启 Swoft 服务使用3或4创建的Swoft容器,便以 bash 作为启动的前置进程,而非启动 Swoft 服务,我们登入容器使用内置的 Composer 安装依赖后,启动Swoft服务即可。docker exec -it my_swoft bash# 安装框架依赖composer install# 启动/停止/重启 Swoft 服务php bin/swoft start|stop|restar6、开启热重载,关闭 daemon,让框架调试信息输出到 stderr 方便开发调试编辑本地的 Swoft 项目 .env 文件# ApplicationAPP_DEBUG=true# Server…AUTO_RELOAD=true…# Swoole Settings…DAEMONIZE=0…保存并重新启动 Swoft服务小提示:可以使用 PHPStorm IDE 配置 FTP/SFTP 文件改动自动上传的方式,开发起飞 ...

February 21, 2019 · 2 min · jiezi

Docker Swarm部署应用的总结

原文大纲本文只是一种实际部署方案的例子,涉及到的技术有(除Docker/Docker Swarm外):Docker overlay networkFluentdPrometheus stackvegasbrianc的Prometheus监控方案步骤大纲:部署Docker machine基本配置配置网络启动Fluentd日志服务部署Docker swarm集群配置网络添加Node部署Prometheus stack给Node打Label创建监控网络启动service部署应用识别stateless与stateful创建应用网络给Node打Label启动service1 部署Docker machine1.1 基本配置准备若干Linux服务器(本例使用Ubuntu 16.04),参照Docker CE 镜像源站提到的步骤安装Docker CE。参照Docker Daemon生产环境配置。1.2 配置bridge网络参照Docker Daemon生产环境配置中的mtu和子网章节。1.3 启动Fluentd日志服务参考使用Fluentd收集Docker容器日志。2 部署Docker swarm集群到一台机器上执行docker swarm init,这个机器将作为manager node。执行docker node ls会看到类似下面的结果:$ docker node lsID HOSTNAME STATUS AVAILABILITY MANAGER STATUSdxn1zf6l61qsb1josjja83ngz * manager1 Ready Active Leader如果你计划不会把工作负载跑在manager node上,那么使用docker drain:docker node update –availability drain <node-id>可参考Docker Swarm基本命令清单。2.1 配置网络MTU和子网参考Docker Overlay网络的MTU。特别注意观察docker_gwbridge和ingress的子网是否与已有网络冲突:$ docker network inspect -f ‘{{json .IPAM}}’ docker_gwbridge{“Driver”:“default”,“Options”:null,“Config”:[{“Subnet”:“172.18.0.0/16”,“Gateway”:“172.18.0.1”}]}$ docker network inspect -f ‘{{json .IPAM}}’ ingress{“Driver”:“default”,“Options”:null,“Config”:[{“Subnet”:“10.255.0.0/16”,“Gateway”:“10.255.0.1”}]}如果有冲突则参考Docker Overlay网络的MTU中的方法修改子网。2.2 添加Node参考Docker Swarm基本命令清单。3 部署Prometheus stack使用的是vegasbrianc的Prometheus监控方案。整个监控方案包含一下几个组件:PrometheusNode-exporter,运行在每个node上AlertmanagercAdvisor,运行在每个node上Grafana3.1 给Node打Label挑选一台Node作为运行监控服务的机器。给这个node打上label:$ docker node update –label-add for-monitor-stack=1 <node-id>3.2 创建监控网络$ docker network create -d overlay –attachable monitor-net参考参考Docker Overlay网络的MTU检查子网与MTU是否配置正确。3.3 启动serviceclone vegasbrianc的Prometheus监控方案 项目代码。使用我修改过的docker-stack.yml启动service:$ docker stack deploy \ –with-registry-auth \ –prune \ -c docker-stack.yml \ p8s-monitor-stack访问地址:Prometheus:http://<任意swarm node ip>:9000Node-exporter:http://<任意swarm node ip>:9010Alertmanager:http://<任意swarm node ip>:9020cAdvisor:http://<任意swarm node ip>:9030Grafana:http://<任意swarm node ip>:9040,用户名admin,密码foobar4 部署应用4.1 识别stateless与stateful如果你的应用由多个组件(service)组成,那么在部署它们之前你得识别出哪些是stateless service哪些是stateful service。针对每个service你自问以下三个问题:这个service崩溃之后,是不是只需要重启它就可以了,而不需要关心数据恢复?这个service是否可以在node之间任意迁移,且不需要分布式存储?这个service是否无需固定IP?如果上述回答都是Yes,那么这个service就是stateless的,只要有一个是No,则这个service是stateful的。对于stateless service,你可以:用docker stack deploy部署用docker service create部署对于stateful service,你可以:用docker run部署用docker-compose up部署如果没有固定IP的要求,那么你也可以用docker stack deploy/docker service create部署,前提是你得保证这个service只会固定在一台机器上运行。有时候你的应用既有stateless service又有stateful service,这时需要把他们挂载到同一个overlay网络里,这样它们之间就能够互相通信了。4.2 创建应用网络创建app-net(你也可以改成自己的名字)$ docker network create -d overlay –attachable app-net参考Docker Overlay网络的MTU检查子网与MTU是否配置正确。4.3 给Node打Label如果你对于Service部署在哪个Node上有要求,那么你得给Node打上Label:$ docker node update –label-add <your-label>=1 <node-id>然后在docker-compose.yaml里添加约束条件:version: “3.7"services: busybox: image: busybox deploy: placement: constraints: - node.labels.<your-label> == 14.4 启动service对于stateless service,编写docker-compose.yaml,里面写了同时挂载app-net和monitor-net,比如:version: “3.7"services: busybox: image: busybox networks: app-net: monitor-net: aliases: - busybox…networks: app-net: external: true monitor-net: external: true注意上面设置了busybox service在monitor-net中的别名,这是因为如果你用docker stack deploy部署,那么busybox的名字默认是<stack-name>_busybox,这样对于prometheus而言此名字不稳定,不便于配置详见Prometehus监控Docker Swarm Overlay网络中的容器。然后用docker stack deploy部署:$ docker stack deploy \ –with-registry-auth \ –prune \ -c docker-compose.yaml <stack-name>如果用docker service create则:$ docker service create \ –network app-net \ –network monitor-net \ –name <name> \ … 其他参数 <image>下面举例docker run启动stateful service的方法:$ docker run -d \ –name <name> \ –network app-net \ … 其他参数 \ <image> # 然后再挂载到monitor-net上$ docker network connect monitor-net <name> ...

February 21, 2019 · 2 min · jiezi

Docker入门(三)使用Docker Compose

Compose介绍 Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。Compose 是一个用户定义和运行多个容器的 Docker 应用程序。在 Compose 中你可以使用 YAML 文件来配置你的应用服务。然后,只需要一个简单的命令,就可以创建并启动你配置的所有服务。为什么使用Compose 在Docker镜像构成和定制介绍中,我们可以使用Dockerfile文件很方便定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。安装Compose Compose的安装十分容易,只需要以下命令即可:pip install docker-compose为了检测Compose是否安装成功,可以查看Compose的版本信息,如下:$ docker-compose -versiondocker-compose version 1.23.2, build 1110ad0Compose实战 接下去我们将通过一个具体的项目来展示Compose的使用。项目的结构如下: 对于项目的Python代码,我们不再具体讲述,有兴趣的同学可移步:https://github.com/percent4/P… 。 首先我们先打包一个poem_search镜像,用于前端运行,然后拉取镜像mongo,最后用Compose将两个镜像打包在一起,共同运行。 打包poem_search镜像涉及到两个文件:poem_search.build及build_poem_search.sh 。其中Dockerfile文件poem_search.build如下:FROM centos:7.5.1804# 维护者MAINTAINER jclian91@sina.com# 安装基础环境RUN yum clean all \ && yum makecache \ && yum update -y \ && yum groupinstall -y “Development tools” \ && yum install -y yum-utils \ && yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel \ && yum install -y sqlite-devel readline-devel tk-devel gdbm-devel \ && yum install -y db4-devel libpcap-devel xz-devel \ && yum install -y wget gcc gcc-c++ automake autoconf libtool make \ && yum install -y wget gcc gcc-c++ python-devel mysql-devel bzip2 \ && yum install -y https://centos7.iuscommunity.org/ius-release.rpm \ && yum install -y python36u \ && yum install -y python36u-pip \ && yum install -y python36u-devel \ && yum clean all# 安装Python3.6RUN cd /usr/bin \ && mv python python_old \ && ln -s /usr/bin/python3.6 /usr/bin/python \ && ln -s /usr/bin/pip3.6 /usr/bin/pip \ && pip install –upgrade pip#环境变量硬编码及时区ENV ENVIRONMENT productionRUN cd / && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime#安装Python的第三方模块RUN pip3 install pandas -i http://pypi.douban.com/simple/ –trusted-host pypi.douban.com \ && pip3 install pymongo -i http://pypi.douban.com/simple/ –trusted-host pypi.douban.com \ && pip3 install tornado -i http://pypi.douban.com/simple/ –trusted-host pypi.douban.com \ && pip3 install urllib3 -i http://pypi.douban.com/simple/ –trusted-host pypi.douban.com \ && pip3 install requests -i http://pypi.douban.com/simple/ –trusted-host pypi.douban.com \ && pip3 install bs4# 拷贝COPY ./src /root/poem_search/src# 工作目录WORKDIR /root/poem_search/src# 暴露端口EXPOSE 8000# 执行命令CMD [“python”,“server.py”]shell脚本build_poem_search.sh的代码如下:tag=$1# -f 指定文件 , -t 指定生成镜像名称 , 冒号后为版本号,最后的.表示docker_file的上下文环境docker build -f poem_search.build -t hub.docker.com/poem_search:test.${tag} .打包镜像,并将该镜像推送至自己的docker hub,命令如下:./build_poem_search.sh 1111镜像打包完后,将其推送至自己的docker hub,具体的命令可以参考文章:Docker入门(一)用hello world入门docker , 如下图:接着,拉取MongoDB镜像:docker pull mongo最后,用docker compose将两个镜像组合在一起,其中docker-compose.yml如下:version: “3.3”# 定义两个服务: poemSearch, mongoservices: poemSearch: depends_on: - mongo image: jclian91/poem_search:v1.0_2019.02.20.1745 container_name: poemSearch ports: - “8000:8000” restart: always deploy: replicas: 1 networks: - poemSearch mongo: image: mongo:latest container_name: mongo deploy: replicas: 1 networks: - poemSearch ports: - “27017:27017” volumes: - $PWD/db:/data/db command: [“mongod”]#Networknetworks: poemSearch:关于YAML文件的编写及说明,可以参考网址:http://blog.51cto.com/wutengf… 。 切换至YAML所在文件夹,输入命令:docker-compose up -d输出的结果如下:Creating mongo … doneCreating poemSearch … done这时,在浏览器中输入“http://localhost:8000/query”即可运行我们的程序,界面如下:在其中输入搜索关键词,比如“白云”,则会显示一条随机的结果,如下:点击“查询词高亮”,则查询词部分会高亮显示。体验Compose 如果需要体验该项目,则需要以下三个工具:gitdockerdocker-compose用git下载该项目,命令如下:git initgit clone -b v1.2 https://github.com/percent4/Poem-Search.git然后切换至docker-compose.yml所在路径,运行命令:docker-compose up -d即可运行该项目,然后在浏览器中输入“http://localhost:8000/query”即可。如需要停止该项目的运行,则运行命令:docker-compose down总结 本项目的github地址为https://github.com/percent4/P…, 分支为v1.2 。注意:本人现已开通微信公众号: Python爬虫与算法(微信号为:easy_web_scrape), 欢迎大家关注哦~~ ...

February 20, 2019 · 2 min · jiezi

Docker入门(二)在docker使用MongoDB

本文将介绍如何在docker中使用MongoDB。 如果你是一名MongoDB的初学者,那么你入门MongoDB的第一件事就是安装MongoDB,但是安装MongoDB又不是一件简单的事情,还需要自己配置一些服务。这时候,docker就能帮上大忙,它能够让你不需要本地安装MongoDB就能使用MongoDB。下面让我们来看看这是怎么实现的。 首先你的电脑上需要安装docker,然后在docker的镜像中查询MongoDB的镜像,命令如下:docker search mongodb输出的结果如下:NAME DESCRIPTION STARS OFFICIAL AUTOMATEDmongo MongoDB document databases provide high avai… 5606 [OK] mongo-express Web-based MongoDB admin interface, written w… 386 [OK] tutum/mongodb MongoDB Docker image – listens in port 27017… 224 [OK]bitnami/mongodb Bitnami MongoDB Docker Image 83 [OK]percona/percona-server-mongodb Percona Server for MongoDB docker images 23 frodenas/mongodb A Docker Image for MongoDB 17 [OK]centos/mongodb-32-centos7 MongoDB NoSQL database server 5 centos/mongodb-26-centos7 MongoDB NoSQL database server 5 eses/mongodb_exporter mongodb exporter for prometheus 4 [OK]centos/mongodb-36-centos7 MongoDB NoSQL database server 4 quadstingray/mongodb MongoDB with Memory and User Settings 3 [OK]bigtruedata/php-mongodb PHP image with MongoDB support 2 [OK]neowaylabs/mongodb-mms-agent This Docker image with MongoDB Monitoring Ag… 2 [OK]tozd/mongodb Base image for MongoDB server. 2 [OK]nuxeoapbcatalog/nuxeo-mongodb-apb MongoDB deployment for Nuxeo 1 [OK]openshift/mongodb-24-centos7 DEPRECATED: A Centos7 based MongoDB v2.4 ima… 1 centos/mongodb-34-centos7 MongoDB NoSQL database server 1 webhippie/mongodb Docker images for mongodb 1 [OK]perconalab/percona-server-mongodb-operator MOVED TO https://hub.docker.com/r/perconalab… 1 phenompeople/mongodb MongoDB is an open-source, document databas… 0 [OK]gebele/mongodb mongodb 0 [OK]mongodbsap/mongodbdocker 0 targetprocess/mongodb_exporter MongoDB exporter for prometheus 0 [OK]ansibleplaybookbundle/mongodb-apb An APB to deploy MongoDB. 0 [OK]xogroup/mongodb_backup_gdrive Docker image to create a MongoDB database ba… 0 [OK] 我们在这些镜像中选择使用第一个镜像,即mongo, 拉取mongo镜像,命令如下:docker pull mongo查看该镜像的信息:docker images mongo输出的结果如下:REPOSITORY TAG IMAGE ID CREATED SIZEmongo latest 0da05d84b1fe 2 weeks ago 394MB 现在,在docker中已经存在MongoDB的镜像mongo,接下来我们将使用这个镜像。输入命令:docker run -p 27000:27017 -v $PWD/db:/data/db -d mongo:latest其中, -p 参数为端口映射,格式为:主机(宿主)端口:容器端口,-v参数为数据卷挂载,将主机的目录$PWD/db 映射到容器的 /data/db,这是为了存储MongoDB操作时的数据,-d参数表示后台运行。 接着输入以下命令查询正在运行的docker容器:docker ps输出如下:CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES9664a5056e78 mongo:latest “docker-entrypoint.s…” 3 seconds ago Up 2 seconds 0.0.0.0:27017->27017/tcp nervous_heyrovsky接着运行以下命令,即可使用docker中的虚拟机,该虚拟机已帮你安装好MongoDB,可以直接使用:docker exec -it 9664a5056e78 bash在虚拟机中输入mongo ,界面如下: 以下将展示一些MongoDB的基础操作,具体的入门教程可以参考:https://blog.csdn.net/jclian9… 。 显示数据库:> show dbsadmin 0.000GBconfig 0.000GBlocal 0.000GB 创建新数据库及集合:> use schoolswitched to db school> db.createCollection(’teacher’){ “ok” : 1 }> show dbsadmin 0.000GBconfig 0.000GBlocal 0.000GBschool 0.000GB 往集合中插入新文档:> db.teacher.insert({id:1,name:‘zhangshan’,age:38})WriteResult({ “nInserted” : 1 })> db.teacher.insert({id:2,name:’lisi’,age:47})WriteResult({ “nInserted” : 1 })> db.teacher.insert({id:3,name:‘wangwu’,age:26})WriteResult({ “nInserted” : 1 }) 查询文档:> db.teacher.count()3> db.teacher.find(){ “_id” : ObjectId(“5c6d12d880b47c18564d99a7”), “id” : 1, “name” : “zhangshan”, “age” : 38 }{ “_id” : ObjectId(“5c6d12df80b47c18564d99a8”), “id” : 2, “name” : “lisi”, “age” : 47 }{ “_id” : ObjectId(“5c6d12e580b47c18564d99a9”), “id” : 3, “name” : “wangwu”, “age” : 26 }> db.teacher.find({name: ’lisi’}){ “_id” : ObjectId(“5c6d12df80b47c18564d99a8”), “id” : 2, “name” : “lisi”, “age” : 47 } 此时,在MongoDB的可视化软件Robo 3T中,依然能够看到我们插入的数据,只是连接的端口改为27000,如下图: 本次介绍完毕,感谢大家阅读~注意:本人现已开通微信公众号: Python爬虫与算法(微信号为:easy_web_scrape), 欢迎大家关注哦~~ ...

February 20, 2019 · 2 min · jiezi

基于 Kubernetes 实践弹性的 CI/CD 系统

大家好,我是来自阿里云容器服务团队的华相。首先简单解释一下何为 Kubernetes 来帮助大家理解。Kuberentes 是一个生产可用的容器编排系统。Kuberentes 一方面在集群中把所有 Node 资源做一个资源池,然后它调度的单元是 Pod,当然 Pod 里面可以有多个容器。 就像一个人左手抓着 ECS 资源或计算资源,右手抓容器,然后把它们两个匹配起来,这样它就可以作为一个容器的编排系统。而 Cloudnative 这个概念现在会经常被大家提起,很多人迷惑 Cloudnative 又与 Kuberentes 有什么关联?我们又该如何判断一个应用是 Cloudnative 呢?我认为有以下三个判断标准:第一,它能够给资源做池化;第二,应用可以快速接入池的网络。在 Kuberentes 里面有一层自己的独立网络,然后只需要知道我要去访问哪个服务名就可以,就是各种服务发现的一些功能,它可以通过 service mesh 去做一个快速地访问;第三是有故障转移功能,如果一个池子里面有一台主机,或者某一个节点 down 掉了,然后整个应用就不可用了,这肯定不算是 Cloudnative 的应用。比较这三点就可以发现 Kuberentes 做的非常好。首先我们看一个资源池的概念,Kuberentes 一个大的集群就是一个资源池,我们再也不用去关心说我这个应用要跑在哪台主机上了,我只要把我们部署的 yaml 文件往 Kuberentes 上发布就可以了,它会自动做这些调度,并且它可以快速地接入我们整个应用的网络,然后故障转移也是自动。接下来我就来分享如何基于 Kuberentes 实现一个弹性的 CI/CD 系统。CI/CD 的现状首先了解一下 CI/CD 的现状。CI/CD 这个概念实际上已经提出很多年了,但是随着技术地演进和新工具地不断推出,它在整个流程和实现方式上逐渐丰富。而我们通常最早接触 CI/CD 就是代码提交,随之触发一个事件,然后在某个 CI/CD 系统上做自动构建。下图可以反映目前 CI/CD 的现状:另外还有 Gitlab CI,它主要特点是与 Gitlab 代码管理工具结合地比较好。Jenkins 2.0 开始引入 pipeline as code 特性,pipeline as code 可以帮我们自动生成一个 jenkins file。在 Jenkins 1.0 时,如果我们想要配置一条流水线,需要先登录 Jenkins 建一个项目,然后在里面写一些 shell。这样虽然能达到同样的效果,但是它有一个最大的弊端,就是可复制性和迁移性不好。而且它与 Devops 有天然地割裂,比如一般是由运维人员来管理 Jenkins 系统,开发人员编写代码,但是这个代码怎么去构建,发布到哪里,开发人员完全不知道。这就造成了开发和运维地割裂。但 pipeline as code 方式出现了之后,jenkins file 与代码源码可以放在同样的仓库里面。首先它有一个非常大的好处是发布的流程也可以纳入版本管理,这样对一个错误就可追溯。这是一个非常大地改动,但是实际上我们在与客户沟通中发现,虽然很多人在 Jenkins 上都已经升到 2.0 系列了,但是它们的用法还是完全在 1.0 系列,很多用户都没有把 jenkins file 这种方式用起来。另外一个就是对容器地支持,大概 2016 年左右,那时对容器的支持是非常弱的,在容器里面运行 Jenkins,同时构建的产物也是 Docker 会非常麻烦。 但是, Drone 对容器的支持力度就非常好。首先它完全用 Docker 方式来运行,就是说你的构建环境也在一个容器里,你需要构建一个 Docker build 镜像,然后在推送出去的时候,它也在容器里面运行,然后它需要一个 privilege 权限。它这种方式有几个特别好的地方,首先它不会对宿主机产生任何残留,比如说你这个容器一销毁,构建中产生的一些中间文件完全都跟着销毁了,但是你如果用 Jenkins 的话,随着用的时间越来越久会沉淀出很多临时文件,它占的空间会越来越大。你需要定期做一些清理,而且清理过程中你又不能直接一键清空,所以这是很麻烦的过程。然后插件的管理 Jenkins 还有一个特别让人头疼的地方,就是它的插件升级。首先你在 Jenkins 登录进去,然后就做插件升级。如果说我想临时在一个新的环境里面,起一个 Jenkins 先测试一下或做一些调试可能每新建一个环境,都需要把这些插件升级一次。而且刚才我们说的在 Jenkins 里面做的所有配置,也都需要重新配置一遍,这是一个非常繁琐的一个过程。但是 Drone 这个工具它有一个特别好的地方,就是所有的插件都是 Docker 容器,比如说你在 pipeline 里用这个插件,你只要声明用这个插件就可以了,你不用去自己管理把插件下载到哪里,然后怎么安装,它这个一切都是全自动,只要说你网络能把插件容器镜像访问到就可以了,这非常便利。然后关于生态的构建,Jenkins 的最大的优势就是它的插件非常多,就是你想用的各种东西都有,而且它基础的底座非常好,你的插件可以实现的能力非常的强。比如说 pipeline 就是这种方式,它从 1.0 到 2.0 虽然有了,但是它完全是通过插件来实现的。但是现在 Jenkins 的发展又开始有点第二春的感觉。他开始对 Kuberentes 的支持力度明显的加大了很多,首先从 JenkinsX 开始,它融合了一些 Kuberentes 生态相关的一些工具,比如 Harbor、Helm 它可以非常便利地在 Kuberentes 集群上来做一些构建,并且把一些做服务的固化的一些编排文件放到 Helm 里面。另外,现在它有一个新的子项目叫 config as code,也就是说他把所有 Jenkin 里面做了一些配置,都可以输出成一个 code 的形式,就是对整个 Jenkins 的迁移,或者说复制都是一个很便利的改进。讲了那么多,实际上最后我们选择的东西还是 Jenkins,因为最重要的是生态的构建,他们已经很好了。今天我们要讲的在做一个弹性在 CI/CD 这个 Jenkins 上已经有这个插件了,但是在 Drone 的社区里面,有人提这个事,但是现在还没有看到。CI/CD 工具的选择接下来,我们看一下 CI/CD 这些工具的选择和他们的发展。首先最老牌的肯定是 Jenkins。实际上在容器技术兴起之前,CI/CD 简直约等于 Jenkins。但是在出现容器技术之后,很多新生 CI/CD 的工具也应运而生,比如说图中 Drone 工具,它是一个完全基于容器来实现的 CI/CD 工具。它与容器地结合地非常好,并且它的构建过程是完全在容器中实现的。第三个是 Gitlab CI,它主要特点是与 Gitlab 代码管理工具结合地比较好。Jenkins 2.0 时开始引入 pipeline as code 特性,什么叫 pipeline as code?pipeline as code 可以帮我们自动生成一个 jenkins file。在 Jenkins 1.0 时,如果我们想要配置一条流水线,需要先登录 Jenkins,然后建一个项目,然后在里面写一些 shell。这样虽然能达到同样的效果,但是它有一个最大的弊端,就是可复制性和迁移性不好。而且它与 Devops 有天然地割裂,比如一般是运维人员来管理 Jenkins 这个系统。开发人员编写代码,但是这个代码怎么去构建,发布到哪里,他是完全不知道的,是运维人员在Jenkins 里面配的。这个就造成了开发和运维地割裂。但 pipeline as code 这种方式出现了之后,我们可以把 jenkins file 跟代码源码放在同样的仓库里面。首先它有一个非常大的好处就是我们发布的流程也可以纳入版本管理,这样对一个错误就可追溯。这是一个非常大地改动,但是实际上我们在与客户沟通中发现,虽然很多人在 Jenkins 上都已经升到 2.0 系列了,但是它们的用法还是完全在 1.0 系列,很多用户都没有把 jenkins file 这种方式用起来。另外一个就是对容器地支持,大概 2016 年左右,那时对容器的支持是非常弱的,在容器里面运行 Jenkins,同时构建的产物也是 Doker 会非常麻烦。 但是, Drone 对容器的支持力度就非常好。首先它完全用 Doker 方式来运行,就是说你的构建环境也在一个容器里,你需要构建一个 Doker build 镜像,然后在推送出去的时候,它也在容器里面运行,然后它需要一个 privilege 权限。它这种方式有几个特别好的地方,首先它不会对宿主机产生任何残留,比如说你这个容器一销毁,构建中产生的一些中间文件完全都跟着销毁了,但是你如果用 Jenkins 的话,随着用的时间越来越久会沉淀出很多临时文件,它占的空间会越来越大。你需要定期做一些清理,而且清理过程中你又不能直接一键清空,所以这是很麻烦的过程。然后插件的管理 Jenkins 还有一个特别让人头疼的地方,就是它的插件升级。首先你在 Jenkins 登录进去,然后就做插件升级。如果说我想临时在一个新的环境里面,起一个 Jenkins 先测试一下或做一些调试可能每新建一个环境,都需要把这些插件升级一次。而且刚才我们说的在 Jenkins 里面做的所有配置,也都需要重新配置一遍,这是一个非常繁琐的一个过程。但是 Drone 这个工具它有一个特别好的地方,就是所有的插件都是 Doker 容器,比如说你在 pipeline 里用这个插件,你只要声明用这个插件就可以了,你不用去自己管理把插件下载到哪里,然后怎么安装,它这个一切都是全自动,只要说你网络能把插件容器镜像访问到就可以了,这非常便利。然后关于生态的构建,Jenkins 的最大的优势就是它的插件非常多,就是你想用的各种东西都有,而且它基础的底座非常好,你的插件可以实现的能力非常的强。比如说 pipeline 就是这种方式,它从 1.0 到 2.0 虽然有了,但是它完全是通过插件来实现的。但是现在 Jenkins 的发展又开始有点第二春的感觉。他开始对 Kuberentes 的支持力度明显的加大了很多,首先从 JenkinsX 开始,它融合了一些 Kuberentes 生态相关的一些工具,比如 Harbor、Helm 它可以非常便利地在 Kuberentes 集群上来做一些构建,并且把一些做服务的固化的一些编排文件放到 Helm 里面。另外,现在它有一个新的子项目叫 config as code,也就是说他把所有 Jenkin 里面做了一些配置,都可以输出成一个 code 的形式,就是对整个 Jenkins 的迁移,或者说复制都是一个很便利的改进。讲了那么多,实际上最后我们选择的东西还是 Jenkins,因为最重要的是生态的构建,他们已经很好了。今天我们要讲的在做一个弹性在 CI/CD 这个 Jenkins 上已经有这个插件了,但是在 Drone 的社区里面,有人提这个事,但是现在还没有看到。CI/CD的系统业务场景然后我们看一下 CI/CD 它的系统的业务场景,它有一个比较典型的场景与特点,首先它面向开发人员,这是比较少见的,因为开发人员一般都比较挑剔一点。所以你要是这个系统做的不够稳健了,或者说响应时间比较长一点的话,会被经常吐槽。然后就是有时效性要求,因为我们代码写完之后,往上提交,我们都不希望在这个代码的构建中一直排队,我们希望马上就开始进行构建,并且资源足够丰富。另外一个就是它的资源占用的波峰波谷是非常明显的。就因为开发人员不可能时时刻刻都在提交代码,有的人可能一天提交几次,有的人会提交很多次。因为我之前看过有一个分享,有一个人画了一条反映自家公司构建任务的曲线。他们公司大概是每天下午三、四点的时候代码提交量最高,其他时间都比较平缓。这说明他们公司三、四点的时候,程序员提交代码开始划水了。然后随着 CI/CD 资源地需求越来越高,构建集群是一个必须要做的一件事情。就是提高负载能力,缩短任务的排队时间。当然真正的集群有一个让人觉得很不太好的地方,就是它的 Master 实际上只有一个,当然这个也可以通过插件来做改进。容器可以给我们 CI/CD 系统来注入新的能力,就是环境隔离的能力。我们可以通过 Kubernetes 来为 CI/CD 系统注入更多的能力,然后矛盾点就出现了。开发人员总是希望 CI/CD 系统能够快速地响应代码提交的一个事件,但是每个公司资源都不可能是无限的。因为就像上面提到的,如果每天下午三、四点的时候是一个代码提交的高峰,这个时候可能需要 30 或 40 台机器才能满足构建的任务。但是我不可能每天就开着 30 或 40 台机器在这里,就为了每天下午三、四点,可能会构建一、两个小时。Kubernetes 可以为 jenkins 注入新的能力,让 CI/CD 系统实现弹性的能力。我们期望的目标是什么呢?有构建任务的时候,可以自动为我们资源增加新的机器也好,或增加新的运计算能力也好,反正就是当我需要的时候,可以帮我自动地做一个资源扩张,但是同时也在我不需要的时候,可以自动把这些资源释放掉。我们期望的目标就是这样,Kuberentes 就可以为 Jenkins 来做这样的能力。Kuberentes 作为一个容器编排的系统,它所能提供的能力,它可以快速地弹出一些新的实例,并且把它们自动调度到空闲的机器上,做一个资源池,在资源池里面做一个调度,并且他执行完任务之后,它可以做一个回收。而且如果把这 Jenkins Master 也部署在 Kuberentes 之上,它可以对 Master 做一个故障转移,就是说如果我们系统可以容忍的话,Master 就算挂了,我可以快速把它调到另外一台机器上,这个响应时间不会很长。Kuberentes-plugin这里面也是用一个插件来实现,这个插件名字比较直接叫 Kuberentes-plugin,它这个插件所能提供的能力就是说,他直接管理一个 Kuberentes 集群,它在 Jenkins 里面安装之后,它可以监听 Jenkins 的构建任务。有构建任务,在等待资源的时候,它就可以向 Kuberenetes 去申请一个新的资源,申请一个新的 Pod 去做自动地构建完之后,它就会自动的清理。先简单介绍一下它的能力,因为这个插件安装完之后,它对 pipeline 的语法也有一个改造,一会我们来看一下实例。但是就算到了这一步,还是不行的。首先,Kuberentes 的集群规划还是一个问题。比说我有个集群有 30 个节点,真正的 master 部署在这上面,然后装了那些插件,做了一个管理之后,我们可以发现来了一个新的任务,它就起一个新的 Pod,把这个构建任务给执行制定完。执行完之后,这 Pod 自动销毁不占集群的资源。 平时我们可以在这集群上做一些别的任务,但这个终究还是有一点不好,就是我们这个集群到底规划多大,并且这个集群我们平时不做构建任务的时候,可以在上面做一些别的任务。但是如果正在做任务,突然来了一些构建任务,它可能会出现资源的冲突问题。Kubernetes Autoscaler总的来说,还是有一些不完美的地方,那么我们可以利用 Kuberentes 一些比较没那么常见的特性,来解决我们刚才说的这个问题。这两个东西一个是叫 Autoscaler,一个叫 Virtual node。我们先看一下 Autoscaler,Autoscaler 是一个 Kubernetes 官方的一个组件。在 Kuberentes 的 group 下面支持三种能力:Cluster Autoscaler,可以对集群节点做自动伸缩;Vertical Pod Autoscaler,对集群的 Pod 竖直方向的资源伸缩。因为 Kuberentes 本身里面就带着 HPA 可以做水平方向的 Pod 伸缩、节点数量的伸缩;这个特性还不是生产可用的特性;Addone Resizer,是 Kuberentes 上那些 addone 比如说 Ingress Controler、 DNS 可以根据 Node 的数量来对资源的分配做调整。Cluster autoscaler我要讲的是 Cluster autoscaler,是对集群 node 节点数量做一个扩缩容。 首先我们看一下,这个是在阿里云我们容器服务上所实现的 Autoscaler 的一个方式。我们看一下这个图,这个是 HPA 和 Autoscler 做结合使用的一个场景。HPA 监听监控的事件时发现资源使用率上升到一定程度了之后,HPA 会自动通知 workload,来弹出一个新的 Pod,弹出一个新的 Pod,可能这时候集群资源就已经不够了,所以这个 Pod 可能就会 pending 在这里。它就会触发 Autoscaler 的事件,Autoscaler 就会根据我们之前配置好的 ESS 这个模板来去弹出来一台新的 Node,然后自动地把 Node 加入到我们集群里面。 它是利用了 ESS 定制的模板功能,并且它可以支持多种的 Node 实例的类型,可以支持普通实例,支持 gpu,还有抢占式实例。Virtual node然后第二种是 Virtual node,Virtual node 实现方式是基于微软开源的 Virtual Kubelet 这个项目。它做了一个虚拟的 Kubelet,然后向 Kubernetes 集群里面去注册上面。但如果不太好理解的话,可以想象一下 MySQL proxy ,然后他把自己伪装成一个MySQL server,然后后端可能管理着非常多的 MySQL server,并且它可以帮你自动的做一些 SQL 查询的路由或者拼接。Virtual kubelet 做的也是类似的工作,就是说它本身向着 Kubernetes 注册说我是一个节点,但实际上它后端管理的可能是整个公有云上的非常多的资源,他可能对接公有云的一些 ECI 或者说对接的这些 VPC,这是一个它的大体的示意图。在阿里云上他们对接的是阿里云的 ECI 做一个弹性的容器实例,它响应时间就非常快,因为它不需要把 Node 去加入到集群里面,它是大概能够到一分钟一百个 Pod 左右这种性能。而且我们可以在 Pod 上声明这种资源的使用情况,这是一个非常快的响应速度时间。然后刚才说我们利用这两种方式,就可以对我们 CI/CD 弹性的系统做出新的改造,我们不用非常早规划好我们集群的规模,我们可以让集群规模在需要的时候自动的做一些伸缩的动作。但是你做了这些动作之后,我们做了这些把真正的放在容器里面的这些动作之后,引入了一些新的门槛:docker-outside-of-docker 和 docker in docker。我们在 Docker 中运行 Jenkins 时,通常有两种方式,一个是把宿主机的 docker.sock 挂载到容器里面,让 Jenkins 通过这个文件来和本机的 docker daemon 做一个通信,然后它在这里面做 docker build 构建镜像,或者把这些镜像 push 到远程的仓库里面,所以它中间产生的所有镜像都会堆积在本机上,这是一个问题。在一些 serverless 的那些场景上使用,它就会有一些限制,因为 serverless 本身就不允许把 socket 文件给挂在进去。另外一个就是 docker in docker 这种方式,它的优点就在于在容器里面它启动一个新的 Docker daemon,它所有的中间产物、构建产物随着容器都一起销毁,但是它有问题,它就是需要 privilege 的权限。很多时候我们是尽量不要用它。另外一个就是说你做 docker build 的时候能在宿主机上做的时候,它如果有已经有镜像了,它会直接就使用这个镜像,但是你用 docker in docker 这种方式来使用的,它每次都会重新拉进项,拉镜像也是需要一定时间,这个取决于我们各个使用场景来判断。新的构建工具——Kaniko这时又引入了一个谷歌开源的新工具——Kaniko。它做的东西是 docker in docker 的方式。它有一个非常大的好处就是不依赖 Docker,而且所以它不需要 privilege 权限就可以在容器里面用用户态的模式,来完全构建 docker image。用户态执行 Dockerfile 的命令,它把这个镜像完全构建出来。这算是一个比较期望的弹性的 CI/CD 系统。然后这个时候就是说从真正的节点到底层的计算资源全部是弹性扩缩的,而且满足交付的需求,可以非常精细化地管理我们的资源。Demo 演示然后我们可以看一下 Demo 演示:https://github.com/hymian/webdemo 这里是我准备好的一个例子,重点在这个 Jenkinsfile 文件,里面定义了agent 的 pod template,包含两个容器,一个用来做 golang 的 build,一个用来做 image 的 build。然后我们现在构建它。开始构建了,刚开始的,因为是现在我们在这环境里面只有一个,只有一个 master,所以他就是没有不会有构建节点。大家可以看到,它现在新启动了一个 Pod,这个 Pod 是作为节点加进来的,但是因为我在这个 Pod 模板里面定义了一个 label,所以它没有这个节点,所以它 Pod 状态是 pending 的。所以我们在构建日志里面显示的这个是 agent 节点是离线的。 但是我们在这个集群里面定义了一个弹性伸缩的一个东西,当没有节点的时候,它会自动做一个新节点分配加入,可以看到有一个节点正在加入,这个我就可以稍等一下。就是说这段时间可能会有个一分钟两分钟的时间。这个是异常,是因为这个节点正在向集群加入,所以它显示是异常,这是我们从命令行看一下,好,已经是四个节点了,加了一个节点,这时候我们看 Pod,这时候在 agent 正在创建,这时候大家可能有一个小的细节,大家可以看一下,就是 0/3 是显示 Pod,它有三个容器,但是我刚才在这个里面定义的,它实际上是 Pod 里面只有两个容器,这就是我们刚才 PPT 上写的一个地方。 JNLP 那个容器,是 plugin 自动注入的一个容器,它通过这个容器实时的向 master 汇报构建的一个中间的状态,我把它的日志给发送出去。这个是 agent 的节点在初始化的一个过程一个事情这时候 slave节点已经在运行了。我这边已经输出完了,构建完成。我的分享内容就这些,谢谢大家。本文作者:jessie筱姜阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 20, 2019 · 3 min · jiezi

个人博客-创建项目

Django是一种基于Python的开源Web框架,采用了MVC的框架模式。工作区 D:Userswork1、创建虚拟环境mkvirtualenv stormsha2、安装django==1.11.12(本项目计划使用此版本开发)pip install django==1.11.123、创建django项目django-admin startproject blog4、启动项目cd blog 进入下项目文件默认(127.0.0.1:8000)启动python manage.py runserver指定8080端口启动python manage.py runserver 127.0.0.1:80805、访问 http://127.0.0.1:8080/ 6、创建第一个app7、完整的项目文件8、在项目文件夹下创建一个apps文件把storm文件放入apps,之后所有app将放于次文件夹便于管理9、扯淡最近考研结果陆续已经公布,身边很多同学都参见了二战,都在讨论怎么调剂的问题。在这里想说一下本渣渣去年目标北邮差10+分落榜失败的感受。首先我考研的目的是明确的,就是为了提升自的圈层,想在人生第一阶段再赌一把,没考上北邮直接毕业奔上海找工作。如果在考研的你们如果真正搞清自己考研的目的,就不会出现二战、怎么调剂的困惑。首先在考研前就应该想好自己是否适合考研,这个别人给不了建议,自己对自己的评估要诚实。其次要确认自己考研的目的,不要为了上学而考研,这是在逃避现实。虽然本渣渣考研失败了,也要说如果考上研还不能认清自己,读研也是浪费时间。考上研就应该对自己进行第二次评估,是否适合搞科研,要对自己诚实。如果要搞科研,最多给自己一年的时间在研一做出成绩,如果没有成绩,就不要再为了逃避现实而自欺欺人的走下去。有时间可以去看一下崔庆才的个人博客,观摩一下一个对自己有清晰规划的研究生都在干些什么。

February 20, 2019 · 1 min · jiezi

如何用Dockerfile构建镜像

本文旨在用通俗的语言讲述枯燥的知识前面讲到镜像的构建时,讲述的是用commit的方式构建镜像,而Dockerfile是另一种构建镜像的方式。Dockerfile构建镜像是以基础镜像为基础的,Dockerfile是一个文本文件,内容是用户编写的一些docker指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。Dockerfile的基本指令有十三个,分别是:FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOINT、VOLUME、USER、WORKDIR、ONBUILD从前面的内容可以看出,要构建一个容器,需要做很多的工作,设置很多的配置,如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。因此学会用Dockerfile来构建镜像,是非常有必要的。学习Dockerfile之前,我们先来学习一些Dockerfile常用的指令。文章提纲:Dockerfile常用指令Dockerfile的编写用Dockerfile构建镜像彩蛋1 Dockerfile常用指令类型命令基础镜像信息FROM维护者信息MAINTAINER镜像操作指令RUN、COPY、ADD、EXPOSE、WORKDIR、ONBUILD、USER、VOLUME等容器启动时执行指令CMD、ENTRYPOINT1.1、FROM :指定基础镜像所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个nginx镜像的容器,再进行修改一样,基础镜像是必须指定的。而FROM就是指定基础镜 像,因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。如:指定ubuntu的14版本作为基础镜像FROM ubuntu:141.2、 RUN:执行命令RUN指令在新镜像内部执行的命令,如:执行某些动作、安装系统软件、配置系统信息之类,格式如下两种:1)shell格式:RUN< command > ,就像直接在命令行中输入的命令一样。如在nginx里的默认主页中写”hello“:RUN echo ‘hello ’ >/etc/nginx/html/index.html2)exec格式:RUN [“可执行文件”, “参数1”, “参数2”]如在新镜像中用yum方式安装nginx:RUN [“yum”,“install”,“nginx”]注:多行命令不要写多个RUN,原因是Dockerfile中每一个指令都会建立一层.多少个RUN就构建了多少层镜像,会造成镜像的臃肿、多层,不仅仅增加了构件部署的时间,还容易出错,RUN书写时的换行符是\1.3、COPY:复制文件COPY命令用于将宿主机器上的的文件复制到镜像内,如果目的位置不存在,Docker会自动创建。但宿主机器用要复制的目录必须是和Dockerfile文件统计目录下。格式:COPY [–chown=<user>:<group>] <源路径>… <目标路径>COPY [–chown=<user>:<group>] ["<源路径1>",… “<目标路径>"]如把宿主机中的package.json文件复制到容器中/usr/src/app/目录下:COPY package.json /usr/src/app/1.4、CMD:容器启动命令CMD命令用于容器启动时需要执行的命令,CMD在Dockerfile中只能出现一次,如果出现多个,那么只有最后一个会有效。其作用是在启动容器的时候提供一个默认的命令项。如果用户执行docker run的时候提供了命令项,就会覆盖掉这个命令,没提供就会使用构建时的命令。格式:shell 格式:CMD <命令>exec 格式:CMD [“可执行文件”, “参数1”, “参数2”…]如容器启动时进入bash:CMD /bin/bash也可以用exec写法:CMD ["/bin/bash”]1.5 MAINTAINER:指定作者用来指定dockerfile的作者名称和邮箱,主要作用是为了标识软件的所有者是谁。语法:MAINTAINER <name> <email>如:MAINTAINER autor_jiabuli 6766633@qq.com1.6、EXPOSE:暴露端口EXPOSE命名适用于设置容器对外映射的容器端口号,如tomcat容器内使用的端口8081,则用EXPOSE命令可以告诉外界该容器的8081端口对外,在构建镜像时用docker run -p可以设置暴露的端口对宿主机器端口的映射。语法:EXPOSE <端口1> [<端口2>…]如:EXPOSE 8081EXPOSE 8081 其实等价于 docker run -p 8081 当需要把8081端口映射到宿主机中的某个端口(如8888)以便外界访问时,则可以用docker run -p 8888:80811.7、WORKDIR:配置工作目录WORKDIR命令是为RUN、CMD、ENTRYPOINT指令配置工作目录。其效果类似于Linux命名中的cd命令,用于目录的切换,但是和cd不一样的是:如果切换到的目录不存在,WORKDIR会为此创建目录。语法:WORKDIR path如需要在nginx目录下创建一个hello.txt的文件:##进入/usr/local/nginx目录下WORKDIR /usr/local/nginx##进入/usr/local/nginx中的html目录下WORKDIR html## 在html目录下创建了一个hello.txt文件RUN echo ‘hello’ > hello.txt1.8、ENTRYPOINT:容器启动执行命名ENTRYPOINT的作用和用法和CMD一模一样,但是ENTRYPOINT有和CMD有2处不一样:CMD的命令会被docker run的命令覆盖而ENTRYPOINT不会CMD和ENTRYPOINT都存在时,CMD的指令变成了ENTRYPOINT的参数,并且此CMD提供的参数会被 docker run 后面的命令覆盖1.9、VOLUMEVOLUME用来创建一个可以从本地主机或其他容器挂载的挂载点。例如我们知道tomcat的webapps目录是放web应用程序代码的地方,此时我们要把webapps目录挂载为匿名卷,这样任何写入webapps中的心都不会被记录到容器的存储层,让容器存储层无状态化。格式:VOLUME [“path”]如创建tomcat的webapps目录的一个挂载点VOLUME /usr/local/tomcat/webapps这样,在运行容器时,也可以用过docker run -v来把匿名挂载点挂载都宿主机器上的某个目录,如docker run -d -v /home/tomcat_webapps:/usr/local/tomcat/webapps1.10、 USERUSER命令用于指定当前望下执行的用户,需要注意的是这个用户必须是已经存在,否则无法指定。它的用法和WORKDIR有点像,切换用户。格式:USER daemon1.11、ADD作用和使用方法和COPY一模一样,在此不重复讲述。1.12、ONBUILDONBUILD用于配置当前所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。意思就是:这个镜像创建后,如果其它镜像以这个镜像为基础,会先执行这个镜像的ONBUILD命令格式:ONBUILD [INSTRUCTION]1.13、ENV:设置环境变量ENV命名用于设置容器的环境变量,这些变量以”key=value”的形式存在,在容器内被脚本或者程序调用,容器运行的时候这个变量也会保留。格式:1) 设置一个: ENV <key> <value>2) 设置多个:ENV <key1>=<value1> <key2>=<value2>…如设置一个环境变量JAVA_HOME,接下来的命名就可以使用这个变量:ENV JAVA_HOME /opt/jdkENV PATH $PATH:$JAVA_HOME/bin在使用ENV设置环境变量时,有几点需要注意:1)具有传递性,也就是当前镜像被用作其它镜像的基础镜像时,新镜像会拥有当前这个基础镜像所有的环境变量2)ENV定义的环境变量,可以在dockerfile被后面的所有指令(CMD除外)中使用,但不能被docker run 的命令参数引用如:ENV tomcat_home_name tomcat_7RUN mkdir $tomcat_home_name3)除了ENV之外,docker run -e 也可以设置环境变量传入容器内。如:docker run -d tomcat -e “tomcat_home_name=tomcat_7"这样我们进入容器内部用ENV可以看到tomcat_home_name这个环境变量。2 Dockerfile的编写我们先看一个例子#在centos上安装nginxFROM centos#标明著作人的名称和邮箱MAINTAINER jiabuli 649917837@qq.com#测试一下网络环境RUN ping -c 1 www.baidu.com#安装nginx必要的一些软件RUN yum -y install gcc make pcre-devel zlib-devel tar zlib#把nginx安装包复制到/usr/src/目录下ADD nginx-1.15.8.tar.gz /usr/src/#切换到/usr/src/nginx-1.15.8编译并且安装nginxRUN cd /usr/src/nginx-1.15.8 \ && mkdir /usr/local/nginx \ && ./configure –prefix=/usr/local/nginx && make && make install \ && ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/ \ && nginx#删除安装nginx安装目录RUN rm -rf /usr/src/nginx-nginx-1.15.8#对外暴露80端口EXPOSE 80#启动nginxCMD [“nginx”, “-g”, “daemon off;"]上面的注释已经讲的非常清楚,其实不难发现,上面的例子就是类似于在centos系统上安装一个nginx的的一个过程,因此编写Dockerfile构建镜像就和在Linux上安装软件的流程几乎是一模一样的。所以我们在编写Dockerfile来构建镜像时,可以先思考在Linux上安装该软件的流程,再用Dockerfile提供的指令转化到Dockerfile中即可。3.用Dockerfile构建镜像用Dockerfile的核心在于编写Dockerfile,但是编写完之后我们需要知道怎么使用Dockerfile来构建镜像,下面以构建nginx镜像为例来简要说明构建流程3.1 上传安装包首先我们需要把要构建的软件安装包上传到服务器中,我们可以在服务器目录上创建一个专门的文件夹,如:/var/nginx_build,然后把从nginx官网下载的nginx-1.15.8.tar.gz安装包上传到这个目录里。3.2 编写Dockerfile如何编写nginx的Dockerfile上面已经详细介绍,现在我们只需把编写好的Dockerfile上传到/var/nginx_build目录下,当然你也可以在服务器上直接编写Dockerfile,但是要记得一定保证Dockerfile文件和安装包在一个目录下。3.3 运行构建命令构建docker build 命令用于使用 Dockerfile 创建镜像。格式: docker build [OPTIONS] PATH | URL | -OPTIONS有很多指令,下面列举几个常用的:–build-arg=[] :设置镜像创建时的变量;-f :指定要使用的Dockerfile路径;–force-rm :设置镜像过程中删除中间容器;–rm :设置镜像成功后删除中间容器;–tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;因此我们构建nginx可以用以下命令:docker build -t nginx:v1.0 .当Dockerfile和当前执行命令的目录不在同一个时,我们也可以指定Dockerfile,如docker build -f /var/nginx_build/Dockerfile .执行命名之后,会看到控制台逐层输出构建内容,直到输出两个Successfully即为构建成功。4. 彩蛋写了很多篇docker的文章,为了方便开发者们学习和查阅docker的基础知识,我做了一份完整的docker基础整理,发布在gitchat上面,有兴趣的读者可以加入一起学习,我这里还有6个免费名额,如有需要,可以加微信:sisi-ceo 索要。觉得本文对你有帮助?请分享给更多人关注「编程无界」,提升装逼技能 ...

February 19, 2019 · 1 min · jiezi

Golang项目部署

文章来源:https://goframe.org/deploymen…一、独立部署使用GF开发的应用程序可以独立地部署到服务器上,设置为后台守护进程运行即可。这种模式常用在简单的API服务项目中。服务器我们推荐使用*nix服务器系列(包括:Linux, MacOS, BSD),以下使用Ubuntu系统为例,介绍如何部署使用GF框架开发的项目。1. nohup我们可以使用简单的nohup命令来运行应用程序,使其作为后台守护进程运行,即使远程连接的SSH断开也不会影响程序的执行。在流行的Linux发行版中往往都默认安装好了nohup命令工具。命令如下:nohup ./gf-app &2. tmuxtmux是一款Linux下的终端复用工具,可以开启不同的终端窗口来将应用程序作为后台守护进程执行,即使远程连接的SSH断开也不会影响程序的执行。在ubuntu系统下直接使用sudo apt-get install tmux安装即可。使用以下步骤将应用程序后台运行。tmux new -s gf-app;在新终端窗口中执行./gf-app即可;使用crt + B & D快捷键可以退出当前终端窗口;使用tmux attach -t gf-app可进入到之前的终端窗口;3. supervisorsupervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启。官方网站:http://supervisord.org/常见配置如下:[program:gf-app]user=rootcommand=/var/www/mainstdout_logfile=/var/log/gf-app-stdout.logstderr_logfile=/var/log/gf-app-stderr.logautostart=trueautorestart=true使用步骤如下:使用sudo service supervisor start启动supervisor服务;创建应用配置文件/etc/supervisor/conf.d/gf-app.conf, 内容如上;使用sudo supervisorctl进入supervisor管理终端;使用reload重新读取配置文件并重启当前supoervisor管理的所有进程;也可以使用update重新加载配置(默认不重启),随后使用start gf-app启动指定的应用程序;随后可以使用status指令查看当前supervisor管理的进程状态;二、代理部署代理部署即前置一层第三方的WebServer服务器处理所有的请求,将部分请求(往往是动态处理请求)有选择性地转交给后端的Golang应用程序执行,后端部署的Golang应用程序可以配置有多个。这种模式常用在复杂的WebServer配置中,常见的场景例如:需要静态文件分离、需要配置多个域名及证书、需要自建负载均衡层,等等。虽然Golang实现的WebServer也能够处理静态文件,但是相比较于专业性的WebServer如nginx/apache来说比较简单,性能也较弱。因此不推荐使用Golang WebServer作为前端服务直接处理静态文件请求。Nginx我们推荐使用Nginx作为反向代理的前端接入层,有两种配置方式实现动静态请求的拆分。静态文件后缀这种方式通过文件名后缀区分,将指定的静态文件转交给nginx处理,其他的请求转交给golang应用。配置示例如下:server { listen 80; server_name goframe.org; access_log /var/log/gf-app-access.log; error_log /var/log/gf-app-error.log; location ~ ..(gif|jpg|jpeg|png|js|css|eot|ttf|woff|svg|otf)$ { access_log off; expires 1d; root /var/www/gf-app/public; try_files $uri @backend; } location / { try_files $uri @backend; } location @backend { proxy_pass http://127.0.0.1:8199; proxy_redirect 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; }}其中,8199为本地的golang应用WebServer监听端口。例如,在该配置下,我们可以通过http://goframe.org/my.png访问到指定的静态文件。静态文件目录这种方式通过文件目录区分,将指定目录的访问请求转交给nginx处理,其他的请求转交给golang应用。配置示例如下:server { listen 80; server_name goframe.org; access_log /var/log/gf-app-access.log; error_log /var/log/gf-app-error.log; location ^~ /public { access_log off; expires 1d; root /var/www/gf-app; try_files $uri @backend; } location / { try_files $uri @backend; } location @backend { proxy_pass http://127.0.0.1:8199; proxy_redirect 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; }}其中,8199为本地的golang应用WebServer监听端口。例如,在该配置下,我们可以通过http://goframe.org/piblic/my.png访问静态文件。三、容器部署容器部署即使用docker化部署golang应用程序,这是在云服务时代最流行的部署方式,也是最推荐的部署方式。1. 编译程序跨平台交叉编译是golang的特点之一,可以非常方便地编译出我们需要的目标服务器平台的版本,而且是静态编译,非常方便地解决了运行依赖问题。使用以下方式静态编译Linux平台amd64架构的可执行文件:CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o gf-app main.go这样便编译出来一个gf-app的可执行文件。2. 编译镜像我们需要将该可执行文件gf-app编译生成docker镜像,以便于分发及部署。Golang的运行环境推荐使用alpine基础系统镜像,编译出的容器镜像约为20MB左右。一个参考的Dockerfile文件如下( 可以参考gf-home项目的Dcokerfile: https://github.com/gogf/gf-home ):FROM loads/alpine:3.8LABEL maintainer=“john@johng.cn”################################################################################ INSTALLATION###############################################################################ADD ./gf-app /bin/mainRUN chmod +x /bin/main################################################################################ START###############################################################################CMD main其中,我们的基础镜像使用了loads/alpine:3.8这个镜像,基础镜像的Dockerfile地址:https://github.com/johngcn/do… ,仓库地址:https://hub.docker.com/u/loads随后使用 docker build gf-app . 命令编译生成名为gf-app的docker镜像。3. 运行镜像使用以下命令运行镜像:docker run gf-app4. 镜像分发容器的分发可以使用docker官方的平台:https://hub.docker.com/ ,国内也可以考虑使用阿里云:https://www.aliyun.com/produc… 。5. 容器编排在企业级生产环境中,docker容器往往需要结合kubernetes或者docker swarm容器编排工具一起使用。容器编排涉及到的内容比较多,感兴趣的同学可以参考以下资料:https://kubernetes.io/https://docs.docker.com/swarm/ ...

February 19, 2019 · 1 min · jiezi

抓取崔庆才个人博客网站前端源码

1、准备工具:仿站小工具+V9.0工具获取方式一: 关注微信公众号微信公众号『stormsha』,后台回复『仿站工具』获取工具工具获取方式二: 仿站小工具官网https://smalltool.github.io/崔庆才博客https://cuiqingcai.com/从网站源码来看此博客应该是使用的wordpress框架,原站用什么写的不重要,重要的是快速使用python实现全栈开发2、开扒下载工具后,解压直接打开exe程序,按如下步骤抓取即可3、扒取结果4、整理新建一个templates文件把扒下的除static文件之外,其它文件中的html文件都放入templates文件中,把static文件和templates放于同级目录,结果5、发现什么了吗?django项目文件: static、templates把index.html打开看一下效果,完美运行6、总结:学习python只有两个方向是最好找工作的,python web、python爬虫,之前我也在《让每行代码产生价值》一文中提到,如果你是因为数据科学的兴起才有了学习python的想法,那你错了,你应该去学习数据科学。对于数据科学而言python只是一个工具。如果你没有一个好的学历背景,学习python数据科学仍然是没有什么竞争力的。学习python web和python爬虫你可以从事数据科学相关工作,比如数据可视化、数据分析、数据抓取等。学习python web只有具备全栈开发能力才有竞争力。只会python你就是一块砖,搬到哪里都一样,会全栈开发你就是一个设计师,其实也不需要全栈,会点python web 加 爬虫就可以搞很多有意思的事情了。如果想快速入门python全栈那基本是不现实。按我的思路来,快速学习找到一份工作问题不大。扒取网站前端的源码应该是一个学习python web开发人员必备的技能,具备了此能力你可以快速搭建起一个网站。学习python就应该从实际需求出发去学习。接下来我会把崔大佬的个人博客网站使用django实现。最终部署上线,一个人的精力有限,计划每天在工作之余花费四个小时左右一点点把这个项目完善,后期我会把项目放在github上,每天进步一点点。今天看到的一段鸡汤文:如何评估一个人的职业化程度?①、时间感强、时间颗粒度小,最大限度地提升自己的时间价值。②、能够分清事实与观点,只用实力佐证事实,而不用情绪陈述观点。关注公众号结识更多python伙伴

February 18, 2019 · 1 min · jiezi

Why Kubernetes ,我所理解的docker与k8s

去年换工作后,开始真正在生产环境中接触容器与Kubernetes。边恶补相关知识的同时,也想把学到的内容和自己的理解整理出来。学习的途径包括k8s官方文档、书籍、极客时间专栏及网上各种博文。所涉及一些摘抄或描述,大多用自己的理解来组织语言,也就不一一注明出处了。Kubernetes(简称K8S)与容器技术,可以说是近几年最火热的技术之一。提起K8S,大家都知道是google开源的容器编排工具。今天想先谈谈,我理解的容器、K8S是什么,以及为什么它们能火起来。Why Docker既然说K8S是一个容器编排的工具,那么要搞清楚K8S是什么,首先得搞清楚,容器是什么,以及为什么要用容器技术。形象的来说,一个linux容器,实际就是一个进程,还是个被系统欺骗的进程。为什么这么说呢?一个运行在容器里的进程,主要有以下特点:它被宿主机操作系统隔离了,它看不到宿主机上的其他进程,以为自己就是pid为1的进程它被宿主机操作系统限制了硬件资源,它能使用的cpu、内存等硬件资源只是宿主机的一部分在被宿主机操作系统限制了存储空间,让这个进程认为宿主机的某个目录就是系统根目录这几个欺骗进程的技术,就是是容器技术的三板斧,用于资源隔离的namespace,用于资源限制的cgroup,以及用于伪装进程根目录的rootfs。这三种技术,都是是早已存在于linux中的,只是docker公司比较创新的把这三种技术整合在一起,并且,提出了容器镜像及镜像仓库等概念,把这个进程及相关的依赖环境都一起打包成可分发及重复使用的镜像文件,方便容器能在不同机器之间移植。这样,在任何地方,只要能运行docker,就能把容器镜像跑成一个包含这应用进程及相关依赖环境的容器实例。这里用利用K8S官方文档的图,简单说下生产中的场景,看看容器技术能解决什么问题。左图是传统的物理机部署应用方式的方式,可以看到除了一个应用的运行,除了程序本身,离不开应用配置、库、等等依赖环境。通常情况下,一个应用的上线,要经历开发环境、pre环境、beta环境、灰度测试、生产环境等等不同的基础环境。开发的同学在开发环境跑通了代码,到其他环境运行时,由于各环境依赖、配置、安全需求不同,可能会出现各种问题。运维的同学忙着在不同的环境中统一依赖。不同应用各种语言,各种应用的特殊依赖需求,也造成了CI/CD逻辑复杂,难以统一。右图是用了容器技术之后的场景。一个容器镜像的实质就是程序进程加所有运行时环境及配置、依赖的集合。只要各个环境的底层能兼容docker,就能实现所有环境部署的一致性。开发同学不用关心生产环境和开发环境的差异可能会导致应用运行问题,运维同学部署一个应用,只要确保容器镜像能正常运行就好。CI/CD的自动化也相对容易实现很多。从而极大增加开发效率、应用迭代效率及节省了运维工作量。说到这里,不得不说下容器与传统意义的虚拟机间的对比。其实二者都可以理解为虚拟化,它们最本质的区别是,容器是操作系统层级的虚拟化,而虚拟机是硬件层级的虚拟化。参看下图。由于虚拟机多了一层Guest OS,需要宿主机额外的性能开销进行硬件虚拟化,同时因为是完整的操作系统,虚拟机镜像一般动辄大几G,很难在各环境间快速传播,hypervisor层相对Docker Engine也比较重。再看docker容器,实质就是宿主机上跑的一个进程,只不过通过docker与其他进程隔离开来。简单的说,敏捷和高效,是容器相比虚拟机最大的优势,当然也有劣势,由于容器只是操作系统级别的隔离,不同容器间隔离的不够彻底。Why Kubernetes简单说完了docker的理解,那么Kubernetes又是什么,它解决了哪些问题呢,为啥现在提容器技术必提k8s呢。这里再谈谈我理解的Why K8S。回到实际的场景来,假如你已经开始用docker镜像来打包应用,可以方便的进行分发和部署,不用去考虑不同环境的差异。但是不是还感觉还少了点什么?因为正常的实际业务系统,不是应用能部署,能运行起来就完事了,还要考虑应用容器的访问、水平伸缩、监控、安全、备份、容灾等等因素。而对于一个完整的业务系统来说,也不是单个应用就能搞定的,还要考虑的是各应用间的关系,及应用运行的形态。比如一个web业务,可能需要web服务器、缓存系统、数据库等多个应用。容器化部署后,它们可能部署在不同宿主机的不同容器中,相互间的访问要怎么实现,这就涉及到容器间访问关系的处理。再比如,有个优化缓存的应用也跑在容器里,只需要定期运行容器实例执行优化任务,执行完毕就终止容器,这就需要处理不同容器应用的运行形态问题。类似上述这些对容器应用及容器间关系进行管理,就是所谓的容器编排及调度。而Kubernetes,就是目前的容器编排的平台的事实标准了。那么,具体来说,K8S到底能做哪些事呢。在官方文档中,对K8S功能的描述如下Kubernetes 满足了生产中运行应用程序的许多常见的需求,例如:Pod提供了一种复合的应用容器模型,挂载外部存储,Secret管理,应用健康检查,副本应用实例,横向自动扩缩容,服务发现,负载均衡,滚动更新,资源监测,日志采集和存储,支持自检和调试,认证和鉴权.从这些功能介绍可以看出,K8S已很类似一个云平台了,可以完全能满足生产级别的容器应用管理需求。下面是一张最简单的K8S系统示意图:K8S集群由一个主节点(master节点)及多个工作节点(node节点)构成,开发者提交应用容器镜像,并将镜像运行的数量、方法等通过描述文件提交给K8S master节点,K8S master节点或根据集群整体情况,将应用按照需求部署在工作节点中。对于开发者来说,利用K8S可以方便的部署程序,不用关心基础设施,而对于运维人员来说,工作重心从维护具体应用,转变为维护K8S集群。而且,不管是开发者还是运维人员,都不用关心应用具体部署在哪个节点,K8S会自动判断搞定一切。相比于传统的应用部署方式,有没有觉得K8S很棒棒?在容器编排这个概念出现的时候,Kubernetes并不是唯一的一个容器编排工具,主流的工具还有Docker公司原生的swarm和Apache基金会的mesos,为什么K8S能笑到最后,成为容器编排的事实标准呢?我理解K8S对比它们有两个最大不同点:(这里不对swarm和mesos做详细介绍了,实际也确实没怎么玩过)K8S对容器又做了一层抽象,也就是POD。不同于与其他两个工具,K8S管理的原子对象,其实并不是容器,而是POD。按照官方文档的定义,一个POD,是由一个或多个共享存储及网络的容器,以及描述怎么运行这些容器的集合,所以,POD实际是一个抽象的概念。k8s对容器的所有操作,比如动态伸缩、监控等,实际上都是对pod的管理。那这层抽象的好处是什么呢?上面有提到过,容器实质就是被特殊处理的进程。想像一个web业务,web应用进程输出的日志需要被大数据agent进程处理。这个业务如果想容器化,通常有两个做法。一是分别起来两个容器,挂载宿主机同一个目录来存放日志。另一种是起一个操作系统级别的容器或supervisord容器作为enterpoint,来管理web服务和agent进程。前一种方式,这个两个容器就被框在这台宿主机上了,要实现业务实例横向扩缩容,要考虑两个容器的运行情况和存储挂载,逻辑比较复杂。后一种方式,你要为每个容器再额外开一个supervisord进程,更重要的是,由于entrypoint是supervisord进程,web应用和大数据agent对docker来说,都是不可见的。即使nginx出错频繁重启,只要supervisord还活着,Docker就认为这个容器是正常的。我们再来看看,使用了pod这个概念以后,有什么变化。一个pod里面同时起了web服务进程和大数据agent两个容器实例,首先,pod里的容器实例是共享存储和网络namespace的,也就是说,这两进程的存储数据是直接共享的,不需要额外的挂载动作。其次,这个pod是作为一个整体被k8s管理着的,k8s会监控pod里每个容器的状态,并根据策略在有问题时进行自动干预。从这个意义上说,pod才更类似传统的虚拟机。声明式API第二点也是比较重要的方面,是K8S的声明式api(貌似swarm的新版也支持了,同样没玩过就不细说了)。什么是声明式API呢,可以参考上面系统图中的描述文件。比如要我需要集群中跑10个web服务容器,传统的命令式API是一步步调用命令构建出容器。而使用声明式api,只要告诉K8S我要10个web容器,K8S就会自动将web集群实例数维持在10个,并且,在某个pod出问题退出时,K8S会自动重新拉起新pod,使集群始终保持10个pod实例在跑。这就使得管理集群变得很简单,只要通过配置文件描述出希望的集群状态,而不用去关注中间的实现过程。最后,总结一下:Why Dokcer: 用容器技术跑应用,相比原来的物理机及虚拟机更高效、轻量、省资源,同时大大方便了不同环境下的应用部署及分发。Why Kubernetes:生产集群光跑容器还不够,还要对容器应用作为一个业务系统集群进行编排及管理,而K8S的一些优势使得它成为目前容器集群编排管理工具的事实标准。最后的最后再多提一点,实际上,容器技术不止Docker公司一家在做,Kubernetes也不是只能管理Docker容器。只是,无论从市场份额、应用性还是开发社区的热度来说,它们都是目前容器技术最主流的解决方案,就生产环境来说,目前基本没有必要去考虑其他的容器技术了。

February 18, 2019 · 1 min · jiezi

Docker学习笔记

本文是我学习Docker的笔记,因为最近工作的原因,要用到docker工具,一开始抱着有需要什么就用什么的心态使用docker,但因为项目是强依赖docker,越来越多的问题因为docker成为了工作的瓶颈,所以进行打算系统化学习。学习过程中主要参考yeasy的<<Docker–从入门到实践>>,以及因为是第一次写技术笔记会模仿segmentfault另一位网友的<<从零开始学习Docker>>的写法。

February 15, 2019 · 1 min · jiezi

Docker镜像仓库清理的探索之路

用友云开发者中心是基于Docker容器进行微服务架构应用的落地与管理。相信各位同学在使用的过程中,会发现随着Docker镜像的增多,占用磁盘空间也约来越多。这时我们需要清理私有镜像仓库中不需要的镜像。但在实际操作时,才会发现这本以为很简单的任务中却暗藏玄机,遇到了不少的麻烦。在这里我们分享一下清理镜像仓库时遇到的坑点。想要直接寻求解决方案的同学可以直接看第二部分。一、那些年,我们在清理镜像仓库时走过的坑坑点1:官方提供的接口并不能真正的删除镜像这着实是最大的坑点。很多同学查资料发现,官方已经提供了删除镜像仓库的API,所以可能相当然的以为直接使用就好,殊不知掉入了官方埋下的最大的坑点,也是本文要着手解决的核心问题:官方提供的删除镜像仓库中镜像的接口,仅仅是把manifest删除了,真正的镜像文件还存在!官方并没有提供删除镜像层的接口!这也就是说,当我们调用删除镜像的接口之后,仅仅是查看镜像的列表时看不到原镜像了,然而原有镜像仍然在磁盘中,占用着宝贵的文件存储空间。 坑点2:直接调用官方的删除镜像API,会返回405的错误码直接调用删除镜像的接口,可能会遇到以下错误提示:405,意味着方法不被允许。实际上,官方可能是处于安全性的考虑,在默认的情况下禁止了直接删除镜像的功能。若要开启删除镜像功能,需要修改镜像仓库的配置文件。具体操作为修改/etc/docker/registry/config.yml文件,在storage下添加delete的许可之后,重启镜像仓库服务。 坑点3:使用官方提供的garbage-collect工具,会有无用的文件残留官方为registry提供了garbage-collect(gc)工具清理镜像的物理存储,将没有引用的layer删除。gc的清理过程分为两部分:1)mark:扫描所有的manifest,列出引用的layer;2)sweep:扫描所有的layer,不在mark里的layer将被清理删除。gc可以在dry-run的模式下运行(添加参数-d),只输出gc信息,不进行实际操作。我们可以通过这种方式来确认哪些镜像会被清除。使用gc工具清理镜像的一个问题就是文件清理得不够干净,无法清理已经没有tag的镜像目录,并且还残存少部分文件,从十KB到几十KB不等。久而久之,垃圾文件和目录的数量会越来越多。坑点4:garbage-collect不是事务操作,清理镜像时可能会产生误操作gc不是事务操作,当gc过程中刚好有push操作时,则可能会误删数据。一个可行的解决办法是手动更改镜像仓库的配置,暂时禁止镜像的push操作。在镜像仓库的配置文件中可以配置read-only模式。当启用read-only之后,再push镜像时会得到405的错误。gc完成后取消read-only模式,再push镜像即可。坑点5:使用garbage-collect工具后,必须重启镜像仓库才能正常使用如果不重启镜像仓库,则再次push该镜像时可能会得到layer already exists错误:其可能的原因是镜像被删除后,仓库的缓存中还存有已经删除的镜像信息,所以再次push会报层存在的错误。二、两种清理镜像仓库的方案方案一:使用官方API + GC使用官方提供的方法可以较为简便的清理镜像仓库。整个清理过程可能需要几百毫秒到几秒的时间。此操作有一定的危险性,因此清理镜像不宜过于频繁。官方在git上也有类似描述。点击查看:https://github.com/docker/doc…具体操作过程如下:1、准备工作在配置中许可删除操作。修改镜像仓库的配置文件,一般在如下路径:/etc/docker/registry/config.yml在storage下添加delete的许可之后,重启镜像仓库。用docker方式启动的镜像仓库也可以添加环境变量:REGISTRY_STORAGE_DELETE_ENABLED=true2、获取待删镜像的digest获取镜像digest的API为:GET /v2/<name>/manifests/<reference>其中,name是仓库名,reference是标签,此时需要注意,调用时需要加上header内容:Accept: application/vnd.docker.distribution.manifest.v2+json其中Docker-Content-Digest的值就是镜像的digest3、调用官方的HTTP API V2删除镜像删除镜像的API为:DELETE /v2/<name>/manifests/<reference>其中,name是仓库名称,reference是包含“sha256:”的digest。4、调用GC清理镜像文件使用gc工具的方式为:bin/registry garbage-collect /etc/docker/registry/config.ymlgc清理需要时间,如果在gc过程中刚好有push操作,可能会产生未知的问题,建议设置read-only模式之后再进行gc,然后再改回来。5、重启docker registry注意,如果不重启会导致push相同镜像时产生layer already exists错误。方案二:使用第三方脚本在清理镜像仓库这件事上,业内已经有很多人进行过各种各样的尝试。本文挑选一种比较好的方式推荐使用。1、宿主机安装delete-docker-registry-image可参考此命令的安装和使用方式。参考链接:https://github.com/burnettk/d…2、执行delete-docker-registry-image命令可以删除某个仓库(sb)或者某个具体的镜像(如alpine:3.2)如果删除某镜像后该仓库为空,可以用删除仓库的方式删除此空仓。该工具也提供了dry-run的方式,只输出待删除的信息不执行删除操作。在命令后加上——dry-run即可。3、重启docker registry跟gc方式一样,删除镜像之后要重启docker registry,不然还是会出现相同镜像push不成功的问题。以上就是本文推荐的两种清理镜像仓库的两种方案。第一种方案更多的使用了官方提供的工具,使用时相对更加安全,且无需额外安装其他内容。第二种方案使用了第三方工具或脚本,使用时更加灵活且简便,且清理的更加彻底。具体操作时可根据自己的需求选择方案。

February 15, 2019 · 1 min · jiezi

还为重复安装开发环境而烦吗? 这或许是更好的解决方案 —— docker

工欲善其事必先利其器开始进行web开发之前,都需要搭建好基本的开发环境.个人用到的有nginx、redis、mysql、node.js.搭建环境不同的方法使用apt(ubuntu)、brew(mac os)一个个安装脚本: LNMP一键安装包源码编译上面的解决方案都有一个共同的缺点一旦系统重装,需要重新安装、配置(有多台电脑时,开发环境版本容易不一致)没有版本控制系统,软件配置维护麻烦更好的解决方案 —— docker基于docker(18.03以上)搭建nginx、 redis 、mysql 服务。项目结构.├── .env # 默认为dev的环境变量├── .gitignore├── README.md├── container # 不同容器的配置文件│ ├── mysql│ │ └── docker-compose.yml│ ├── nginx│ │ ├── conf│ │ ├── docker-compose.prod.yml│ │ └── docker-compose.yml│ └── redis│ └── docker-compose.yml└── prod # prod的环境变量 └── .envdocker-compose 在运行时会使用当前目录下的.env文件,并且不支持指定env文件,所以需要多个不同环境时,只能在对应文件夹下建立.env文件项目内容通过.env文件配置整个项目所需要的环境变量# file .env# 项目名称COMPOSE_PROJECT_NAME=site# compose文件COMPOSE_FILE=container/nginx/docker-compose.yml:container/mysql/docker-compose.yml:container/redis/docker-compose.yml# mysql configMYSQL_ROOT_PASSWORD=123456MYSQL_DATABASE=demo# redis configREDIS_PASSWORD=123456# 自定义环境变量 本地服务器 IPSITE_IP=host.docker.internal # host.docker.internal需要18.03以上版本 以nginx的 docker-compose.yml 文件为例: ${SITE_IP}将被替换成host.docker.internal, $${SITE_IP}将不会被替换version: “3"services: nginx: image: nginx volumes: - ./conf/dev.template:/etc/nginx/conf.d/dev.template ports: - “80:80” environment: - SITE_IP=${SITE_IP} command: /bin/bash -c “envsubst ‘$${SITE_IP}’< /etc/nginx/conf.d/dev.template > /etc/nginx/conf.d/dev.conf && exec nginx -g ‘daemon off;’” networks: - default - network_sitenetworks: network_site: driver: bridge其他镜像的配置可以从dockerhub查看redis、mysql启动全部// dev模式docker-compose up// prod模式,使用 prod下的.env文件cd ./prod && docker-compose up单独启动docker-compose up nginxdocker-compose up mysqldocker-compose up redis停止# 停止某个服务docker-compose stop nginx # 停止全部docker-compose stop具体配置请从github仓库查看通过使用docker,我们只需要一个repository存放配置, 便可以在多台电脑上迅速安装环境. ...

February 15, 2019 · 1 min · jiezi

runc容器逃逸漏洞最强后续:应对之策汇总与热点疑问解答

美国时间2019年2月11日晚,runc通过oss-security邮件列表披露了runc容器逃逸漏洞CVE-2019-5736的详情。runc是Docker、CRI-O、Containerd、Kubernetes等底层的容器运行时,此次安全漏洞无可避免地会影响大多数Docker与Kubernetes用户,也因此为整个业界高度关注。漏洞披露后,Docker在第一时间发布了两个版本18.06.2和18.09.2,这两个版本都可以修复runc漏洞。Rancher Labs极速响应,Rancher Kubernetes管理平台和RancherOS操作系统均在不到一天时间内紧急更新,是业界第一个紧急发布新版本支持Docker补丁版本的平台,并持严谨态度在oss-security邮件列表披露漏洞后的五小时内连夜邮件通知所有Rancher用户此次漏洞的详情及应对之策。更值得一提的是,尽管Docker发布了修复版本,但因为不是所有用户都能轻易将生产环境中的Docker版本升至最新,Rancher帮忙将修复程序反向移植到所有版本的Docker并提供给用户。且目前Docker官方提供的修复版本并不支持3.x内核(只兼容4.x内核),而runc的开发者特意向Rancher提交了支持3.x内核的PR,目前PR已合并,Rancher提供的方案现已可以支持3.x内核。runc安全漏洞事件背景runc是一个根据OCI(Open Container Initiative)标准创建并运行容器的CLI tool,目前Docker引擎内部也是基于runc构建的。2019年2月11日,研究人员通过oss-security邮件列表披露了runc容器逃逸漏洞的详情,根据OpenWall的规定EXP会在7天后也就是2019年2月18日公开。此漏洞允许以root身份运行的容器以特权用户身份在主机上执行任意代码。这意味着容器可能会破坏Docker主机(覆盖Runc CLI),而所需要的只是能够使用root来运行容器。攻击者可以使用受感染的Docker镜像或对未受感染的正在运行的容器运行exec命令。Rancher在12号当天已通过公众号文章详细分析了漏洞详情和用户的应对之策。相信目前大部分用户已经对漏洞已经有了初步的了解,甚至在Github上已经有人提交了EXP代码。Rancher在第一时间完成了补丁修复,并向企业用户推送的修复方案。同时在我们也收到了大量来自社区用户在后台的提问,为了疏解种种谜团,这篇后续文章,我们将选取大家重点关注的一些热点疑问进行进一步的解答。热点问题非特权容器也能发起攻击吗?答案是肯定的,Rancher安全团队在第一时间做了一些测试,即使运行容器时不使用privileged参数,一样可以发起攻击。因为这个漏洞核心要素在于,容器内的用户是否对runc有访问权限, 容器内默认是root用户,只是这个root是受限制的root,但是它是具有对runc的访问权限,所以一定可以发起攻击。主机上不用root用户启动容器可以避免攻击吗?答案是无法避免,如上一个问题分析,它和容器内的用户有关,至于在主机上以什么用户启动无关。Rancher安全团队在Ubuntu系统上做了测试,即使使用ubuntu用户启动容器, 依然可以完成对runc的替换。更新官方Docker的注意事项Docker也在第一时间发布了两个版本18.06.2和18.09.2,这两个版本都可以修复runc漏洞,但是你需要注意的是他们都只兼容4.x内核,如果你的系统依然使用的3.x内核, 请谨慎使用,因为它基本不会起作用,甚至可能导致额外的问题。Ubuntu 14.04 customers using a 3.13 kernel will need to upgrade to a supported Ubuntu 4.x kernel参考两个版本的RN:https://docs.docker.com/engin…https://docs.docker.com/engin...Kubernetes用户怎么办?使用K8s的用户都很清楚,K8s并不能兼容太高的Docker版本,所以更新官方Docker版本是很难的一件事,为此K8s官方特意发表了一篇Blog:https://kubernetes.io/blog/20… 。 主要思想就是,不要在容器中使用root,它推荐的方案是使用PodSecurityPolicy。当然很多用户修改PodSecurityPolicy后可能会引发各种问题,所以它也推荐用户更新Docker。 同时它也提到,不能更新Docker的用户,可以使用Rancher提供的方案,Rancher为每个版本都移植了补丁:If you are unable to upgrade Docker, the Rancher team has provided backports of the fix for many older versions at github.com/rancher/runc-cve.如何使用Rancher提供的补丁?如上一个问题提到的,用户可以直接访问https://github.com/rancher/ru… 来获取方案,值得一提的是Rancher为3.x和4.x内核用户都提供了补丁版本。To install, find the runc for you docker version, for example Docker 17.06.2 for amd64 will be runc-v17.06.2-amd64.For Linux 3.x kernels use the binaries that end with no-memfd_create. Then replace the docker-runc on your host with the patched one.如何正确使用EXP?首先不建议大家广泛传播EXP,因为它每暴露一次,就为整体环境增加了一丝风险,我们可以研究学习但是不要恶意传播。 我们在后台看到有些人问到,他们使用了某些EXP代码,攻击没有成功,想知道是不是自己的系统是安全的,不用考虑升级。 Rancher安全团队也查看了一些外部公开的EXP,有些EXP是不完整的,它可能只能在某些环境上起作用。 比如利用libseccomp的EXP,就无法在静态编译的runc上起作用,我们使用了一些公开的EXP就无法在RancherOS上完成攻击。 虽然不同版本的Docker都使用runc,但是不同的操作系统使用runc的方式不同,有的使用static runc,有的使用dynamic runc。 所以不能以某些公开的EXP的执行结果为标准,来判断自己系统是否存在漏洞。守护用户的K8S之路Rancher Kubernetes平台拥有着超过一亿次下载量,我们深知安全问题对于用户而言的重要性,更遑论那些通过Rancher平台在生产环境中运行Docker及Kubernetes的数千万用户。2018年年底Kubernetes被爆出的首个严重安全漏洞CVE-2018-1002105,就是由Rancher Labs联合创始人及首席架构师Darren Shepherd发现的。2019年1月Kubernetes被爆出仪表盘和外部IP代理安全漏洞时,Rancher Labs也是第一时间向用户响应,确保了所有Rancher 2.x和1.6.x的用户都完全不被漏洞影响。负责、可靠、快速响应、以用户为中心,是Rancher始终不变的初心;在每一次业界出现问题时,严谨踏实为用户提供相应的应对之策,也是Rancher一如既往的行事之道。未来,Rancher也将一如既往支持与守护在用户的K8S之路左右,确保大家安全、稳妥、无虞地继续前进❤️ ...

February 15, 2019 · 1 min · jiezi

ubuntu 16.04下docker的安装

安装:sudo apt-get updatesudo apt-get install docker.io运行:sudo service docker start使用国内镜像(网易):在/etc/docker/daemon.json(没有此文件请新建)中添加:{ “registry-mirrors”: [“http://hub-mirror.c.163.com”]}此时运行docker命令会报错:docker: Got permission denied while trying to connect to the Docker daemon socket sock: connect: permission denied.See ‘docker run –help’.这是因为默认情况下,docker使用socket与Docker引擎通讯。而只有root用户和docker组的用户才可以访问Docker引擎的socket。可以直接加sudo或切换root用户,但是最好是将当前用户加入 docker 用户组:sudo groupadd dockersudo usermod -aG docker $USER重启服务:sudo service docker restart切换至docker组:newgrp - docker再运行:charlesz@charlesz-OptiPlex-3046:~$ docker run ubuntu:15.10 /bin/echo “Hello everybody"Hello everybody完成。

February 14, 2019 · 1 min · jiezi

Dockerfile参考

Docker可以从Dockerfile中读取指令来自动构建镜像。Dockerfile是一个文本文件,它包含了用户可以在命令调用以制作镜像的命令。用户可以使用docker build连续执行一些命令行指令来开启一个自动构建。此文档描述了在Dockerfile中可以使用的命令。当你读完这个文档时,请参阅Dockfile最佳实践获取进阶指南。使用docker build命令从Dockerfile和上下文构建镜像。构建上下文是特定路径或URL的文件集合。该路径是你本地文件系统的一个目录。URL是一个Git仓库地址。上下文会被递归处理。所以,路径包含的任意字母路合URL包含的仓库及其子模块也会被处理。一下实例展示了一个使用当前目录作为上下文的build命令:$ docker build .Sending build context to Docker daemon 6.51 MB…构建由Docker daemon执行, 而非cli。构建进程的第一件事是(递归的)发送上下文给守护进程(daemon)。在大多数情况下,最好以一个空目录下作为上下文发送给守护进程并且保持Dockerfile在该目录下。只为构建Dockerfile增加必须的文件。CMDCMD指令有三种用法:CMD [“executable”,“param1”,“param2”] (exec形式, 这是首选形式)CMD [“param1”,“param2”] (作为ENTRYPOINT默认参数)CMD command param1 param2 (shell 形式)一个Dockerfile里只能有一个CMD指令。如果你有多个CMD指令,只有 最后一个 生效。CMD的主要目的是为运行容器提供默认值。 默认值可以包含一个可执行文件,也忽略可执行文件,在此情况下必须同时指定ENTRYPOINT指令。注: 如果CMD用于为ENTRYPOINT指令提供默认参数,CMD和ENTRYPOINT都应该使用json数组格式。注: exec形式传递json数组,意味着你必须使用双引号(")而不是单引号(’)引用字符注: 与shell形式不同,exec形式不会像,那样调用命令行shell。这意味着没有通常的shell处理。例如,CMD [ “echo”, “$HOME” ]将不会对$HOME做变量替换。如果你想使用shell处理可使用shell形式或直接执行一个shell,例如:[“sh”, “-c”, “echo $HOME”]。当使用exec形式并且直接执行一个shell,在这种情况下shell形式,执行环境变量扩展的是shell,而不是docker。当使用shell或exec格式时,CMD指令设置镜像运行时执行的命令。如果你使用CMD的shell形式,<command>将以/bin/sh -c的形式运行:FROM ubuntuCMD echo “This is a test.” | wc -如果你想不使用shell运行你的<command>就必须以JSON数组的形式表示并且使用可执行文件的完整路径。数组形式是CMD的首选格式。任何独立的参数都必须表达为数组的一个独立的字符串。FROM ubuntuCMD ["/usr/bin/wc","–help"]如果你系统容器每次运行相同的可执行文件,你应该考虑ENTRYPOINT和CMD结合使用。如果用户为docker run指定了参数,那么他们将覆盖CMD中指定的默认参数。注:不要混淆RUN和CMD。RUN实际上运行命令并提交结果;CMD在构建时什么都不执行,只是指定镜像将要执行的命令。EXPOSEEXPOSE <port> [<port>…]EXPOSE指令通知Docker容器运行时监听指定的网络端口。EXPOSE不会使容器端口对宿主机可访问。要那么做,你必须使用-p标记来发布一系列端口或者-P标记发布所有暴露端口。你可以暴露一个端口号并可以使用另一个端口对外发布。要在宿主机系统上设置端口重定向,使用-P标记。Docker网络功能支持网络内创建网络而不需要暴露端口,详细信息请查看功能概述。ADDADD有两种形式:ADD <src>… <dest>ADD ["<src>",… “<dest>”] (路径中包含空格需要这种形式)ADD指令ENTRYPOINTENTRYPOINT有2中形式:ENTRYPOINT [“executable”, “param1”, “param2”] (exec 形式, 首选)ENTRYPOINT command param1 param2 (shell 形式)ENTRYPOINT允许你配置一个将作为可执行程序运行的容器。例如,以下命令将启动一个nginx默认监控80端口:docker run -i -t –rm -p 80:80 nginxdocker run <image>的命令行参数将被追加到以exec形式的ENTRYPOINT所有元素后面,并且覆盖使用CMD指定的所有元素。这使得参数可以被传递给入口, 例如,docker run <image> -d将传递 -d参数给入口。你可以使用docker run –entrypoint标记覆盖ENTRYPOINT执行。shell形式阻止任何CMD或者run的命令行参数被使用,但是有个弊端,你的ENTRYPOINT将被作为/bin/sh -c的一个子命令启动,不能传递信号。这意味着可执行程序不是容器ID为1的进程 - 并且不会接受Unix信号 - 所以你的可执行程序不会接受来自docker stop <container>的SIGTERM。只有Dockerfile最后一个ENTRYPOINT指令会生效。VOLUMEVOLUME ["/data"]VOLUME指令创建一个具有指定名称的挂载点并且将其标记作为从宿主机或者其他容器外部挂载卷。值可以是一个JSON数组,VOLUME ["/var/log"],或者有多参数的纯字符串,比如:VOLUME /var/log或者VOLUME /var/log /var/db。更多Docker客户端的挂载指令信息/例子,移步文档通过卷共享目录。docker run命令使用基础镜像内指定位置存在的任意数据初始化新创建的卷。比如,认为以下Dockerfile片段:FROM ubuntuRUN mkdir /myvolRUN echo “hello world” > /myvol/greetingVOLUME /myvol这个Dockerfile的结果是致使docker run会创建一个新的挂载点/myvol并且拷贝gretting文件到新创建的卷。指定volumes的注意事项关于Dockerfile中的volumes,请注意以下事项。基于Windows容器的Volumes: 当使用基于Windows的容器,容器内volume的目标位置必须是以下之一:一个不存在的或者空目录C盘以外的驱动器:从Dockerfile内更改卷: 如果任何构建步骤在volume声明之后修改了数据,这些修改将会被丢弃。JSON 格式: 列表将会被作为一个JSON数组解析。你必须使用双引号(")而不是单引号(’)将单词包起来。主机目录在容器运行时声明: 主机目录(挂载点)本质上是与主机相关的。这是为了保证镜像的可移植性。因为一个指定的主机目录不能保证在所有的主机上可用。因此,你不能在Dockerfile内挂载一个主机目录。VOLUME指令不支持指定一个主机目录参数。你必须在容器创建或运行时指定挂载点。Exec形式ENTRYPOINT实例你可以使用ENTRYPOINT的exec形式设置相当稳定的默认命令和参数,然后使用CMD任意一种形式设置额外的更可能被修改的其他附加默认值。FROM ubuntuENTRYPOINT [“top”, “-b”]CMD ["-c"]但你运行该容器时,你仅仅可以看到top进程:$ docker run -it –rm –name test top -Htop - 08:25:00 up 7:27, 0 users, load average: 0.00, 0.01, 0.05Threads: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 stKiB Mem: 2056668 total, 1616832 used, 439836 free, 99352 buffersKiB Swap: 1441840 total, 0 used, 1441840 free. 1324440 cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top要进一步检查结果,可以使用docker exec:$ docker exec -it test ps auxUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 2.6 0.1 19752 2352 ? Ss+ 08:24 0:00 top -b -Hroot 7 0.0 0.1 15572 2164 ? R+ 08:25 0:00 ps aux并且你可以使用docker stop test请求top优雅的退出。以下Dockerfile展示了使用ENTRYPOINT在前端运行Apache(例如,PID为1)。FROM debian:stableRUN apt-get update && apt-get install -y –force-yes apache2EXPOSE 80 443VOLUME ["/var/www", “/var/log/apache2”, “/etc/apache2”]ENTRYPOINT ["/usr/sbin/apache2ctl", “-D”, “FOREGROUND”]如果你需要为单个可执行程序写一个启动脚本,你可以使用exec和gosu命令来确保最终执行程序可以收到Unix信号。#!/usr/bin/env bashset -eif [ “$1” = ‘postgres’ ]; then chown -R postgres “$PGDATA” if [ -z “$(ls -A “$PGDATA”)” ]; then gosu postgres initdb fi exec gosu postgres “$@“fiexec “$@“最后,如果你需要在退出时做一些额外的清理(或者与其他容器通信),或者配合执行多个可执行文件,你可能需要确保ENTRYPOINT脚本接受Unix信号,传递他们并做更多工作:#!/bin/sh# Note: I’ve written this using sh so it works in the busybox container too# USE the trap if you need to also do manual cleanup after the service is stopped,# or need to start multiple services in the one containertrap “echo TRAPed signal” HUP INT QUIT TERM# start service in background here/usr/sbin/apachectl startecho “[hit enter key to exit] or run ‘docker stop <container>’“read# stop service and clean up hereecho “stopping apache”/usr/sbin/apachectl stopecho “exited $0"如果你使用docker run -it -p 80:80 –name test apache运行该镜像,然后你可以使用docker exec检查容器进程,或者docker top,并且可以通过脚本停止Apache。$ docker exec -it test ps auxUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.1 0.0 4448 692 ? Ss+ 00:42 0:00 /bin/sh /run.sh 123 cmd cmd2root 19 0.0 0.2 71304 4440 ? Ss 00:42 0:00 /usr/sbin/apache2 -k startwww-data 20 0.2 0.2 360468 6004 ? Sl 00:42 0:00 /usr/sbin/apache2 -k startwww-data 21 0.2 0.2 360468 6000 ? Sl 00:42 0:00 /usr/sbin/apache2 -k startroot 81 0.0 0.1 15572 2140 ? R+ 00:44 0:00 ps aux$ docker top testPID USER COMMAND10035 root {run.sh} /bin/sh /run.sh 123 cmd cmd210054 root /usr/sbin/apache2 -k start10055 33 /usr/sbin/apache2 -k start10056 33 /usr/sbin/apache2 -k start$ /usr/bin/time docker stop testtestreal 0m 0.27suser 0m 0.03ssys 0m 0.03s注:你可以使用–entrypoint覆盖ENTRYPOINT配置,但是这只会将二进制设置为exec(sh -c不会被使用)。注:exec形式被解析为JSON数组,意味着你必须使用双引号(")包裹单词而不是单引号(’)。注:不像shell形式,exec形式并不会调用shell命令。这意味着不会做普通的shell处理。例如,ENTRIPOIN [“echo”, “$HOME”]将不能对$HOME做变量置换。如果你既想shell处理又想使用shell形式或直接执行一shell,例如:ENTRYPOINT [“sh”, “-c”, “echo $HOME”]。当使用exec形式和直接执行shell时,在shell形式这种情况下,是shell做的环境变量扩展,而不是docker。Shell形式ENTRYPOINT实例你可以为ENTRYPOINT指定一个纯文本的字符串,它会以/bin/sh -c的形式运行。这种形式将使用shell处理shell代替shell环境变量,并且将忽略任何CMD或者docker run命令的命令行参数。为了确保docker stop能够正常发出信号给任何长时间运行的ENTRYPOINT可执行文件,您需要记住使用exec启动它:FROM ubuntuENTRYPOINT exec top -b当你启动镜像,你会看到PID为1的进程:$ docker run -it –rm –name test topMem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cachedCPU: 5% usr 0% sys 0% nic 94% idle 0% io 0% irq 0% sirqLoad average: 0.08 0.03 0.05 2/98 6 PID PPID USER STAT VSZ %VSZ %CPU COMMAND 1 0 root R 3164 0% 0% top -b它将会在执行docker stop时彻底退出:$ /usr/bin/time docker stop testtestreal 0m 0.20suser 0m 0.02ssys 0m 0.04s如果你忘记了在ENTRYPOINT开头增加exec:FROM ubuntuENTRYPOINT top -bCMD –ignored-param1你可以启动它(为了下一步给它指定名称):$ docker run -it –name test top –ignored-param2Mem: 1704184K used, 352484K free, 0K shrd, 0K buff, 140621524238337K cachedCPU: 9% usr 2% sys 0% nic 88% idle 0% io 0% irq 0% sirqLoad average: 0.01 0.02 0.05 2/101 7 PID PPID USER STAT VSZ %VSZ %CPU COMMAND 1 0 root S 3168 0% 0% /bin/sh -c top -b cmd cmd2 7 1 root R 3164 0% 0% top -b你可以看到top的输出,ENTRYPOINT指定的不是PID 1。如果你接下来执行docker stop test,容器不会被彻底退出 - 超时以后top命令会被发送一个SIGKILL。$ docker exec -it test ps auxPID USER COMMAND 1 root /bin/sh -c top -b cmd cmd2 7 root top -b 8 root ps aux$ /usr/bin/time docker stop testtestreal 0m 10.19suser 0m 0.04ssys 0m 0.03s理解CMD和ENTRYPOINT如何交互CMD和ENTRYPOINT指令都定义了当启动一个容器时执行什么命令。描述他们如何一起工作的规则很少。Dockerfile至少应该指定一个CMD或ENTRYPOINT命令。当容器做一个可执行程序时,ENTRYPOINT应该被定义。CMD应该被用作一种给ENTRYPOINT定义默认参数的方式,或在容器中执行ad-hoc命令的方式。当运行容器时是用了交互参数时,CMD将被会被覆盖。下表显示了对不同ENTRYPOINT / CMD组合执行的命令: No ENTRYPOINTENTRYPOINT exec_entry p1_entryENTRYPOINT [“exec_entry”, “p1_entry”]No CMDerror, not allowed/bin/sh -c exec_entry p1_entryexec_entry p1_entryCMD [“exec_cmd”, “p1_cmd”]exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry exec_cmd p1_cmdCMD [“p1_cmd”, “p2_cmd”]p1_cmd p2_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry p1_cmd p2_cmdCMD exec_cmd p1_cmd/bin/sh -c exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry /bin/sh -c exec_cmd p1_cmd ...

February 14, 2019 · 4 min · jiezi

新近爆出的runC容器逃逸漏洞,用户如何面对?

runC是一个根据OCI(Open Container Initiative)标准创建并运行容器的CLI工具,目前Docker引擎内部也是基于runc构建的。 2019年2月11日,研究人员通过oss-security邮件列表(https://www.openwall.com/list… )披露了runc容器逃逸漏洞的详情,根据OpenWall的规定EXP会在7天后也就是2019年2月18日公开。此漏洞允许以root身份运行的容器以特权用户身份在主机上执行任意代码。实际上,这意味着容器可能会破坏Docker主机(覆盖Runc CLI),而所需要的只是能够使用root来运行容器。攻击者可以使用受感染的Docker镜像或对未受感染的正在运行的容器运行exec命令。针对此问题的已知缓解措施包括:使用只读主机文件系统运行运行用户命名空间不在容器中运行root正确配置的AppArmor / SELinux策略(当前的默认策略不够)Rancher团队第一时间响应收到披露邮件后,RancherOS团队立刻尝试编写了攻击脚本,在一个普通容器中运行一个非常简单的脚本就完成了对主机的攻击,将主机上的runc替换成了其他程序。漏洞披露后,Docker在第一时间发布了18.09.2,用户可升级到此版本以修复该漏洞。Rancher Labs研发团队同样第一时间响应,发布了Rancher v2.1.6、v2.0.11和v1.6.26,这三个新版本Rancher支持Docker刚刚发布的18.09.2,Rancher用户可以升级Docker版本以防止被该安全漏洞影响。无法升级Docker版本怎么办通常由于各种因素,很多用户的生产环境并不容易升级太新的Docker版本。为了帮助无法按照Docker官方建议升级至最新版Docker 18.09.2的用户解决此次问题,Rancher Labs团队更进一步,已经将修复程序反向移植到所有版本的Docker,为Docker 1.12.6、1.13.1、17.03.2、17.06.2、17.09.1、18.03.1和18.06.1提供补丁,修复这次漏洞!相关修补程序以及安装说明,请参考:https://github.com/rancher/ru…。RancherOS的更新:v1.5.1 和 v1.4.3RancherOS作为一款容器化操作系统,其中很多组件依赖runc,我们也在第一时间更新了补丁并发布了v1.5.1和v1.4.3两个版本。RancherOS的核心部件system-docker和user-docker都依赖runc,所以v1.5.1和v1.4.3都对他们进行了更新。而针对user-docker,RancherOS可以切换各种版本的docker engine, 所以我们对以下docker engine都进行了反向移植:v1.12.6/v1.13.1/v17.03.2/v17.06.2/v17.09.1/v17.12.1/v18.03.1/v18.06.1。如果是默认安装v1.5.1或v1.4.3,补丁程序已经是内置的,你无需任何操作就可以避免该漏洞。如果你希望使用早期的docker版本,那么切换user-docker时,请使用上面提到的补丁修复版本:同时v1.5.1版本也是支持docker 18.09.2,你可以切换到该版本,如果你考虑使用Docker官方的修复版本,只需简单运行: ros engine switch docker-18.09.2。我们推荐您使用最新的RancherOS v1.5.1版本,该除了修复CVE-2019-5736漏洞外还支持其他新特性以及一些Bug Fix。当然,因为仍然有很多用户在使用1.4.x版本,所以我们也发布了v1.4.3, 它只修复了runc漏洞,没有其他额外的更新。AWS相关镜像已经上传到各个region中,可以直接搜索查找并使用,包括AWS中国区。其他主要镜像列表参考:https://github.com/rancher/os…更多新特性和Bug Fix请参考v1.5.1的Release Notes:https://github.com/rancher/os…文档说明:https://rancher.com/docs/os/v…RancherOS专注于Docker在Linux上的精简体验,它还是一个小众的开源项目,欢迎您下载使用并给RancherOS团队提供更多反馈。 同时,Github上的Star也是鼓励我们继续前行的精神动力喔~初心不忘,为用户的Docker & K8S之旅护航Rancher Kubernetes平台拥有着超过一亿次下载量,我们深知安全问题对于用户而言的重要性,更遑论那些通过Rancher平台在生产环境中运行Docker及Kubernetes的数千万用户。2018年年底Kubernetes被爆出的首个严重安全漏洞CVE-2018-1002105,就是由Rancher Labs联合创始人及首席架构师Darren Shepherd发现的。2019年1月Kubernetes被爆出仪表盘和外部IP代理安全漏洞时,Rancher Labs也是第一时间向用户响应,确保所有Rancher 2.x和1.6.x的用户都完全不被漏洞影响。未来,Rancher也将一如既往陪伴与支持在用户的K8S之路左右❤️

February 13, 2019 · 1 min · jiezi

k8s与HPA--通过 Prometheus adaptor 来自定义监控指标

k8s与HPA–通过 Prometheus adaptor 来自定义监控指标自动扩展是一种根据资源使用情况自动扩展或缩小工作负载的方法。 Kubernetes中的自动缩放有两个维度:Cluster Autoscaler处理节点扩展操作,Horizontal Pod Autoscaler自动扩展部署或副本集中的pod数量。 Cluster Autoscaling与Horizontal Pod Autoscaler一起用于动态调整计算能力以及系统满足SLA所需的并行度。虽然Cluster Autoscaler高度依赖托管您的集群的云提供商的基础功能,但HPA可以独立于您的IaaS / PaaS提供商运营。Horizontal Pod Autoscaler功能最初是在Kubernetes v1.1中引入的,并且从那时起已经发展了很多。 HPA缩放容器的版本1基于观察到的CPU利用率,后来基于内存使用情况。在Kubernetes 1.6中,引入了一个新的API Custom Metrics API,使HPA能够访问任意指标。 Kubernetes 1.7引入了聚合层,允许第三方应用程序通过将自己注册为API附加组件来扩展Kubernetes API。 Custom Metrics API和聚合层使Prometheus等监控系统可以向HPA控制器公开特定于应用程序的指标。Horizontal Pod Autoscaler实现为一个控制循环,定期查询Resource Metrics API以获取CPU /内存等核心指标和针对特定应用程序指标的Custom Metrics API。以下是为Kubernetes 1.9或更高版本配置HPA v2的分步指南。您将安装提供核心指标的Metrics Server附加组件,然后您将使用演示应用程序根据CPU和内存使用情况展示pod自动扩展。在本指南的第二部分中,您将部署Prometheus和自定义API服务器。您将使用聚合器层注册自定义API服务器,然后使用演示应用程序提供的自定义指标配置HPA。在开始之前,您需要安装Go 1.8或更高版本并在GOPATH中克隆k8s-prom-hpa repo。cd $GOPATHgit clone https://github.com/stefanprodan/k8s-prom-hpa部署 Metrics Serverkubernetes Metrics Server是资源使用数据的集群范围聚合器,是Heapster的后继者。度量服务器通过汇集来自kubernetes.summary_api的数据来收集节点和pod的CPU和内存使用情况。摘要API是一种内存高效的API,用于将数据从Kubelet / cAdvisor传递到度量服务器。在HPA的第一个版本中,您需要Heapster来提供CPU和内存指标,在HPA v2和Kubernetes 1.8中,只有在启用horizontal-pod-autoscaler-use-rest-clients时才需要指标服务器。默认情况下,Kubernetes 1.9中启用了HPA rest客户端。 GKE 1.9附带预安装的Metrics Server。在kube-system命名空间中部署Metrics Server:kubectl create -f ./metrics-server一分钟后,度量服务器开始报告节点和pod的CPU和内存使用情况。查看nodes metrics:kubectl get –raw “/apis/metrics.k8s.io/v1beta1/nodes” | jq .结果如下:{ “kind”: “NodeMetricsList”, “apiVersion”: “metrics.k8s.io/v1beta1”, “metadata”: { “selfLink”: “/apis/metrics.k8s.io/v1beta1/nodes” }, “items”: [ { “metadata”: { “name”: “ip-10-1-50-61.ec2.internal”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/nodes/ip-10-1-50-61.ec2.internal”, “creationTimestamp”: “2019-02-13T08:34:05Z” }, “timestamp”: “2019-02-13T08:33:38Z”, “window”: “30s”, “usage”: { “cpu”: “78322168n”, “memory”: “563180Ki” } }, { “metadata”: { “name”: “ip-10-1-57-40.ec2.internal”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/nodes/ip-10-1-57-40.ec2.internal”, “creationTimestamp”: “2019-02-13T08:34:05Z” }, “timestamp”: “2019-02-13T08:33:42Z”, “window”: “30s”, “usage”: { “cpu”: “48926263n”, “memory”: “554472Ki” } }, { “metadata”: { “name”: “ip-10-1-62-29.ec2.internal”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/nodes/ip-10-1-62-29.ec2.internal”, “creationTimestamp”: “2019-02-13T08:34:05Z” }, “timestamp”: “2019-02-13T08:33:36Z”, “window”: “30s”, “usage”: { “cpu”: “36700681n”, “memory”: “326088Ki” } } ]}查看pods metrics:kubectl get –raw “/apis/metrics.k8s.io/v1beta1/pods” | jq .结果如下:{ “kind”: “PodMetricsList”, “apiVersion”: “metrics.k8s.io/v1beta1”, “metadata”: { “selfLink”: “/apis/metrics.k8s.io/v1beta1/pods” }, “items”: [ { “metadata”: { “name”: “kube-proxy-77nt2”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-proxy-77nt2”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:00Z”, “window”: “30s”, “containers”: [ { “name”: “kube-proxy”, “usage”: { “cpu”: “2370555n”, “memory”: “13184Ki” } } ] }, { “metadata”: { “name”: “cluster-autoscaler-n2xsl”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/cluster-autoscaler-n2xsl”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:12Z”, “window”: “30s”, “containers”: [ { “name”: “cluster-autoscaler”, “usage”: { “cpu”: “1477997n”, “memory”: “54584Ki” } } ] }, { “metadata”: { “name”: “core-dns-autoscaler-b4785d4d7-j64xd”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/core-dns-autoscaler-b4785d4d7-j64xd”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:08Z”, “window”: “30s”, “containers”: [ { “name”: “autoscaler”, “usage”: { “cpu”: “191293n”, “memory”: “7956Ki” } } ] }, { “metadata”: { “name”: “spot-interrupt-handler-8t2xk”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/spot-interrupt-handler-8t2xk”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:04Z”, “window”: “30s”, “containers”: [ { “name”: “spot-interrupt-handler”, “usage”: { “cpu”: “844907n”, “memory”: “4608Ki” } } ] }, { “metadata”: { “name”: “kube-proxy-t5kqm”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-proxy-t5kqm”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:08Z”, “window”: “30s”, “containers”: [ { “name”: “kube-proxy”, “usage”: { “cpu”: “1194766n”, “memory”: “12204Ki” } } ] }, { “metadata”: { “name”: “kube-proxy-zxmqb”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-proxy-zxmqb”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:06Z”, “window”: “30s”, “containers”: [ { “name”: “kube-proxy”, “usage”: { “cpu”: “3021117n”, “memory”: “13628Ki” } } ] }, { “metadata”: { “name”: “aws-node-rcz5c”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/aws-node-rcz5c”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:15Z”, “window”: “30s”, “containers”: [ { “name”: “aws-node”, “usage”: { “cpu”: “1217989n”, “memory”: “24976Ki” } } ] }, { “metadata”: { “name”: “aws-node-z2qxs”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/aws-node-z2qxs”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:15Z”, “window”: “30s”, “containers”: [ { “name”: “aws-node”, “usage”: { “cpu”: “1025780n”, “memory”: “46424Ki” } } ] }, { “metadata”: { “name”: “php-apache-899d75b96-8ppk4”, “namespace”: “default”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/default/pods/php-apache-899d75b96-8ppk4”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:08Z”, “window”: “30s”, “containers”: [ { “name”: “php-apache”, “usage”: { “cpu”: “24612n”, “memory”: “27556Ki” } } ] }, { “metadata”: { “name”: “load-generator-779c5f458c-9sglg”, “namespace”: “default”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/default/pods/load-generator-779c5f458c-9sglg”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:34:56Z”, “window”: “30s”, “containers”: [ { “name”: “load-generator”, “usage”: { “cpu”: “0”, “memory”: “336Ki” } } ] }, { “metadata”: { “name”: “aws-node-v9jxs”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/aws-node-v9jxs”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:00Z”, “window”: “30s”, “containers”: [ { “name”: “aws-node”, “usage”: { “cpu”: “1303458n”, “memory”: “28020Ki” } } ] }, { “metadata”: { “name”: “kube2iam-m2ktt”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube2iam-m2ktt”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:11Z”, “window”: “30s”, “containers”: [ { “name”: “kube2iam”, “usage”: { “cpu”: “1328864n”, “memory”: “9724Ki” } } ] }, { “metadata”: { “name”: “kube2iam-w9cqf”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube2iam-w9cqf”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:03Z”, “window”: “30s”, “containers”: [ { “name”: “kube2iam”, “usage”: { “cpu”: “1294379n”, “memory”: “8812Ki” } } ] }, { “metadata”: { “name”: “custom-metrics-apiserver-657644489c-pk8rb”, “namespace”: “monitoring”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/monitoring/pods/custom-metrics-apiserver-657644489c-pk8rb”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:04Z”, “window”: “30s”, “containers”: [ { “name”: “custom-metrics-apiserver”, “usage”: { “cpu”: “22409370n”, “memory”: “42468Ki” } } ] }, { “metadata”: { “name”: “kube2iam-qghgt”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube2iam-qghgt”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:11Z”, “window”: “30s”, “containers”: [ { “name”: “kube2iam”, “usage”: { “cpu”: “2078992n”, “memory”: “16356Ki” } } ] }, { “metadata”: { “name”: “spot-interrupt-handler-ps745”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/spot-interrupt-handler-ps745”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:10Z”, “window”: “30s”, “containers”: [ { “name”: “spot-interrupt-handler”, “usage”: { “cpu”: “611566n”, “memory”: “4336Ki” } } ] }, { “metadata”: { “name”: “coredns-68fb7946fb-2xnpp”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/coredns-68fb7946fb-2xnpp”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:12Z”, “window”: “30s”, “containers”: [ { “name”: “coredns”, “usage”: { “cpu”: “1610381n”, “memory”: “10480Ki” } } ] }, { “metadata”: { “name”: “coredns-68fb7946fb-9ctjf”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/coredns-68fb7946fb-9ctjf”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:13Z”, “window”: “30s”, “containers”: [ { “name”: “coredns”, “usage”: { “cpu”: “1418850n”, “memory”: “9852Ki” } } ] }, { “metadata”: { “name”: “prometheus-7d4f6d4454-v4fnd”, “namespace”: “monitoring”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/monitoring/pods/prometheus-7d4f6d4454-v4fnd”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:00Z”, “window”: “30s”, “containers”: [ { “name”: “prometheus”, “usage”: { “cpu”: “17951807n”, “memory”: “202316Ki” } } ] }, { “metadata”: { “name”: “metrics-server-7cdd54ccb4-k2x7m”, “namespace”: “kube-system”, “selfLink”: “/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/metrics-server-7cdd54ccb4-k2x7m”, “creationTimestamp”: “2019-02-13T08:35:19Z” }, “timestamp”: “2019-02-13T08:35:04Z”, “window”: “30s”, “containers”: [ { “name”: “metrics-server-nanny”, “usage”: { “cpu”: “144656n”, “memory”: “5716Ki” } }, { “name”: “metrics-server”, “usage”: { “cpu”: “568327n”, “memory”: “16268Ki” } } ] } ]}基于CPU和内存使用情况的Auto Scaling您将使用基于Golang的小型Web应用程序来测试Horizontal Pod Autoscaler(HPA)。将podinfo部署到默认命名空间:kubectl create -f ./podinfo/podinfo-svc.yaml,./podinfo/podinfo-dep.yaml使用NodePort服务访问podinfo,地址为http:// <K8S_PUBLIC_IP>:31198。接下来定义一个至少维护两个副本的HPA,如果CPU平均值超过80%或内存超过200Mi,则最多可扩展到10个:apiVersion: autoscaling/v2beta1kind: HorizontalPodAutoscalermetadata: name: podinfospec: scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: podinfo minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu targetAverageUtilization: 80 - type: Resource resource: name: memory targetAverageValue: 200Mi创建这个hpa:kubectl create -f ./podinfo/podinfo-hpa.yaml几秒钟后,HPA控制器联系度量服务器,然后获取CPU和内存使用情况:kubectl get hpaNAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGEpodinfo Deployment/podinfo 2826240 / 200Mi, 15% / 80% 2 10 2 5m为了增加CPU使用率,请使用rakyll / hey运行负载测试:#install heygo get -u github.com/rakyll/hey#do 10K requestshey -n 10000 -q 10 -c 5 http://<K8S_PUBLIC_IP>:31198/您可以使用以下方式监控HPA事件:$ kubectl describe hpaEvents: Type Reason Age From Message —- —— —- —- ——- Normal SuccessfulRescale 7m horizontal-pod-autoscaler New size: 4; reason: cpu resource utilization (percentage of request) above target Normal SuccessfulRescale 3m horizontal-pod-autoscaler New size: 8; reason: cpu resource utilization (percentage of request) above target 暂时删除podinfo。稍后将在本教程中再次部署它:kubectl delete -f ./podinfo/podinfo-hpa.yaml,./podinfo/podinfo-dep.yaml,./podinfo/podinfo-svc.yaml部署 Custom Metrics Server要根据自定义指标进行扩展,您需要拥有两个组件。一个组件,用于从应用程序收集指标并将其存储在Prometheus时间序列数据库中。第二个组件使用collect(k8s-prometheus-adapter)提供的指标扩展了Kubernetes自定义指标API。您将在专用命名空间中部署Prometheus和适配器。创建monitoring命名空间:kubectl create -f ./namespaces.yaml在monitoring命名空间中部署Prometheus v2:kubectl create -f ./prometheus生成Prometheus适配器所需的TLS证书:make certs生成以下几个文件:# ls outputapiserver.csr apiserver-key.pem apiserver.pem部署Prometheus自定义指标API适配器:kubectl create -f ./custom-metrics-api列出Prometheus提供的自定义指标:kubectl get –raw “/apis/custom.metrics.k8s.io/v1beta1” | jq .获取monitoring命名空间中所有pod的FS使用情况:kubectl get –raw “/apis/custom.metrics.k8s.io/v1beta1/namespaces/monitoring/pods//fs_usage_bytes” | jq .查询结果如下:{ “kind”: “MetricValueList”, “apiVersion”: “custom.metrics.k8s.io/v1beta1”, “metadata”: { “selfLink”: “/apis/custom.metrics.k8s.io/v1beta1/namespaces/monitoring/pods/%2A/fs_usage_bytes” }, “items”: [ { “describedObject”: { “kind”: “Pod”, “namespace”: “monitoring”, “name”: “custom-metrics-apiserver-657644489c-pk8rb”, “apiVersion”: “/v1” }, “metricName”: “fs_usage_bytes”, “timestamp”: “2019-02-13T08:52:30Z”, “value”: “94253056” }, { “describedObject”: { “kind”: “Pod”, “namespace”: “monitoring”, “name”: “prometheus-7d4f6d4454-v4fnd”, “apiVersion”: “/v1” }, “metricName”: “fs_usage_bytes”, “timestamp”: “2019-02-13T08:52:30Z”, “value”: “24576” } ]}基于custom metrics 自动伸缩在默认命名空间中创建podinfo NodePort服务和部署:kubectl create -f ./podinfo/podinfo-svc.yaml,./podinfo/podinfo-dep.yamlpodinfo应用程序公开名为http_requests_total的自定义指标。 Prometheus适配器删除_total后缀并将度量标记为计数器度量标准。从自定义指标API获取每秒的总请求数:kubectl get –raw “/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods//http_requests” | jq .{ “kind”: “MetricValueList”, “apiVersion”: “custom.metrics.k8s.io/v1beta1”, “metadata”: { “selfLink”: “/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/%2A/http_requests” }, “items”: [ { “describedObject”: { “kind”: “Pod”, “namespace”: “default”, “name”: “podinfo-6b86c8ccc9-kv5g9”, “apiVersion”: “/__internal” }, “metricName”: “http_requests”, “timestamp”: “2018-01-10T16:49:07Z”, “value”: “901m” }, { “describedObject”: { “kind”: “Pod”, “namespace”: “default”, “name”: “podinfo-6b86c8ccc9-nm7bl”, “apiVersion”: “/__internal” }, “metricName”: “http_requests”, “timestamp”: “2018-01-10T16:49:07Z”, “value”: “898m” } ]}建一个HPA,如果请求数超过每秒10个,将扩展podinfo部署:apiVersion: autoscaling/v2beta1kind: HorizontalPodAutoscalermetadata: name: podinfospec: scaleTargetRef: apiVersion: extensions/v1beta1 kind: Deployment name: podinfo minReplicas: 2 maxReplicas: 10 metrics: - type: Pods pods: metricName: http_requests targetAverageValue: 10 在默认命名空间中部署podinfo HPA:kubectl create -f ./podinfo/podinfo-hpa-custom.yaml几秒钟后,HPA从指标API获取http_requests值:kubectl get hpaNAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGEpodinfo Deployment/podinfo 899m / 10 2 10 2 1m在podinfo服务上应用一些负载,每秒25个请求:#install heygo get -u github.com/rakyll/hey#do 10K requests rate limited at 25 QPShey -n 10000 -q 5 -c 5 http://<K8S-IP>:31198/healthz几分钟后,HPA开始扩展部署:kubectl describe hpaName: podinfoNamespace: defaultReference: Deployment/podinfoMetrics: ( current / target ) “http_requests” on pods: 9059m / 10Min replicas: 2Max replicas: 10Events: Type Reason Age From Message —- —— —- —- ——- Normal SuccessfulRescale 2m horizontal-pod-autoscaler New size: 3; reason: pods metric http_requests above target按照当前的每秒请求速率,部署永远不会达到10个pod的最大值。三个复制品足以使每个吊舱的RPS保持在10以下。负载测试完成后,HPA会将部署缩到其初始副本:Events: Type Reason Age From Message —- —— —- —- ——- Normal SuccessfulRescale 5m horizontal-pod-autoscaler New size: 3; reason: pods metric http_requests above target Normal SuccessfulRescale 21s horizontal-pod-autoscaler New size: 2; reason: All metrics below target您可能已经注意到自动缩放器不会立即对使用峰值做出反应。默认情况下,度量标准同步每30秒发生一次,只有在最后3-5分钟内没有重新缩放时才能进行扩展/缩小。通过这种方式,HPA可以防止快速执行冲突的决策,并为Cluster Autoscaler提供时间。结论并非所有系统都可以通过单独依赖CPU /内存使用指标来满足其SLA,大多数Web和移动后端需要基于每秒请求进行自动扩展以处理任何流量突发。对于ETL应用程序,可以通过作业队列长度超过某个阈值等来触发自动缩放。通过使用Prometheus检测应用程序并公开正确的自动缩放指标,您可以对应用程序进行微调,以更好地处理突发并确保高可用性。 ...

February 13, 2019 · 6 min · jiezi

runC爆严重漏洞影响Kubernetes、Docker,阿里云修复runC漏洞的公告

2月12日,春节刚过。朋友圈就看到《runC爆严重漏洞:Kubernetes、Docker等中招》的消息。runC 是 Docker,Kubernetes 等依赖容器的应用程序的底层容器运行时。此次爆出的严重安全漏洞可使攻击者以 root 身份在主机上执行任何命令。容器的安全性一直是容器技术的一个短板。关于容器最大的安全隐患是攻击者可以使用恶意程序感染容器,更严重时可以攻击主机系统。2019年2月11日,研究人员通过oss-security邮件列表(https://www.openwall.com/list…)披露了runc容器逃逸漏洞的详情,根据OpenWall的规定EXP将在2019年2月18日公开。同样2月12日,阿里云文档中心已经发布《修复runc漏洞CVE-2019-5736的公告》。阿里云容器服务已修复runc漏洞CVE-2019-5736。本文介绍该漏洞的影响范围及解决方法。背景:Docker、containerd或者其他基于runc的容器在运行时存在安全漏洞,攻击者可以通过特定的容器镜像或者exec操作获取到宿主机runc执行时的文件句柄并修改掉runc的二进制文件,从而获取到宿主机的root执行权限。在云栖社区的小伙伴中,已有同学询问:阿里的k8s容器服务已经修复了? 无需人工处理?群主回复:新集群不受影响。老集群需要手动升级docker或者runc受影响的版本,可根据文档中具体的修复步骤进行操作。本文作者:云篆阅读原文本文为云栖社区原创内容,未经允许不得转载。

February 13, 2019 · 1 min · jiezi

Docker Stack 部署web集群

Docker越来越成熟,功能也越来越强大。使用Dokcer Stack做服务集群也是非常的方便,docker 自己就提供了负载功能,感觉很方便,就想给大家分享一下,做一个简单的教程。环境 我是用了两台centos7的虚拟机来做这个教程他们的ip分别是主服务器:192.168.0.105 // 也是私有仓库服务器服务器2: 192.168.0.49 这篇帖子中所有的代码 github地址:https://github.com/lpxxn/godo…设置Docker Swarm 我以192.168.0.105做为主服务器,在他上面开启swarmdocker swarm init 执行命令后会给出加入这个swarm的命令 在192.168.0.49上执行命令加入swarmdocker swarm join –token SWMTKN-1-425vswwmb8o34uhnmo58w0k4rfzs5okjtye7mokpqps1vl9ymq-0p6pr2gua7l8a6udb67tfndoo 192.168.0.105:2377 这样我们就建好了swarm,两台主机现在建立好了关系。web服务 web服务是用go语言写一个简单的接口,返回主机的名称:这样方便我们查看是否有负载复制代码package mainimport (“fmt"“log"“net/http"“os”)func main() {http.HandleFunc("/hi”, func(w http.ResponseWriter, r *http.Request) { hostName, _ := os.Hostname() fmt.Fprintf(w, “HostName: %s”, hostName)})log.Fatal(http.ListenAndServe(":8000”, nil))}复制代码Docker file看一下dockerfile 文件:执行的意思就是基于golang境像,把代码复制到相应文件夹,暴露出端口,运行程序。简单吧复制代码FROM golangCopy the current directory contents into the containerCOPY . /go/src/github.com/lpxxn/godockerswarm/WORKDIR /go/src/github.com/lpxxn/godockerswarm/RUN go buildEXPOSE 8000CMD [”./godockerswarm"]复制代码看一下dockerfile 文件所在的文件夹在这个目录下执行docker build 命令:docker build . -t goweb:1.0你可以运行一下新生成的镜像docker run -p 8100:8000 7a7e3镜像提交到私有仓库关于如何搭建私有仓库服务器我这里的就多说了,可以去我之前的帖子看一下 地址1:http://www.cnblogs.com/li-pen… 地址2:https://yq.aliyun.com/article… 也可以用harbor自己搭建,这个我还没有做过教程,有时间再写。因为集群的上机器是自动从仓库取镜像然后再运行程序,所以需要将我们上面生成的镜像推送到我们的私有仓库上去。我自己搭建的使用tag重新命名docker tag goweb:1.0 lpxxn.com:5000/goweb:1.0推送docker push lpxxn.com:5000/goweb:1.0docker-compose 文件 接下来创建docker-compose.yml文件image 就是我们上面创建好的镜像。运行5个应用程序,docker 会自己做负载,端口映射8111,失败时自动重启服务,并且创建了自己的网络,当有多个server服务时这个非常有用。里面的具体参数,大家可以看官方教程:https://docs.docker.com/compo…复制代码version: “3"services: web:image: lpxxn.com:5000/goweb:1.0deploy: replicas: 5 resources: limits: cpus: “0.1” memory: 50M restart_policy: condition: on-failureports: - “8111:8000"networks: - gowebnetnetworks: gowebnet:复制代码部署应用 到了最后的阶段了,部属一样很简单,执行deploy命令docker stack deploy -c docker-compose.yml mygoweb查看启动的服务docker service ps mygoweb测试服务看这些返回的主机名:不一样吧。docker 为我们做了负载了。本文作者:lpxxn阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 12, 2019 · 1 min · jiezi

Traefik 入手及简单配置

Traefik 入手及简单配置Traefik 与 nginx 一样,是一款反向代理的工具,至于使用他原因基于以下几点漂亮的 dashboard 界面可基于容器 label 进行配置新添服务简单,不用像 nginx 一样复杂配置,并且不用频繁重启对 prometheus 和 k8s 的集成尝试一下…接下来讲一下它的基本功能以及文件配置安装下载二进制文件,指定配置文件,直接执行可以启动。./traefik -c traefik.toml当然,你也可以通过 docker 启动,参考 Traefik Get Started。另外,如果需要使用 docker 启动,需要所有的服务都在一个 network 中,或者设置 traefik 的 network 为 host。启动成功后,可以访问 localhost:8080 访问 Dashboard 页面。问题每当有配置改动需要重启时,只能杀了进程,然后启动,导致服务有短暂的暂停补充一下,每当使用 docker 新添一个服务时,不需要更改配置,或者更改部分配置时,如 file provider,traefik 会监听配置文件变化并自动重启。但是需要修改 https 的证书或者日志的路径时,需要手动重启。所以需要手动重启的时候并不是很多。当配置文件修改后,会有语法错误,无法向 nginx -t 一样检查是否配置文件有问题日志[accessLog]# Sets the file path for the access log. If not specified, stdout will be used.# Intermediate directories are created if necessary.## Optional# Default: os.Stdout#filePath = “./traefik-access.json”# Format is either “json” or “common”.## Optional# Default: “common”#format = “json"日志文件配置为 json 格式,方便调试。同时,强烈推荐 jq,一款 linux 下解析 json 的工具。以下是两个常用的命令,统计某个站点的请求以及响应时间。不过最好建议有专门的日志系统去处理,可以获取更完善的,更定制化的信息。另外,traefik 无法查看请求的 body。# 筛选特定站点的请求cat traefik-access.json | jq ‘select(.[“RequestHost”] == “shici.xiange.tech”) | {RequestPath, RequestHost, DownstreamStatus, “request_User-Agent”, OriginDuration}’# 筛选大于 300ms 的接口cat traefik-access.json | jq ‘select(.[“RequestHost”] == “shici.xiange.tech” and .OriginDuration > 300000000) | {RequestPath, RequestHost, DownstreamStatus,“request_User-Agent”, OriginDuration, DownstreamContentSize}‘prometheus + grafanajq 虽然可以分析日志,但是适合做日志的统计以及更细化的分析。Prometheus 作为时序数据库,可以用来监控 traefik 的日志,支持更加灵活的查询,报警以及可视化。traefik 默认设置 prometheus 作为日志收集工具。另外可以使用 grafana 做为 prometheus 的可视化工具。某个服务的平均响应时间PromQL 为sum(traefik_backend_request_duration_seconds_sum{backend="$backend”}) / sum(traefik_backend_requests_total{backend="$backend"}) * 1000某个服务响应时长大于 300ms 的请求的个数TODO统计请求数大于 10000 的服务TODOentryPointhttphttp 配置在 entryPoints 中,暴露出80端口。开启 gzip 压缩,使用 compress = true 来配置。[entryPoints] [entryPoints.http] address = “:80” compress = true # 如果配置了此项,会使用 307 跳转到 https [entryPoints.http.redirect] entryPoint = “https"考虑到隐私以及安全,不对外公开的服务可以配置 Basic Auth,Digest Auth 或者 WhiteList,或者直接搭建 VPN,在内网内进行访问。如在我服务器上 xiange.tech 对外公开,xiange.me 只能通过VPN访问。更多文档查看 Traefik entrypoints。https使用 Let’s Encrypt 安装证书后,在 entryPoints.https.tls.certificats 中指定证书位置。[entryPoints] [entryPoints.https] address = “:443” compress = true [[entryPoints.https.tls.certificates]] certFile = “/etc/letsencrypt/live/xiange.tech/fullchain.pem” keyFile = “/etc/letsencrypt/live/xiange.tech/privkey.pem"另外,traefik 默认开启 http2。other另外,如果需要暴露其它的端口出去,如 consul 的 8500,类似于 nginx 的 listen 指令。可以设置[entryPoints] [entryPoints.consul] address = “:8500"Dockertraefik 会监听 docker.sock,根据容器的 label 进行配置。容器的端口号需要暴露出来,但是不需要映射到 Host。因为 traefik 可以通过 docker.sock 找到 container 的 IP 地址以及端口号,无需使用 docker-proxy 转发到 Host。version: ‘3’services: frontend: image: your-frontend-server-image labels: - “traefik.frontend.rule=Host:frontend.xiange.tech” api: image: your-api-server-image expose: 80 labels: # 同域配置, /api 走server - “traefik.frontend.rule=Host:frontend.xiange.tech;PathPrefix:/api"如何给一个服务配置多个域名labels: - “traefik.prod.frontend.rule=Host:whoami.xiange.tech” - “traefik.another.frontend.rule=Host:who.xiange.tech” - “traefik.dev.frontend.rule=Host:whoami.xiange.me"如何把前端和后端配置在统一域名services: frontend: image: your-frontend-server-image labels: - “traefik.frontend.rule=Host:frontend.xiange.tech” api: image: your-api-server-image expose: 80 labels: - “traefik.frontend.rule=Host:frontend.xiange.tech;PathPrefix:/api"部署时,如果项目代码有更新,如何当新服务 start 后,再去 drop 掉旧服务TODO负载均衡如果使用docker,对一个容器进行扩展后,traefik 会自动做负载均衡,而 nginx 需要手动干预。version: ‘3’services: whoami: image: emilevauge/whoami labels: - “traefik.frontend.rule=Host:whoami.xiange.tech"手动扩展为3个实例,可以自动实现负载均衡。实现效果可以直接访问 whoami.xiange.tech,每次通过 WRR 策略分配到不同的容器处理,可以通过 Hostname 和 IP 字段看出。docker-compose up whoami=3手动配置当然,以上反向代理配置都是基于 docker,那如何像 nginx 一样配置呢。如把 consul.xiange.me 转发到 8500 这个端口。可以利用 traefik 的 file provider。[file] [backends] # consul 是服务的名字,也可以叫张三,也可以叫李四 [backends.consul] [backends.consul.servers] [backends.consul.servers.website] url = “http://0.0.0.0:8500” weight = 1 [frontends] [frontends.consul] entryPoints = [“http”] backend = “consul” [frontends.consul.routes] # website 是路由的名字,也可以叫阿猫,也可以叫阿狗 [frontends.consul.routes.website] rule = “Host:consul.xiange.me” # 可以配置多个域名 [frontends.consul.routes.website2] rule = “Host:config.xiange.me” ...

February 3, 2019 · 2 min · jiezi

log4j2+ELK

问题初衷最近有个项目需求,需要统计下用户app的使用情况,比如:什么时候登录的,查询了什么内容等信息。解决方案1.定义用户轨迹模型,每步操作都写到数据库中,然后在前端展示。优点:可以针对业务需求自定义模型,操作灵活,有针对性。缺点:扩展能力需要靠自己设计,统计展示画面需要自己做。2.用现成的用户数据分析平台,网上有很多成熟的平台,对接其api接口即可。优点:各种分析图,什么有的没有的,见过的没见过的一应俱全。ps:现在用不上以后可能会用上。缺点:受制于人,主要还要看自身业务是否适用。3.ELK,集中式日志管理,特点:收集,传输,存储,管理,告警。优点:开源,可以通过日志记录各种你想要记录的东西。丰富的分析图表。可轻松应对分布式,数据量大的情况。ELK对接Elasticsearch+Logstash+Kibana(ELK)是一套开源的日志管理方案。Elasticsearch:负责日志检索和分析Logstash:负责日志的收集,处理和储存Kibana:负责日志的可视化对于保存日志到文件的的项目,可以用logstash的logstash-input-file插件直接进行文件读取,处理后转存到Elasticsearch中。处理可以用logstash-filter-kv键值插件或者logstash-filter-mutate等插件进行解析。具体查看Filter plugins。由于我们的项目没有记录日志文件,所以选择直接发送日志信息到logstash,(对于log4j,logstash有专门的input插件)。log4j2的配置<?xml version=“1.0” encoding=“UTF-8”?><Configuration> <Properties> <Property name=“PATTERN”>{“logger”: “%logger”, “level”: “%level”, “msg”: “%message”}%n</Property> </Properties> <Appenders> <Socket name=“logstash-tcp” host=“127.0.0.1” port=“4560” protocol=“TCP”> <PatternLayout pattern="${PATTERN}"/> </Socket> </Appenders> <Loggers> <Root level=“INFO”> <AppenderRef ref=“logstash-tcp” /> </Root> </Loggers></Configuration>logstash的配置input { tcp { host => “127.0.0.1” port => 4560 codec => json { charset => “UTF-8” } }}filter {}output { elasticsearch { hosts => [“localhost”] manage_template => false index => “logstash-%{+YYYY.MM.dd}” document_type => “logstash” }}转存到Elasticsearch的message就是我们log4j2配置中的Json形式,如果想要将message拉平,那么只需要加入logstash-filter-json。filter { json { source => “message” }}这样保存到Elasticsearch中的数据,就会变成如下形式{ … “message” => “{"logger": "elk", "level": "INFO", "msg": "logstash test"}\r”, “logger” => “elk”, “level” => “INFO”, “msg” => “logstash test”} 这样对于Kibana分析的时候比较方便。log4j2用Socket的方式会有一个问题,当logstash断掉,重启后你会发现收不到log4j2的日志了,传输断掉没有重连。在生产环境中,elk断掉,我们不可能在去重启所有与之相连的服务。所以接下来我们采用gelf方式。修改pom<dependency> <groupId>biz.paluch.logging</groupId> <artifactId>logstash-gelf</artifactId> <version>1.12.0</version></dependency>修改log4j2的配置<?xml version=“1.0” encoding=“UTF-8”?><Configuration> <Appenders> <Gelf name=“logstash-gelf” host=“tcp:127.0.0.1” port=“4560” version=“1.1”> <Field name=“timestamp” pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}" /> <Field name=“logger” pattern="%logger" /> <Field name=“level” pattern="%level" /> <Field name=“className” pattern="%C" /> <Field name=“method” pattern="%M" /> <Field name=“line” pattern="%L" /> <Field name=“server” pattern="%host" /> </Gelf> </Appenders> <Loggers> <Root level=“INFO”> <AppenderRef ref=“logstash-gelf” /> </Root> </Loggers></Configuration>修改logstash配置input { gelf { host => “127.0.0.1” port => 4560 use_tcp => true codec => json { charset => “UTF-8” } }}filter { json { source => “message” }}output { elasticsearch { hosts => [“localhost”] manage_template => false index => “logstash-%{+YYYY.MM.dd}” document_type => “logstash” }}保存到Elasticsearch中的数据就会如同log4j2中的配置格式一样,由于我们这里依然配置了json filter,如果你的message是json字串,这里依然会拉平处理。ELK搭建以上我们log4j2与logstash的对接就完成了,对于docker部署elk,比较简单,网上有很多教程,当然还是推荐大家先去官网看看,注意版本对应。我选择的是sebp/elk,集成好的elk容器。详细的文档elk-docker这里主要说说需要注意的地方,强烈建议安装之前看下文档中的Prerequisites。首先,我就遇见了vm.max_map_count限制问题,在Elasticsearch version 5这是最常出现的问题。通过,如下修改,在重启后又会恢复原值。sysctl -w vm.max_map_count=262144持久性的做法是修改/etc/sysctl.conf文件中的vm.max_map_countecho “vm.max_map_count=262144” > /etc/sysctl.confsysctl -p其次,logstash的配置文件在/etc/logstash/conf.d/目录下,将input,filter,out分开,最终组合成一个,建议将/etc/logstash/conf.d映射出来,自行管理。最后,因为log4j2在发送日志是是通过4560接口,所以启动docker的时候需要映射此接口。以上是我在对接elk的时候遇到的问题,再次记录下,更多的elk内容后续会继续探索。 ...

January 31, 2019 · 1 min · jiezi

Docker入门(一)用hello world入门docker

初识DockerDocker是什么? Docker 是一个开源的应用容器引擎,基于 Go 语言并遵从Apache2.0协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。 容器是完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。Docker与传统虚拟化的不同之处 Docker与传统虚拟化的不同之处在于:传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。Docker的优势 Docker的五大优势为:持续集成版本控制可移植性隔离性安全性关于其说明,具体可参考网址:http://dockone.io/article/389 。Docker的安装 本文以Mac系统为例,讲解Docker的安装,其它系统的安装方式可参考:安装 Docker 。使用 Homebrew 安装 Homebrew 的 Cask 已经支持 Docker for Mac,因此可以很方便的使用 Homebrew Cask 来进行安装:brew cask install docker手动下载安装 如果需要手动下载,可以通过这个链接下载:https://download.docker.com/mac/stable/Docker.dmg 。如同 MacOS 其它软件一样,安装也非常简单,双击下载的 .dmg 文件,然后将鲸鱼图标拖拽到 Application 文件夹即可。启动Docker 从应用中找到 Docker 图标并点击,即可启动Docker,启动后在最上侧的菜单栏状态如下: 启动终端后,通过命令可以检查安装后的 Docker 版本:$ docker –versionDocker version 18.09.1, build 4c52b90 接着我们运行docker中的hello world来验证docker是否安装且启动成功:Dockerhub账号注册 为了方便展示以及后续的docker使用,我们最好在DockerHub上注册一个自己的账号。 DockerHub,类似于代码管理的Github,可以简单高效地管理我们的docker项目。 DockerHub的注册地址为:https://hub.docker.com/signup,界面如下: 注册完DockerHub后,我们登录个人账号。点击Create Repository +按钮新建Docker仓库,名字为dockertest。如下:再点击Create按钮即可。创建后的dockertest项目如下: 至此,我们已经在DockerHub上创建了一个自己的账号,并且新建的一个dockertest仓库,尽管这个仓库里面没有任何东西。 下一步,我们往dockertest这个仓库里面装点什么。Docker使用实例:hello world 首先,我们需要用docker在本地打包一个docker镜像,然后对其打标签(tag),然后将其推送(push)至个人的dockerhub账号中的dockertest仓库,最后将该镜像拉下来,并运行。镜像打包新建mydocker文件夹$ tree mydockermydocker├── src│ └── test_docker.py└── test_docker.buildtest_docker.py代码:print “hello world from python!“print “this is from docker!“test_docker.build代码(利用Dockerfile打包镜像):FROM centos:7.2.1511#环境变量硬编码及时区ENV ENVIRONMENT DOCKER_PRODRUN cd / && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime# 拷贝并so加密代码COPY src /root/src# 工作目录WORKDIR /root/src/这里,我们不再过多讲述上述Dockerfile中的命令,有兴趣的同学可移步:http://blog.51cto.com/wutengf… , 笔者也会在后续的文章中给出Dockerfile命令的详细讲述。命令行打包镜像:cd ./mydockerdocker build -f test_docker.build -t hub.docker.com/dockertest:test.1111 .列出镜像 在打包完镜像后,我们可以列出docker的镜像,查看镜像的ID,方便后续操作。命令为docker images,如下:由此可知,我们新打包的docker镜像的ID为be236e996983。推送镜像打标签(tag)docker tag be236e996983 jclian91/dockertest:hello_world_test_19.01.31.1100镜像ID可通过docker images查看。登录dockerhub账号docker login再输入自己的账号、密码即可。推送(push)镜像docker push jclian91/dockertest:hello_world_test_19.01.31.1100所有命令的界面如下:此时,我们去dockerhub的dockertest仓库中去查看,发现已经上传一个镜像了,如下:运行镜像为了运行新上传的镜像,我们先删除本地打包的镜像:$ docker rmi -f be236e996983Untagged: jclian91/dockertest:hello_world_test_19.01.31.1100Untagged: jclian91/dockertest@sha256:c2ac02cb725a8256c2d752461133004cc05a6060390220b15a0aaefefc7c95e7Untagged: hub.docker.com/dockertest:test.1111Deleted: sha256:be236e996983339318796f588fd5acda1da5f942289a2559f948a4811d68428dDeleted: sha256:5c46baa463a1e86d0924c493bb0e12888fc6aaefdcaf128d8193406eb0ef4ed1Deleted: sha256:f76e87a3e84bf1a03e81dfdc53a569a7adce6cfc80bb56d7d2040e118e2848f7拉取(pull)新上传的镜像docker pull jclian91/dockertest:hello_world_test_19.01.31.1100运行(run)该镜像docker run -it be236e996983 bash参数说明:其中 -i:交互式操作, -t:终端,如存在-d参数,-d:后台运行。这样我们就能进入到这台用docker创建好的虚拟机内部了,我们在该Linux虚拟机内部运行命令,如下:[root@ca9070ce82e1 src]# lstest_docker.py[root@ca9070ce82e1 src]# python test_docker.py hello world from python!this is from docker!总结 hello world是所有编程语言的入门例子,在本文中,我们用docker自己创建了一个hello world的例子,用来展示docker的基本用法,希望能给初入门的同学一个明显直观的例子。 后续的文章将会更多地介绍docker方面的知识,欢迎大家关注,如有任何疑问,请在留言区留言。注意:本人现已开通微信公众号: Python爬虫与算法(微信号为:easy_web_scrape), 欢迎大家关注哦~~参考文献:Docker —— 从入门到实践: https://yeasy.gitbooks.io/doc...Docker基本介绍和操作:http://blog.51cto.com/wutengf…Docker 教程: http://www.runoob.com/docker/…《第一本Docker书 修订版》 詹姆斯·特恩布尔 人民邮电出版社 ...

January 31, 2019 · 1 min · jiezi

认识docker

一、Docker工作原理二、Docker容器和虚拟机对比三、镜像容器管理1、Docker关键组件2、Docker架构3、Docker内部组件镜像(Image)——一个特殊的文件系统Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)容器(Container)——镜像运行时的实体容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等仓库(Repository)——集中存放镜像文件的地方镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务Docker采用了C/S架构。客户端和服务端可以运行在一个机器上,也可以通过socket或者RESTful API 来进行通信。Docker Daemon: 一般在宿主机后台运行,等待接收客户端的消息Docker Client:则为客户提供一系列可执行的命令, 用户使用这些命令跟docker daemon交互Docker daemon:Docker daemmon是Docker架构中的主要用户接口。首先,它提供了API Server用于接收来自Docker client的请求,其后根据不同的请求分发给Docker daemon的不同模块执行相应的工作Image managerment:需要创建DOcker容器时,可通过镜像管理(image management)部分的distribution和registry模块从Docker registry中下载镜像,并通过镜像管理的image、reference和layer存储镜像的元数据,通过镜像存储驱动graphdriver将镜像文件存储于具体的文件系统中Network:当需要为Docker容器创建网络环境时,通过网络模块network调用libnetwork创建并配置Docker容器的网络环境Volume:当需要为容器创建数据卷volume时,则通过volume模块调用某个具体的volumedrive,来创建一个数据卷并负责后续的挂载操作Execdriver:当需要限制Docker容器运行资源或执行用户指令操作时,则通过execdrive来完成Libcontainer:是对cgroups和namespace的二次封装,execdrive是通过libcontainer来实现对容器的具体管理,包括利用UTS、IPC、PID、Network、Mount、User等namespace实现容器之间的资源隔离和利用cgroups实现对容器的资源限制.当运行容器的命令执行完毕后,一个实际的容器就处于运行状态,该容器具有独立的文件系统、相对安全且相互隔离的运行环境.1、用户是使用Docker Client与Docker Daemon建立通信,并发送请求给后者2、Engine执行Docker内部的一系列工作,每一项工作都是以一个Job的形式的存在。3、Job的运行过程中,当需要容器镜像时,则从Docker Registry中下载镜像,并通过镜像管理驱动graphdriver将下载镜像以Graph的形式存储;当需要为Docker创建网络环境时,通过网络管理驱动networkdriver创建并配置Docker容器网络环境;当需要限制Docker容器运行资源或执行用户指令等操作时,则通过execdriver来完成。libcontainer是一项独立的容器管理包,networkdriver以及execdriver都是通过libcontainer来实现具体对容器进行的操作。1、容器和虚拟机对比2、Docker的优势3、Docker的劣势4、Docker的应用场景Docker 的的优势:持续部署和测试发到产品发布的整个过程中使用相同的容器来确保没有任何差异或者人工干预。Docker可以保证测试环境、开发环境、生产环境的一致性。可移植性容器可以移动到任意一台Docker主机上,而不需要过多关注底层系统。弹性伸缩更快速配合K8S可以很容易的无状态应用的弹性伸缩,只需要改一个yml的数字即可。利用docker能在几秒钟之内启动大量的容器,这是虚拟机无法办到的,快速启动,秒级和分钟级的对比。资源利用率高由于docker不需要Hypervisor实现硬件资源虚拟化,docker容器和内核交互,几乎没有性能损耗,性能优于通过Hypervisor层与内核层的虚拟化。一台机器启动上前台容器也没问题。对硬件无要求不需要CPU支持虚拟化Docker 的的劣势资源隔离docker是利用cgroup实现资源隔离的,只能限制资源消耗的最大值,而不能隔绝其他应用程序占用自己的资源; docker属于进程之间的隔离,虚拟机可实现系统级别隔离;安全性问题一个用户拥有执行docker的权限,可以删除任何用户创建的容器。兼容性问题docker目前还在版本快速更新中,细节功能调整较大,一些核心的模块依赖于高版本的内核,存在兼容性的问题。1、Docker安装2、认识镜像和容器3、镜像容器管理什么是镜像?镜像是一个多层的联合只读的文件系统。什么是容器?容器是在镜像基础上加上读写层。容器即进程。构建镜像的过程?镜像->镜像+可写层+执行命令->commit为新的镜像(新的一层)->镜像+可写层+执行命令->commit为新的镜像(新的一层)->…典型文件系统启动 :一个典型的 Linux 文件系统由 bootfs 和 rootfs 两部分组成,bootfs(boot file system) 主要包含 bootloader 和 kernel,bootloader 主要用于引导加载 kernel,当 kernel 被加载到内存中后 bootfs 会被 umount 掉rootfs (root file system)包含的就是典型 Linux 系统中的/dev,/proc,/bin,/etc 等标准目录和文件加载过程:bootfs 时会先将 rootfs 设为 read-only,然后在系统自检之后将 rootfs 从 read-only 改为 read-write,Docker文件系统启动:Docker 在 bootfs 自检完毕之后并不会把 rootfs 的 read-only 改为 read-write,而是利用 union mount(UnionFS 的一种挂载机制)将 image 中的其他的 layer 加载到之前的 read-only 的 rootfs 层之上,每一层 layer 都是 rootfs 的结构,并且是read-only 的。所以,我们是无法修改一个已有镜像里面的 layer 的!只有当我们创建一个容器,也就是将 Docker 镜像进行实例化,系统会分配一层空的 read-write 的 rootfs ,用于保存我们做的修改DockerfileFROM centosENV TZ “Asia/Shanghai"ADD echo.sh /opt/echo.shRUN chmod +x /opt/echo.shCMD ["/opt/echo.sh”]docker build -t test -f Dockerfile . 镜像工作原理:如果运行中的容器修改一个已经存在的文件,那么会将该文件从下面的只读层复制到读写层,只读层的这个文件就会覆盖(隐藏),但还存在。如果删除一个文件,在最上层会被标记隐藏,实际只读层的文件还存在。这就实现了文件系统隔离,当删除容器后,读写层的数据将会删除,只读镜像不变。查看具体的挂载逻辑[root@centos7 l]# mount|grep overlayoverlay on /var/lib/docker/overlay2/56375ce93fd54484061ef08a48a7093905be680dd14754642970616127b30fca/merged type overlay (rw,relatime,seclabel,lowerdir=/var/lib/docker/overlay2/l/A6JYT4QIFZMKOPIGY675JWKS7F:/var/lib/docker/overlay2/l/4L4SUINS3DX6XPD5BL2J54JQDT,upperdir=/var/lib/docker/overlay2/56375ce93fd54484061ef08a48a7093905be680dd14754642970616127b30fca/diff,workdir=/var/lib/docker/overlay2/56375ce93fd54484061ef08a48a7093905be680dd14754642970616127b30fca/work)Overlay和overlay2的区别overlay: 只挂载一层,其他层通过最高层通过硬连接形式共享(增加了磁盘inode的负担)overlay2: 逐层挂载(最多128层)基础镜像的层信息docker pull centostree -L 2 /var/lib/docker/overlay2/构建后镜像的层信息cd layer_dockerfile/docker build -t centos:test -f ./Dockerfile .tree -L 2 /var/lib/docker/overlay2/每一层都包含了”该层独有的文件”以及”和其低层共享的数据的连接”,在Docker 1.10之前的版本中,目录的名字和镜像的UUID相同,而Docker 1.10后则采用了新的存储方式,可以看到目录名和下载镜像的UUID并不相同Diff存放挂载点的具体的文件内容Link对应l目录的链接源的名称Lower根没有lower,其它的lower指向的父层的链接L:”l“目录包含一些符号链接作为缩短的层标识符. 这些缩短的标识符用来避免挂载时超出页面大小的限制docker run -idt –name centos_con centos:test /bin/bashtree -L 2 /var/lib/docker/overlay2/多出的两层“……”为读写层,“…..-init”为初始层。初始层:初始层中大多是初始化容器环境时,与容器相关的环境信息,如容器主机名,主机host信息以及域名服务文件等。读写层:所有对容器做出的改变都记录在读写层Diff存放挂载点的具体的文件内容Link对应l目录的链接源的名称Lower根没有lower,其它的lower指向的父层的链接Merged如果是读写层会有一个MergedCommit:容器提交为镜像docker run -idt –name test centosTouch liweidocker commit 6de test2Create:创建容器但是不启动docker create –name nginx-con -p80:80 nginx:latestStart:启动容器docker start nginx-conStop:停止容器docker stop nginx-conKill:杀掉容器,和停止相比不友好docker kill nginx-conPause:暂停容器docker pause nginx-conUnpause:恢复暂停的容器docker unpause nginx-conRun:创建且启动容器docker run -idt –restart=always –name nginx_con -v /tmp/:/mnt -p 88:80 -e arg1=arg1 nginxDocker attach nginx_conCtrl+^p+^qCP:宿主机和容器之间copy文件docker cp docker_install.sh nginx_con:/optdocker exec nginx_con ls /optdocker cp nginx_con:/opt/docker_install.sh ./1.shExec:执行命令,也可附加到容器docker exec nginx_con ls /optAttach:附加到容器docker attach nginx_condocker exec -it nginx_con /bin/bashLogs:查看容器日志docker logs –f nginx_conInspect:查看元数据,可以查看镜像和容器docker inspect nginx_conPort:查看容器端口映射docker port nginx_conTop:查看容器中正在运行的进程docker top nginx_conPs:查看容器docker psdocker ps -adocker ps -aq查看正在运行的容器,加上-a查看所有容器(包含停止和暂停状态的容器)Rm:删除容器docker rm nginx_con 删除容器docker rm -f nginx_con 强行删除容器Export:导出容器docker pull busyboxdocker run -itd busyboxdocker export 983989307eef>busybox.tarImport:导入容器docker import busybox.tar busybox:1.3Save:导出镜像docker save busybox:1.3>busybox1.3.tarLoad:导入镜像docker load -i busybox1.3.tar Tag:镜像打标签docker tag busybox:1.3 192.168.199.160/test/busybox:latestBuild:从dockerfile构建镜像FROM centosENV TZ “Asia/Shanghai"ADD echo.sh /opt/echo.shRUN chmod +x /opt/echo.shCMD ["/opt/echo.sh”]docker build -t centos:test -f Dockerfile . Pull:从 registry拉取镜像docker pull nginx 从dockerhub上拉取docker pull 192.168.199.160/test/nginx:latest 从内网harbor上拉取Push:推送镜像到仓库[第一次需要输入密码,后续不需要了]docker login 192.168.199.160adminHarbor12345docker push 192.168.199.160/test/busybox:latestInfo、version、eventsdocker info 查看docker相关信息信息Docker version 查看docker相关版本信息Docker events 查看docker事件【注】1、创建容器后防火墙不要再动2、cmd会被覆盖的问题,需要注意,可能会导致/bin/bash 把启动命令覆盖了,启动不了的问题例如docker run -idt –restart=always –name nginx_con -v /tmp/:/mnt -p 88:80 -e arg1=arg1 nginx /bin/bash3、cmd的命令都会在挂在后执行。 ...

January 30, 2019 · 2 min · jiezi

Docker镜像细节

前言只有光头才能变强。文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y回顾前面:为什么需要Docker?Docker入门为什么可以这么简单?前面两篇已经讲解了为什么需要Docker这项技术,以及解释了Docker的基本概念/术语,使用Docker成功运行Tomcat~在上篇也同样留下一个问题:我们知道Tomcat运行起来需要Java的支持,那么我们在DockerHub拉取下来的Tomcat镜像是不是也有Java环境呢?所以,这篇主要来讲讲Docker镜像相关的知识点!一、简单了解DockerfileDockerfile是用来构建Docker镜像的文件,是由一系列命令和参数构成的脚本。简单来说:Dockerfile是镜像的源码。上一篇我们pull了一份Tomcat的镜像,我们也可以去看看它的Dockerfile长的什么样:我们随便点进去一个看一下:我们在Dockerfile的第一行就可以发现FROM openjdk:8-jre,所以可以确定的是:在DockerHub拉取下来的Tomcat镜像一定有Java环境!在这里我们先不说如何阅读/编写Dockerfile文件,先了解到Dockerfile是镜像的源码即可简单来说:通过Dockerfile文件可以知道我们拉取下来的镜像究竟是怎么构建的。二、解除镜像的疑惑我们知道Docker Hub有很多常用的镜像,比如说Centos。我们去pull一个下来看看Docker中的Centos长啥样:我们可以发现的是:Tomcat的SIZE竟然比Centos还要大!但按我们常规的想法,Centos的镜像可能是3或4GB(现在200M),Tomcat的镜像可能就200M(现在400M)。这是为什么呢??如果我们在pull的时候观察得比较仔细的话,可以发现pull会拉下很多层镜像:完全pull下来的之后,我们如果使用docker images只能查看到最终的镜像:如果我们使用docker images -a 命令的话,可以把中间层镜像都查出来:理想效果:(在镜像列表里边除了tomcat和centos应该还夹杂着名为<none>的镜像)遗憾的是:博主一直没测出效果来,也就是我的镜像列表里没有<none>的镜像(怀疑是版本的问题,我的版本是Docker版本是18.09.1,Centos的版本是CentOS Linux release 7.3.1611 。如果知道具体原因的不妨在评论区下告诉我)Emmm,我们可以使用history命令来看看,可以发现Tomcat包含很多个镜像层还可以发现一点:Dockerfile有多少条命令,那就有多少个镜像层(不信你数数)说了那么多,就想让大家知道:我们拉取下来的镜像实际上是由很多中间层镜像组成的。再结合我们上一篇Docker入门为什么可以这么简单?,在解决Tomcat启动时一直卡住问题时,能够发现的是,我们可以使用cd, ls等基础命令,但无法使用vi命令(需要我自己去下载)。我们可以推断出,pull下来的镜像由很多层镜像组成【这些镜像都是精简过的(甚至连vi命令都不支持)】因为Tomcat镜像要的基础环境比Centos镜像要多,所以Tomcat镜像的SIZE比Centos要大三、Docker镜像的特点关于Docker镜像,有以下特点:由Dockerfile生成呈现层级结构每层镜像包含:镜像文件以及镜像json元数据信息图像来源:http://open.daocloud.io/allen-tan-docker-xi-lie-zhi-shen-ke-li-jie-docker-jing-xiang-da-xiao/3.1镜像呈现层级结构联合文件系统(UnionFS)是实现Docker镜像的技术基础。在Docker中一般使用是AUFS(Another Union File System或Advanced Multilayered Unification File System)【具体还是得看宿主机用的什么系统】。在搜索中文资料的时候,常常会发现有类似的解释:“AUFS是一种 Union FS, 简单来说就是“支持将不同目录挂载到同一个虚拟文件系统下的文件系统”, AUFS支持为每一个成员目录设定只读(Rreadonly)、读写(Readwrite)和写(Whiteout-able)权限。Union FS 可以将一个Readonly的Branch和一个Writeable的Branch联合在一起挂载在同一个文件系统下”。看得我一头雾水….后来去官方文档介绍AUFS:AUFS is a union filesystem, which means that it layers multiple directories on a single Linux host and presents them as a single directory. These directories are called branches in AUFS terminology, and layers in Docker terminology说白了,还是可以理解成:Docker的镜像的基础是联合文件系统,它支持将文件系统中的修改信息作为一次提交,并层层叠加,外界看到的是最外层的镜像。(比如外界只看到Tomcat镜像,而中间叠加了很多层镜像)(这里只是拿AUFS说明,Docker实际上支持很多存储驱动,比如还有devicemapper,overlay2(Ubuntu的14.04.4或更高版本,16.04或更高版本), overlay,zfshttps://docs.docker-cn.com/engine/userguide/storagedriver/selectadriver/3.1.1镜像继承(共享)Docker镜像可以通过分层来进行继承。例如,hello-world的Dockerfile镜像FROM scratch镜像,scratch在Docker中是一个基础镜像FROM scratchCOPY hello /CMD ["/hello"]Centos的Dockerfile镜像也是FROM scratch镜像:FROM scratchADD centos-7-docker.tar.xz /LABEL org.label-schema.schema-version=“1.0” \ org.label-schema.name=“CentOS Base Image” \ org.label-schema.vendor=“CentOS” \ org.label-schema.license=“GPLv2” \ org.label-schema.build-date=“20181205"CMD ["/bin/bash”]那么Centos镜像和hello-world共享同一个基础镜像层scratch,提高了存储效率。再说个例子,比如我们有一个Centos镜像,这个镜像大小是202M。然后,我们基于Centos镜像手动往里边添加一个Tomcat(假设这个Tomcat的大小是300M),生成一个镜像,总大小就是502M了。如果仅仅是单纯的累加这两个镜像的大小:202M+502M=704M,但是由于镜像复用的存在,实际占用的磁盘空间大小是:202M+300M=502MAUFS uses the Copy-on-Write (CoW) strategy to maximize storage efficiency and minimize overhead。如果想要了解COW,不妨阅读我之前写过的文章:COW奶牛!Copy On Write机制了解一下CopyOnWriteArrayList你都不知道,怎么拿offer?3.2json文件Docker每一层镜像的json文件,都扮演着一个非常重要的角色,其主要的作用如下:记录 Docker 镜像中与容器动态信息相关的内容记录父子 Docker 镜像之间真实的差异关系弥补 Docker 镜像内容的完整性与动态内容的缺失Docker镜像的json文件可以认为是镜像的元数据信息最后今天简单地聊了一下Docker镜像的一些细节,但没去深入了解,想要继续深入的同学还得通过官方文档等途径去学习哈。参考资料:Allen 谈 Dockerhttp://open.daocloud.io/tag/allen-tan-docker/官方文档介绍AUFShttps://docs.docker-cn.com/engine/userguide/storagedriver/aufs-driver/#example-image-and-container-on-disk-constructsDocker核心实现技术(命名空间&控制组&联合文件系统&Linux网络虚拟化支持)https://www.cnblogs.com/wade-luffy/p/6589254.html#_label3Docker联合文件系统Union File Systemhttp://www.dockerinfo.net/1753.html乐于输出干货的Java技术公众号:Java3y。公众号内有200多篇原创技术文章、海量视频资源、精美脑图,不妨来关注一下!觉得我的文章写得不错,不妨点一下赞! ...

January 29, 2019 · 1 min · jiezi

开发函数计算的正确姿势——网页截图服务

前言首先介绍下在本文出现的几个比较重要的概念:函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息参考。Fun: Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作。Fun 的更多文档参考。fun install: fun install 是 fun 工具的一个子命令,用于安装 pip 和 apt 依赖,提供了命令行接口和 fun.yml 描述文件两种形式。备注: 本文介绍的技巧需要 Fun 版本大于等于 2.9.3。依赖工具本项目是在 MacOS 下开发的,涉及到的工具是平台无关的,对于 Linux 和 Windows 桌面系统应该也同样适用。在开始本例之前请确保如下工具已经正确的安装,更新到最新版本,并进行正确的配置。DockerFunFcliFun 和 Fcli 工具依赖于 docker 来模拟本地环境。对于 MacOS 用户可以使用 homebrew 进行安装:brew cask install dockerbrew tap vangie/formulabrew install funbrew install fcliWindows 和 Linux 用户安装请参考:https://github.com/aliyun/fun/blob/master/docs/usage/installation.mdhttps://github.com/aliyun/fcli/releases安装好后,记得先执行 fun config 初始化一下配置。初始化使用 fun init 命令可以快捷的将本模板项目初始化到本地。$ fun init vangie/puppeteer-example? Please input oss bucket to upload chrome shell? chrome-headless? Please select a region? cn-hangzhou? Please input oss accessKeyId for upload? xxxxxxxxxxxKbBS? Please input oss accessKeySecret for upload? xxxxxxxxxxxx5ZgM上面会提示输入一个 OSS 的 BUCKET,注意 OSS Bucket 是全球唯一的,上面的 chrome-headless 已经被占用了,请换一个新的名称或者一个已经创建好的(已经创建好的,请确保 region 一致)。然后选择一个 OSS 的 Region,请保持和部署函数计算 Region 一致输入一个具备 OSS 写权限的秘钥。安装依赖$ fun installskip pulling image aliyunfc/runtime-nodejs8:build-1.2.0…Task => [UNNAMED] => apt-get update (if need) => apt-get install -y -d -o=dir::cache=/code/.fun/tmp libnss3 => bash -c ‘for f in $(ls /code/.fun/tmp/archives/*.deb); do dpkg -x $f /code/.fun/root; done;’ => bash -c ‘rm -rf /code/.fun/tmp/archives’Task => [UNNAMED] => bash -c ‘curl -L https://github.com/muxiangqiu/puppeteer-fc-starter-kit/raw/master/chrome/headless_shell.tar.gz –output headless_shell.tar.gz’…fun install 会执行 fun.yml 文件里的任务,这些任务会:安装 puppeteer 依赖的 .so 文件;将 puppeteer 依赖的 chrome headless 二进制文件上传到 OSS;安装 npm 依赖。部署$ fun deployusing region: cn-shanghaiusing accountId: ***********4733using accessKeyId: ***********KbBSusing timeout: 60Waiting for service puppeteer to be deployed… Waiting for function html2png to be deployed… Waiting for packaging function html2png code… package function html2png code done function html2png deploy successservice puppeteer deploy success执行$ fcli function invoke -s puppeteer -f html2pngThe screenshot has been uploaded to http://chrome-headless.oss-cn-shanghai.aliyuncs.com/screenshot.png打开上面的返回链接,看到截取出来的是全屏滚动的长图,考虑的篇幅下面只截取了部分:如果想换一个网址,可以使用如下命令格式fcli function invoke -s puppeteer -f html2png –event-str ‘http://www.alibaba.com’调试如果需要在本地调试代码,可以使用如下命令fun local invoke html2png <<<‘http://www.alibaba.com’参考阅读三分钟学会如何在函数计算中使用 puppeteer本文作者:倚贤阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 29, 2019 · 2 min · jiezi

macos 本地安装部署k8s

1.开启docker自带k8s开启完成之后右下角会回显示Kubernetes is Runnign1.2 查看安装的镜像docker images或 docker image ls1.3 查看安装的容器docker container ls –format “table{{.Names}}\t{{.Image }}\t{{.Command}}“2.部署k8s dashboard kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yamlps:如遇到yaml失效请访问这里2.1 开启代理kubectl proxy然后访问地址 http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/ps:会报错2.2 解决报错问题kubectl -n kube-system edit service kubernetes-dashboard将之前的修改成图片箭头标注的即可然后在访问之前的地址2.3 使用NodePort的方式访问dashboard查看暴露的端口:kubectl -n kube-system get service kubernetes-dashboard然后访问https://localhost:31950/

January 29, 2019 · 1 min · jiezi

Dockerfile部署web应用

Dockerfile部署web应用在记录一次web测试这一篇博文中,我把此次测试所产出的文件都放在了自己的服务器上,并提供了下载接口,正好最近在学习docker,今天我把这个web应用用docker跑起来。编写DockerfileFROM python # 拉取python基础镜像COPY . /server/ # 将当前项目文件加下的所有文件拷贝到docker容器中的server文件夹WORKDIR /server # 容器内切换到server目录RUN pip install flask # 在镜像安装flaskRUN pip install gunicorn # 在镜像中安装gunicornEXPOSE 8081 # 暴露出8081端口CMD gunicorn -b 0.0.0.0:9998 -w 4 server:app # 最终运行服务器的命令编译docker镜像docker build -t test:v1 # test:v1 –> 自定义镜像名:标签运行容器docker run -d –name test_server -p 8081:8081 test:v1访问访问ip:port即可

January 27, 2019 · 1 min · jiezi

cockroachDB部署使用

容器部署容器部署拉取镜像docker pull cockroachdb/cockroach:v2.0.1创建网络docker network create -d bridge roachnet查看网络(不清楚为什么没有docker0,或者docker0和eb330591e579的关系)# docker network ls NETWORK ID NAME DRIVER SCOPEeb330591e579 bridge bridge local 95d2d6473765 host host local 8edade4ef56f none null local 7e50fa75c534 roachnet bridge local 运行容器docker run -d –name=roach1 –hostname=roach1 –net=roachnet -p 26257:26257 -p 8080:8080 -v “${PWD}/cockroach-data/roach1:/cockroach/cockroach-data” cockroachdb/cockroach:v2.0.1 start –insecuredocker run -d –name=roach2 –hostname=roach2 –net=roachnet -v “${PWD}/cockroach-data/roach2:/cockroach/cockroach-data” cockroachdb/cockroach:v2.0.1 start –insecure –join=roach1docker run -d –name=roach3 –hostname=roach3 –net=roachnet -v “${PWD}/cockroach-data/roach3:/cockroach/cockroach-data” cockroachdb/cockroach:v2.0.1 start –insecure –join=roach1验证集群 bug 了[root@log-test cockroach-data]# docker exec -it roach1 ./cockroach sql –insecure# Welcome to the cockroach SQL interface.# All statements must be terminated by a semicolon.# To exit: CTRL + D.## Server version: CockroachDB CCL v2.0.1 (x86_64-unknown-linux-gnu, built 2018/04/23 18:39:21, go1.10) (same version as client)# Cluster ID: 0721d2db-a3a1-4a87-96f2-7b35ee4a4b86fatal error: unexpected signal during runtime execution[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1b0abb7]runtime stack:runtime.throw(0x2485b32, 0x2a) /usr/local/go/src/runtime/panic.go:619 +0x81runtime.sigpanic() /usr/local/go/src/runtime/signal_unix.go:372 +0x28egoroutine 1 [syscall]:non-Go function pc=0x1b0abb7non-Go function pc=0x1b0ad81non-Go function pc=0x1b0a344non-Go function pc=0x72e5afruntime.cgocall(0x1b0a300, 0xc420657820, 0x2480f4f) /usr/local/go/src/runtime/cgocall.go:128 +0x64 fp=0xc4206577d8 sp=0xc4206577a0 pc=0x6d60c4github.com/cockroachdb/cockroach/vendor/github.com/knz/go-libedit/unix._C2func_go_libedit_init(0x0, 0x7f5986e59210, 0xc4200ba438, 0x7f5986e54000, 0x7f5986e54280, 0x7f5986e54500, 0x0, 0x0, 0x0, 0x0) _cgo_gotypes.go:200 +0x5c fp=0xc420657820 sp=0xc4206577d8 pc=0x1aa10acgithub.com/cockroachdb/cockroach/vendor/github.com/knz/go-libedit/unix.InitFiles.func3(0x0, 0x7f5986e59210, 0xc4200ba438, 0x7f5986e54000, 0x7f5986e54280, 0x7f5986e54500, 0x0, 0xc420657930, 0x16b03db, 0x274de80) /go/src/github.com/cockroachdb/cockroach/vendor/github.com/knz/go-libedit/unix/editline_unix.go:93 +0x19e fp=0xc420657880 sp=0xc420657820 pc=0x1aa425egithub.com/cockroachdb/cockroach/vendor/github.com/knz/go-libedit/unix.InitFiles(0x243bd49, 0x9, 0x1, 0xc4200ba000, 0xc4200ba008, 0xc4200ba048, 0x0, 0x0, 0x0) /go/src/github.com/cockroachdb/cockroach/vendor/github.com/knz/go-libedit/unix/editline_unix.go:93 +0x482 fp=0xc4206579c8 sp=0xc420657880 pc=0x1aa2462github.com/cockroachdb/cockroach/vendor/github.com/knz/go-libedit.InitFiles(0x243bd49, 0x9, 0x6ff101, 0xc4200ba000, 0xc4200ba008, 0xc4200ba048, 0xc420000180, 0x2568c10, 0xc42003ca80) /go/src/github.com/cockroachdb/cockroach/vendor/github.com/knz/go-libedit/editline_unix.go:15 +0x63 fp=0xc420657a20 sp=0xc4206579c8 pc=0x1aa5dd3github.com/cockroachdb/cockroach/pkg/cli.runInteractive(0xc420471900, 0x0, 0x0) /go/src/github.com/cockroachdb/cockroach/pkg/cli/sql.go:1062 +0x41a fp=0xc420657bf8 sp=0xc420657a20 pc=0x1aceb3agithub.com/cockroachdb/cockroach/pkg/cli.runTerm(0x3658dc0, 0xc420044400, 0x0, 0x1, 0x0, 0x0) /go/src/github.com/cockroachdb/cockroach/pkg/cli/sql.go:1179 +0x159 fp=0xc420657c50 sp=0xc420657bf8 pc=0x1acf349github.com/cockroachdb/cockroach/pkg/cli.MaybeDecorateGRPCError.func1(0x3658dc0, 0xc420044400, 0x0, 0x1, 0x0, 0x0) /go/src/github.com/cockroachdb/cockroach/pkg/cli/error.go:39 +0x5a fp=0xc420657cc8 sp=0xc420657c50 pc=0x1ae4c9agithub.com/cockroachdb/cockroach/vendor/github.com/spf13/cobra.(Command).execute(0x3658dc0, 0xc420044390, 0x1, 0x1, 0x3658dc0, 0xc420044390) /go/src/github.com/cockroachdb/cockroach/vendor/github.com/spf13/cobra/command.go:698 +0x46d fp=0xc420657d70 sp=0xc420657cc8 pc=0x1a727cdgithub.com/cockroachdb/cockroach/vendor/github.com/spf13/cobra.(Command).ExecuteC(0x3655460, 0x6, 0x0, 0xc420657ee8) /go/src/github.com/cockroachdb/cockroach/vendor/github.com/spf13/cobra/command.go:783 +0x2e4 fp=0xc420657ea0 sp=0xc420657d70 pc=0x1a72f44github.com/cockroachdb/cockroach/vendor/github.com/spf13/cobra.(Command).Execute(0x3655460, 0x18, 0xc420657f00) /go/src/github.com/cockroachdb/cockroach/vendor/github.com/spf13/cobra/command.go:736 +0x2b fp=0xc420657ed0 sp=0xc420657ea0 pc=0x1a72c3bgithub.com/cockroachdb/cockroach/pkg/cli.Run(0xc4200a4160, 0x2, 0x2, 0xc4200b6010, 0xc420558000) /go/src/github.com/cockroachdb/cockroach/pkg/cli/cli.go:156 +0x6d fp=0xc420657ef8 sp=0xc420657ed0 pc=0x1aad83dgithub.com/cockroachdb/cockroach/pkg/cli.Main() /go/src/github.com/cockroachdb/cockroach/pkg/cli/cli.go:51 +0x15d fp=0xc420657f78 sp=0xc420657ef8 pc=0x1aad3cdmain.main() /go/src/github.com/cockroachdb/cockroach/main.go:27 +0x20 fp=0xc420657f88 sp=0xc420657f78 pc=0x1b00f20runtime.main() /usr/local/go/src/runtime/proc.go:198 +0x212 fp=0xc420657fe0 sp=0xc420657f88 pc=0x701562runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc420657fe8 sp=0xc420657fe0 pc=0x72f8e1goroutine 20 [syscall]:os/signal.signal_recv(0x0) /usr/local/go/src/runtime/sigqueue.go:139 +0xa6os/signal.loop() /usr/local/go/src/os/signal/signal_unix.go:22 +0x22created by os/signal.init.0 /usr/local/go/src/os/signal/signal_unix.go:28 +0x41goroutine 5 [chan receive]:github.com/cockroachdb/cockroach/pkg/util/log.flushDaemon() /go/src/github.com/cockroachdb/cockroach/pkg/util/log/clog.go:1131 +0xf1created by github.com/cockroachdb/cockroach/pkg/util/log.init.0 /go/src/github.com/cockroachdb/cockroach/pkg/util/log/clog.go:592 +0x110goroutine 6 [chan receive]:github.com/cockroachdb/cockroach/pkg/util/log.signalFlusher() /go/src/github.com/cockroachdb/cockroach/pkg/util/log/clog.go:601 +0x105created by github.com/cockroachdb/cockroach/pkg/util/log.init.0 /go/src/github.com/cockroachdb/cockroach/pkg/util/log/clog.go:593 +0x128goroutine 22 [select, locked to thread]:runtime.gopark(0x2569e48, 0x0, 0x2436d0b, 0x6, 0x18, 0x1) /usr/local/go/src/runtime/proc.go:291 +0x11aruntime.selectgo(0xc420476f50, 0xc4204884e0) /usr/local/go/src/runtime/select.go:392 +0xe50runtime.ensureSigM.func1() /usr/local/go/src/runtime/signal_unix.go:549 +0x1f4runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:2361 +0x1二进制文件部署部署下载wget -qO- https://binaries.cockroachdb…. | tar xvz拷贝cp -i cockroach-v2.0.1.linux-amd64/cockroach /usr/local/bin授权chmod a+x /usr/local/bin/cockroach部署# cockroach start –insecure –host=0.0.0.0 –http-host=0.0.0.0 –http-port=8081 –port=26257 WARNING: RUNNING IN INSECURE MODE! * - Your cluster is open for any client that can access localhost.* - Any user, even root, can log in without providing a password.* - Any user, connecting as root, can read or write any data in your cluster.* - There is no network encryption nor authentication, and thus no confidentiality.* * Check out how to secure your cluster: https://www.cockroachlabs.com/docs/v2.0/secure-a-cluster.html*CockroachDB node starting at 2018-04-28 08:22:34.397265926 +0000 UTC (took 0.7s)build: CCL v2.0.1 @ 2018/04/23 18:39:21 (go1.10)admin: http://0.0.0.0:8081sql: postgresql://root@localhost:26257?sslmode=disablelogs: /home/msxu/cockroach-v2.0.1.linux-amd64/cockroach-data/logstemp dir: /home/msxu/cockroach-v2.0.1.linux-amd64/cockroach-data/cockroach-temp258550286external I/O path: /home/msxu/cockroach-v2.0.1.linux-amd64/cockroach-data/externstore[0]: path=/home/msxu/cockroach-v2.0.1.linux-amd64/cockroach-datastatus: restarted pre-existing nodeclusterID: 0fe058a5-9f6b-47c0-a061-3c043d7d7100nodeID: 1helm 部署git clone https://github.com/helm/chart...https://github.com/kubernetes...helm install –name ckdb01 http://172.16.59.153:10806/charts/cockroachdb-1.1.0.tgz –set Image=172.16.59.153/devops/cockroach –set StorageClass=csi-lvmNOTES:CockroachDB can be accessed via port 26257 (or whatever you set the GrpcPortvalue to) at the following DNS name from within your cluster:ckdb02-public.default.svc.cluster.localBecause CockroachDB supports the PostgreSQL wire protocol, you can connect tothe cluster using any available PostgreSQL client.For example, you can open up a SQL shell to the cluster by running: kubectl run -it –rm cockroach-client \ –image=cockroachdb/cockroach \ –restart=Never \ –command – ./cockroach sql –insecure –host ckdb02-cockroachdb-public.defaultFrom there, you can interact with the SQL shell as you would any other SQL shell,confident that any data you write will be safe and available even if parts ofyour cluster fail.Finally, to open up the CockroachDB admin UI, you can port-forward from yourlocal machine into one of the instances in the cluster: kubectl port-forward ckdb02-cockroachdb-0 8080Then you can access the admin UI at http://localhost:8080/ in your web browser.For more information on using CockroachDB, please see the project’s docs athttps://www.cockroachlabs.com/docs/kubectl run -it –rm cockroach-client \ –image=172.16.59.153/devops/cockroach \ –restart=Never \ –command – ./cockroach sql –insecure –host ckdb02-cockroachdb-public.defaultkubectl run -it –rm cockroach-client \ –image=172.16.59.153/devops/cockroach:v2.0.0 \ –restart=Never \ –command – ./cockroach sql –insecure –host ckdb02-cockroachdb-public.defaultcockroachDB 操作进入cockroach sql –insecure显示所有库show databases;选择库:use 库名;显示数据库下的所有表:show tables;查看表基本结构语句DESCRIBEDESCRIBE 表名SHOW CREATE TABLE 表名实际中我一般使用DESC 表名来查看表的结构,我们可以查出各字段的字段名,数据类型,完整性约束条件。这种查询是用表格来显示表结构的,所以看起来比较漂亮,但是查出来的内容不是太多;使用SHOW CREATE TABLE 表名来查看表的结构,除了查出上面的信息之外,还可以查出表的存储引擎(ENGINE),自增的当前值,字符编码等信息。可以用G来结尾,使得结果容易阅读DELETE 语句用于删除表中的行。DELETE FROM table_nameWHERE some_column=some_value;delete from tmptables where 1=1lamp 请注意 SQL DELETE 语句中的 WHERE 子句!WHERE 子句规定哪条记录或者哪些记录需要删除。如果您省略了 WHERE 子句,所有的记录都将被删除! ...

January 26, 2019 · 4 min · jiezi

单实例mysql.yaml kubernetes

借助于容器和k8s管理平台,mysql的部署和使用都很方便,能满足平时开发测试环境使用client集群内:mysql -h mysql.default.svc.cluster.local -P3306 -uroot -p mysqlimagepercona:5.7.22yamlapiVersion: apps/v1kind: Deploymentmetadata: name: mysqlspec: replicas: 1 selector: matchLabels: name: mysql template: metadata: labels: name: mysql spec: containers: - name: mysql image: percona:5.7.22 imagePullPolicy: Always ports: - containerPort: 3306 resources: limits: memory: “500Mi” cpu: “500m” requests: memory: “500Mi” cpu: “250m” env: - name: MYSQL_ROOT_PASSWORD value: “mysql” volumeMounts: - name: mysql-pvc mountPath: /var/lib/mysql volumes: - name: mysql-pvc persistentVolumeClaim: claimName: mysql—kind: ServiceapiVersion: v1metadata: name: mysqlspec: type: ClusterIP ports: - name: mysql port: 3306 targetPort: 3306 protocol: TCP selector: name: mysql—apiVersion: v1kind: PersistentVolumeClaimmetadata: name: mysqlspec: accessModes: - ReadWriteMany resources: requests: storage: “5Gi” volumeName: storageClassName: glusterfs ...

January 26, 2019 · 1 min · jiezi

树莓派安装Docker

因为树莓派是ARM架构的,所以Docker的安装和使用也都有不同。需要讲的内容比较多,这里单挑出来。树莓派是基于ARM架构的,和PC不同。所以即使树莓派上能做一些docker镜像,也不能在别的PC上运行。反过来别的PC上的docker镜像,也不能在树莓派上运行。如果需要找树莓派专用的镜像,那就在Dockerhub上搜索ARM或Rpi相关就能找到了。有一个叫Hypriot的仓库制作了非常多树莓派专用docker,可以参考下。树莓派参考:Get Docker CE for Debian参考:My home server powered by Pi and Docker树莓派安装Docker,最难的在于正确的选择源和添加GPG-key,才能找到版本适合的docker并下载。这个过程是非常繁琐且很难有统一方案的。官方版一键安装脚本注意:官方的一键安装脚本很多人说不再支持了。但是目前位置,其实还是能支持的。参考:The easy way to set up Docker on a Raspberry Pi开始执行之前,先说明:我之前很多次都不成功,找了很多相关解决方案都不行。直到。。。直到我先sudo apt-get update并且最最最重要的是sudo apt-get upgrade,之后才行。其实在upgrade时候就能看到,更新了很多系统依赖包,这也就解决了之前docker安装不成功的一切毛病了。upgrade完成后,就开始正式安装了:需要用到一个shell脚本,get.docker.com,整个网站就这一个脚本。下载并执行:$ curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh完成后,会显示:然后运行hello world试试:$ sudo docker run hello-world然后显示:手动安装准备工作:#安装SSL相关,让apt通过HTTPS下载:sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common# 添加docker的GPG keycurl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -#检查key是否相符(9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88)sudo apt-key fingerprint 0EBFCD88#添加docker的apt下载源sudo echo “\ndeb-src [arch=amd64] https://download.docker.com/linux/debian wheezy stable\n” >> /etc/apt/sources.list#更新源sudo apt-get update安装docker:$ sudo apt-get install docker-ce无需sudo执行docker为了每次执行docker不需要总是输入sudo,我们需要为docker创建一个用户组,并授予权限才行:# 创建docker用户组sudo groupadd docker# 把当前用户加入到docker用户组sudo gpasswd -a $USER docker# 更新当前用户组变动(就不用退出并重新登录了)newgrp docker安装docker-compose可以通过把docker compose当作一个docker的container下载并运行:docker run \ -v /var/run/docker.sock:/var/run/docker.sock \ -v “$PWD:/rootfs/$PWD” \ -w="/rootfs/$PWD" \ docker/compose:1.13.0 up# 设置alias快捷键(~/.zshrc或~/.bash_profile)alias docker-compose="’"‘docker run \ -v /var/run/docker.sock:/var/run/docker.sock \ -v “$PWD:/rootfs/$PWD” \ -w="/rootfs/$PWD" \ docker/compose:1.13.0’"’“常见错误问题Python: No module name lsb_release先检查本机是否已经安装了lsb_release,或者重新安装一遍:$ sudo apt-get install lsb-release如果还是这个问题,那么就检查Python版本。如果是python3,那么很可能是版本不够,因为lsb_release需要最少python3.5。解决这个问题,就把默认python设置回python2就好了。就是个ln建立快捷方式都事:# 备份(python具体的位置根据自己情况定)$ sudo mv /usr/bin/python /usr/bin/python_bak# 更换$ sudo ln -s /usr/bin/python2.7 /usr/bin/python然后再试一下$ lsb_release -cs看看有没有显示jessie无法添加源 add-apt-repository 报错找不到相关源 ...

January 26, 2019 · 1 min · jiezi

进入docker的世界

最近学习Machine Learning发现好多人都用docker,之前一直听说但是感觉和自己无关。但是现在发现原来docker是个这么方便的东西,可以跨平台(不分什么版本的linux,甚至mac和windows也行)运行。所以这里开一篇来记录学习感受。参考:Docker 完全指南参考: Gitbook - Docker — 从入门到实践不提那些难懂的术语,大白话就是:一个Docker就是一个Linux的Live CD系统,跟USB系统一样,有完整的系统文件目录和程序。我们可以在这个与外界隔离的便携系统里随便读写操作,只是每次进入它时候,都会恢复最开始的样子,像什么事都没发生一样。我们可以像定制Live CD或WinPE一样,定制这个小系统里面默认装什么软件。一旦定制好了,就是不可更改的,非常稳定。理解Docker的逻辑一开始发现很乱很难理解,觉得所有人都把它说的太复杂了。直到后来发现,其实它的运行逻辑很简单。实际上,可以把Docker看成是给电脑安装Linux系统时的Live CD,或者是给Windows用USB安装系统时的WinPE。这样会方便理解一点。回想下自己在给PC或是虚拟机上安装Linux系统时,都会有个Live CD选项。也是就是你可以什么都不安装,直接进入系统,所有的工具都能用,所有的软件都能安装,所有的配置也可以改。只不过你重启过后,一切修改的地方都恢复原样了。每篇攻略都会提到这三个基本概念:镜像 Image相当于一个系统光盘的ISO镜像文件,是只读的。你可以直接进入image中各种操作没有障碍,感觉就像进入_Live CD_系统了。只是所有操作都会在退出时消失,下次进image时候还是初始的样子。容器 Container就像给"ISO文件"加了一层可读写的外衣,所有的变动都会保存在Container里,而image还是image,不会变。就像你可以随便换衣服,但是身体不会变。仓库 Repo一般指的Dockerhub,就是一个像Github的网站,只不过不是收集代码,而是收集各种image镜像。你可以随意上传下载各大厂商或个人制作的镜像。安装DockerDocker分CE和EE两个版本,一个社区公开免费,一个商业付费。参考官方:About Docker CEUbuntu上安装Docker参考官方安装步骤:Get Docker CE for Ubuntu准备工作:#安装SSL相关,让apt通过HTTPS下载:sudo apt-get install apt-transport-https ca-certificates curl software-properties-common# 添加docker的GPG keycurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -#检查key是否相符(9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88)sudo apt-key fingerprint 0EBFCD88#添加docker的apt下载源sudo add-apt-repository “deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable”#更新源sudo apt-get update安装docker:$ sudo apt-get install docker-ce卸载Docker:$ sudo apt-get remove docker docker-engine docker.ioMac上安装Docker直接下载app:树莓派上安装Docker树莓派是基于ARM架构的,和PC不同。所以即使树莓派上能做一些docker镜像,也不能在别的PC上运行。反过来别的PC上的docker镜像,也不能在树莓派上运行。如果需要找树莓派专用的镜像,那就在Dockerhub上搜索ARM或Rpi相关就能找到了。有一个叫Hypriot的仓库制作了非常多树莓派专用docker,可以参考下。树莓派安装Docker,最难的在于正确的选择源和添加GPG-key,才能找到版本适合的docker并下载。这个过程是非常繁琐且很难有统一方案的。另外:官方的一键安装版本已经失效了。必须手动操作。参考另一篇笔记:树莓派安装Docker运行Docker[站外图片上传中…(image-e81557-1548159126389)]从Image镜像创建一个Container容器:# 新建一个Container容器(如果本地有image则直接从它创建,如果没有则从网上下载)# 进入docke的shell -t,即进入虚拟的一个系统,有自己的/root文件系统结构$ docker run -it <repo>:<tag> <CMD>#如:$ docker run -it jekyll/jekyll:latest bash# 为container指定名称(而不是只用ID来引用)$ docker run -it –name <NAME> <image-ID>查看已有的:# 查看已有的images$ docker images# 查看已创建的containers$ docker container ls -a运行一个已有的Container:# 先启动container$ docker container <ID> start# 运行(挂载)container,挂载后自动进入容器里的shell$ docker attach <ID># 或者一句话完成(–attach)$ docker start -a<ID> 删除已有的:# 删除image$ docker rm <Image-ID># 删除container$ docker rm <Container-ID>无需sudo执行docker为了每次执行docker不需要总是输入sudo,我们需要为docker创建一个用户组,并授予权限才行:# 创建docker用户组sudo groupadd docker# 把当前用户加入到docker用户组sudo gpasswd -a $USER docker# 更新当前用户组变动(就不用退出并重新登录了)newgrp docker挂载Host主机上的文件夹我们肯定不会满足于docker只访问自己的小世界里的文件系统(什么数据都没有),所以有必要让它能访问外界Host主机上的一些文件夹。比如我有一个docker是作为下载机用的,那么我肯定得让它把下载好的东西存到我的主机上,要不然就白下载了。参考:Docker学习—挂载本地目录挂载文件夹是在docker运行镜像的命令里就指定的(利用-v参数):$ docker run -it -v <HOST-PATH>:<DOCKER-PATH> ubuntu64 /bin/bash#或者作为只读挂载 (:ro)$ docker run -it -v <HOST-PATH>:<DOCKER-PATH>:ro ubuntu64 /bin/bash注意,挂载的双方都必须是绝对路径。映射Docker里的端口到Host主机上的端口如果Docker里运行的是Web服务比如Nginx,里面有一个网站,那你必须得把”虚拟机“里的端口映射到外部才能正常看到网页。映射是在运行docker命令时指定的,比如把里面的80端口映射到外面的8888端口,命令如下:$ docker run –name webserver -d -p 80:8888 nginx然后你在主机上的浏览器访问http://localhost:8888,就可以看到nginx里的网页了。Docker镜像保存更改直接在镜像上改动的内容,会在退出时全部消失。但是我们经常需要把这些变动保存下来。Docker保存这些变动的机制就是——生成另一个只读镜像。(-_-!)虽然正常看来,这不太好吧。但实际上,这很好!Docker镜像实际上是非常小的,所以生成另一个镜像也没有多费事。而且这种机制保证了每个镜像的不可随便修改的性质,这一点就极大的避免了混乱。Docker保存更改有两种方式:docker commit:就像git commit一样,把每次改动作为一个commit提交,可以追溯历史Dockerfile:这是从头build构建一个镜像的配置文件,把你想改动的地方(如安装一个程序)写成一句bash命令,加到Dockerfile这个文件里,它就会按照你的要求执行所有的命令,然后生成一个新的镜像。Commit可以追溯历史,但是变动了哪些地方是对外界黑箱的。Dockerfile确实明明白白写清楚有哪些改变。所以一般情况下,正式构建一个镜像,都是用Dockerfile的。“docker commit” 将变动过的Container保存为镜像参考:利用 commit 理解镜像构成docker commit命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。慎用docker commit:由于命令的执行,还有很多文件被改动或添加了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添加进来,如果不小心清理,将会导致镜像极为臃肿。此外,使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体在操作的。虽然 docker diff 或许可以告诉得到一些线索,但是远远不到可以确保生成一致镜像的地步。这种黑箱镜像的维护工作是非常痛苦的。而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。 ...

January 26, 2019 · 1 min · jiezi

Docker: 容器互访的三种方式

场景三个容器digger-app: 启动 API 服务,依赖 redis 和 mysqldigger-redis: redis 服务digger-mysql: mysql 服务我们需要让 digger-app 容器内运行的服务能够访问 digger-redis 和 digger-mysql 容器。方法一:–link–link 的格式为 –link name:alias,name 为需要连接到的容器的 name,alias 是给这个连接取个别名。首先启动 redis 服务和 mysql 服务:# redisdocker run –name digger-redis -d redis:5.0.3-alpine# mysqldocker run –name digger-mysql -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7.25如果不指定 name,docker 会随机生成一个 name,使用 docker ps 可以查看到运行容器的 name。在启动 digger-app 时,指定 –link 参数:docker run –name digger-api –link digger-redis:redis –link digger-mysql:mysql -d -p 3000:3000 your-image这样在 digger-api 中就能通过 连接名 访问到对应的服务了,如与 digger-redis 的 link 别名为 redis,那么在 digger-api 代码中,可以指定 redis 的 host 为 redis,以 node.js 举例:// redis.jsconst redis = require(‘redis’);const client = redis.createClient({ host: ‘redis’, port: 6379});// mysql.jsconst mysql = require(‘mysql’);const connection = mysql.createConnection({ host : ‘mysql’, user : ‘root’, password : ‘root’, database : ‘my_db’});connection.connect();使用 docker exec 命令进入容器,使用 ping 命令也可以查看容器是否互联成功:事实上,在 digger-api 容器内,如果查看 hosts 文件,可以发现 docker 已经将另外两个容器配置在了 hosts 中:/app # cat /etc/hosts127.0.0.1 localhost…172.17.0.6 redis 7a6409598773 cache-redis172.17.0.5 mysql f08bf0e0bf18 digger-mysql172.17.0.7 6eb8dab1e6db方法二:–network随着 Docker 网络的完善,更建议将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 –link 参数。使用 –network 命令可以指定容器运行的网络,通过将多个容器指定到同一个网络可以让容器间相互访问。创建网络docker network create -d bridge my-net指定网络# redisdocker run –name digger-redis -d –network my-net redis:5.0.3-alpine# mysqldocker run –name digger-mysql -e MYSQL_ROOT_PASSWORD=root -d –network my-net mysql:5.7.25# apidocker run –name digger-api –network my-net -d -p 3000:3000 your-image不过需要注意这时候就没有连接的别名了,在容器里面,host 直接使用对方容器的 name 访问即可。方法三:docker composeDocker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应用。新建 docker-compose.yml 文件,编写如下version: “3"services: digger-api: image: “express:v1” ports: - “3000:3000” digger-mysql: image: “mysql:5.7.25” environment: - MYSQL_ROOT_PASSWORD=root digger-redis: image: “redis:5.0.3-alpine"docker compose 的官方文档查看 这里。然后使用 docker-compose up -d 启动即可,容器会在后台运行。 ...

January 24, 2019 · 1 min · jiezi

使用Fluentd收集Docker容器日志

本文介绍使用Fluentd收集standalone容器日志的方法。Docker提供了很多logging driver,默认情况下使用的json-file,它会把容器打到stdout/stderr的日志收集起来存到json文件中,docker logs所看到的日志就是来自于这些json文件。当有多个docker host的时候你会希望能够把日志汇集起来,集中存放到一处,本文讲的是如何通过fluentd logging driver配合fluentd来达成这一目标。目标:将standalone容器打到stdout/stderror的日志收集起来收集的日志根据容器名分开存储日志文件根据每天滚动第一步:配置Fluentd实例首先是配置文件fluent.conf:<source> @type forward</source><match *> @type file path /fluentd/log/${tag}/${tag} append true <format> @type single_value message_key log </format> <buffer tag,time> @type file timekey 1d timekey_wait 10m flush_mode interval flush_interval 30s </buffer></match>新建一个目录比如/home/ubuntu/container-logs,并赋予权限chmod 777 /home/ubuntu/container-logs。然后启动Fluentd实例,这里使用的Docker方式:docker run -it \ -d \ -p 24224:24224 \ -v /path/to/conf/fluent.conf:/fluentd/etc/fluent.conf \ -v /home/ubuntu/container-logs:/fluentd/log fluent/fluentd:v1.3第二步:指定容器的logging driver在启动容器的时候执行使用fluentd作为logging driver:docker run -d \ … –log-driver=fluentd \ –log-opt fluentd-address=<fluentdhost>:24224 \ –log-opt mode=non-blocking \ –log-opt tag={{.Name}} \ <image>第三步:观察日志到/home/ubuntu/container-logs目录下能够看到类似这样的目录结构:.└── <container-name> └── <container-name>.20190123.log参考文档Configure logging driversCustomize log driver outputUse Fluentd logging driverDocker CLI - runFluentdFluentd - out_fileFluentd - formatter_single_valueFluentd - buf_fileFluentd - buffer ...

January 24, 2019 · 1 min · jiezi

gdal扩展支持Excel与postgresql

最近做的项目有关数据的上传入库,涉及到空间数据的处理大部分从业人员第一反应都是想到用gdal来做,但是gdal默认支持的数据格式不包含xls和xlsx以及postgresql,因此需要我们自己安装拓展进行编译,为了能够复用,我把编译的整个过程写进了Dockerfile制作成了一个镜像,以此记录FROM centos:7.4.1708# 安装xls依赖库RUN yum groupinstall -y “Development Tools” && \ yum -y install wget && \ wget http://www.gaia-gis.it/gaia-sins/freexl-sources/freexl-1.0.5.tar.gz && \ tar -zvxf freexl-1.0.5.tar.gz && \ cd freexl-1.0.5 && \ ./configure && \ make -j 4 && \ make install# 安装GDAL的依赖库,这个都是可选的,其中expat-devel是支持excel扩展,postgresql-devel是pg的扩展RUN yum install -y sqlite-devel libxml2-devel swig expat-devel libcurl-devel libgeos-dev postgresql postgresql-devel && \ # 编译GDAL wget http://download.osgeo.org/gdal/2.3.2/gdal-2.3.2.tar.gz && \ tar -zvxf gdal-2.3.2.tar.gz && \ cd gdal-2.3.2 && \ # 配置支持扩展 ./configure –with-pg –with-freexl –with-expat && \ make -j 4 && \ make installCMD [ “ogr2ogr”,"–formats" ] ...

January 21, 2019 · 1 min · jiezi

Jenkins + Docker 简单部署 node.js 项目

有了前几篇的基础后,我们现在已经能docker 篇:构建 docker 镜像上传私有仓库拉取私有镜像启动容器jenkins 篇:配置 pipeline触发 pipeline接下来就可以结合两者,用 jenkins + docker 来自动化部署我们的项目。配置 Jenkinsjenkins 的配置思路为构建机(IP: xx.xx.xx.xx)拉取代码构建机安装依赖构建机运行测试构建机打包并上传镜像至私有镜像仓库部署机(IP: yy.yy.yy.yy)拉取镜像部署机重启服务对应 pipeline 配置如下pipeline { agent any stages { stage(‘Update’) { steps { sh """ npm install """ } } stage(‘Test’) { steps { sh “npm test” } } stage(‘Build’) { steps { sh """ docker build -t localhost:5000/wool-digger-api:$BUILD_NUMBER . docker push localhost:5000/wool-digger-api:$BUILD_NUMBER """ } } stage(‘Deploy’) { steps { sh """ ssh -o stricthostkeychecking=no root@xx.xx.xx.xx " source /etc/profile docker pull yy.yy.yy.yy:5000/wool-digger-api:$BUILD_NUMBER docker rm -f wool-digger-api docker run -d –name=wool-digger-api –network host yy.yy.yy.yy:5000/wool-digger-api:$BUILD_NUMBER " """ } } }}BULID_NUMBER在 Build 和 Deploy 环节里,使用了 $BUILD_NUMBER 这个变量来作为镜像的 tag,这个变量是 jenkins 的系统变量之一,代表当前的构建号,每次构建这个号会加一,所以可以作为我们镜像的 tag。其他系统变量可 在此查看。Network这里使用 docker run 命令的时候,加入了 –network 参数,这个参数用来指定 Docker 容器运行的网络,默认为 bridge,即桥接模式。这种模式下在容器内通过 localhost 是访问不到宿主机的。如果指定为 host 则容器与宿主机共用网络,就无需使用 -p 命令映射端口了。这种模式下会破话隔离性,这里是为了在容器内方便地连接宿主机的 mysql 和 redis,推荐将 mysql 和 redis 也使用 docker 运行,host 值可作为一种临时解决方案。配置 Dockerdocker 的配置无需做太多修改FROM node:10.15.0-alpineMAINTAINER sunhengzhe@foxmail.comCOPY . /app/WORKDIR /appRUN npm install pm2 -gEXPOSE 1337CMD [“pm2-runtime”, “pm2/production.json”]这里的基本镜像使用了 node 的 alpine 版本,alpine 是面向安全的轻型 Linux 发行版,它的体积非常小。目前 Docker 官方已开始推荐使用 Alpine 替代之前的 Ubuntu 做为基础镜像环境。这样会带来多个好处。包括镜像下载速度加快,镜像安全性提高,主机之间的切换更方便,占用更少磁盘空间等。其他删除镜像如果需要批量删除镜像,可以使用docker rmi $(docker images | grep ‘镜像名’ | awk ‘{print $3}’) 持久化日志如上篇提到的,可以通过 -v 挂载容器内日志目录到宿主机。 ...

January 21, 2019 · 1 min · jiezi

docker 容器不自动退出结束运行的方法

本文主要简单介绍 docker 容器与前置进程的关系,以及如何编写 Dockerfile/docker-compose.yml 优雅的让容器可以常驻运行。docker 容器的生命周期是同容器中的前置进程相关在一起的,这也是我们平时可能会遇到一些容器只是运行几秒便自动结束的原因:因为容器中没有一个常驻的前置进程,前置进程运行结束后,容器便自动退出了。比如 docker hello-world# 一闪而过 输出一堆东西docker run –name hello-world hello-world# 可以看到 hello-world 容器已经退出了docker ps -a那怎样可以让容器不自动退出呢?如果我们想登入一个纯净的容器 alpine/centos/ubuntu 之类的,在其基础上安装一些服务组件,然后在 commit 成自己的镜像。看网上有不少方法是创建容器时执行一个 while(true) 的死循环(当然,sleep 一下)或者用 tail -f /dev/null 一类的,反正就是以开启一个可以常驻的前置进程为目的。其实我们可以更优雅的使用 docker 容器的 interactive 和 tty 参数来将 sh/bash (*nix 系统必有)命令作为前置命令开启,这样容器就不会自动退出了。例如使用 alpine 镜像做为基础镜像,创建一个 alpine 系统小容器,让其可以常驻运行,以便我们登录交互执行某些命令。# 使用 alpine 系统镜像创建容器# -i interactive=true 开启 stdin# -t tty=true 分配会话终端# -d 守护模式 不加也可以 不加就直接进入容器中了 需要 ctrl+p+q 切出# 不能 exit 哟, exit 相当于结束 sh 会话了 容器会退出的docker run -it -d –name alpine alpine sh# alpine 肯定在运行docker ps# 登入容器docker exec -it alpine sh# apline 使用的 apk 作为包管理# 安装个小火车# 后续可以使用 docker commit -m “alpine with sl cmd” -a “big_cat” alpine big_cat/alpine_sl 生成新的镜像apk add sl# 退出容器 注:-d 启动的才可以,如果没有 -d 启动直接进入的 sh终端 则不能退出,否则容器也会退出exit提交容器变更生成新的镜像docker commit -m “alpine with sl cmd” -a “big_cat” alpine big_cat/alpine_sldocker images# 有账号的话发布到 docker hub 上去docker push big_cat/alpine_sl# 后续停止/启动容器时就不需要在指定 -it 参数了docker stop alpinedocker start alpine提交容器变更生成新的镜像docker commit -m “alpine with sl cmd” -a “big_cat” alpine big_cat/alpine_sldocker images# 有账号的话发布到 docker hub 上去docker push big_cat/alpine_sl以上命令其实是借助 sh/bash 会话终端作为前置进程,使得容器不会自动退出。如果你觉得在创建容器时如此书写会很粗陋,没关系,我们可以将这些都推给 docker-composedocker-compose.ymlversion: ‘3’services: big_cat_alpine: container_name: big_cat_alpine image: alpine stdin_open: true # -i interactive tty: true # -t tty privileged: true entrypoint: [“sh”] # 执行 sh创建容器 & 登入容器docker-compose up -d big_cat_alpine ./docker psdocker exec -it big_cat_alpine sh通过 docker-compose 将那两个参数传入进去,编排后启动服务容器。 ...

January 21, 2019 · 1 min · jiezi

Docker安装+HelloWorld+运行Tomcat

前言只有光头才能变强。文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y上一篇已经讲解了为什么需要Docker?,相信大家已经对Docker有一个简单的认识了。这篇我来讲讲Docker入门的相关概念和常用的命令,跟大家一起入门Docker!一、Docker相关术语一般从零学习一门技术(语言),都是从HelloWorld开始的,但这次我偏不。我先来讲讲Docker的相关术语~镜像容器仓库在之前分享的Docker科普文,也通俗易懂地讲解了这三个术语:镜像、容器、仓库的概念可以类比代码、进程、github如果让我来给完全不懂Docker解释这三个术语,我会这样做:镜像就是镜像,我们重装系统/搞虚拟机的时候都要用镜像,没镜像哪来系统我们安装完镜像,就可以跑起来一个系统(Windows也好、Centos也好),于是我们就可以愉快地使用我们通过镜像安装好的系统了。在Docker中,通过镜像运行起来的东西叫做容器仓库就是专门存放镜像的地方镜像(image)【image除了图片/图像的意思外,还有镜像的意思】容器(container)通过镜像运行起来的实例仓库(reposity)专门存放镜像的地方二、安装Docker与HelloWorld首先需要明确自己所使用的环境,就我而言,我使用的是CentOS 7。据我所知,CentOS 7和CentOS 6.8在安装中都是有区别的。所以,如果你想跟着我一起安装Docker,先明确自己的版本是否是CentOS 7,如果不是只能找其他的教程来进行安装。cat /etc/redhat-release// 结果CentOS Linux release 7.3.1611 (Core)2.1安装Docker首先我们需要安装GCC相关的环境:// 安装GCC相关的环境yum -y install gccyum -y install gcc-c++如果曾经安装过Docker(旧版本)的话,得先卸载,如果没有安装过,跳过这一步:// 卸载旧Docker版本yum -y remove docker docker-common docker-selinux docker-engine安装Docker需要的依赖软件包:// 安装Docker需要的依赖软件包:yum install -y yum-utils device-mapper-persistent-data lvm2设置stable镜像仓库(注意:我们这里使用国内的镜像地址【因为Docker 官网给出的地址在国外,太慢了!】)// 设置stable镜像仓库:yum-config-manager –add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo更新yum软件包索引:// 更新yum软件包索引:yum makecache fast安装DOCKER CE(注意:Docker分为CE版和EE版,一般我们用CE版就够用了)// 安装DOCKER CE:yum -y install docker-ce启动Docker// 启动Dockersystemctl start docker2.2HelloWorld走起到上面为止,我们已经启动了Docker,下面我们检验一下我们运行的Docker是否正常。首先,我们可以看看下载回来的Docker版本:// 查看Docker的版本docker version版本都查到了,说明Docker是运行起来的啦。我们来一发HelloWorld:// docker的HelloWorlddocker run hello-world效果:简单解释:docker run hello-world这条命令指示Docker去运行hello-world这个镜像,但是我们本地没有这份镜像啊。所以Docker就去DockerHub拉取了一份hello-world镜像,并运行起来了(生成容器)。这个hello-world容器的功能就是一句话:Hello from Docker!。所以我们在屏幕上就可以看到这句话了。2.3配置加速器由于安装的Docker默认是去Docker Hub找我们想要的镜像的,我们知道国外肯定没国内的快,所以我们一般会配置一个加速器国内的镜像仓库一般我们使用的有:阿里云网易云// 等等首先,我们到https://promotion.aliyun.com/ntms/act/kubernetes.html搜索一下镜像,比如Tomcat (这就需要我们注册/登录一个阿里云账号)随后,我们可以在镜像加速器上找到我们的url:我们依照阿里云给出的教程,就可以配置加速器了。mkdir -p /etc/dockertee /etc/docker/daemon.json <<-‘EOF’{ “registry-mirrors”: [“https://cubmirje.mirror.aliyuncs.com”]}EOF让配置生效,并重启Dockersystemctl daemon-reloadsystemctl restart docker不知道大家学到这里,觉得怎么样。如果是我,我肯定觉得没意思。搞了个HelloWorld案例,就打印了一句话,没意思。三、体验Docker假如说,要在一台全新的系统上将Tomcat跑起来。我们需要做以下的事:安装Java,配置环境变量安装Tomcat如果我们用了Docker,我们是这样做的:直接在仓库里拉一份tomcat的镜像下来,将镜像跑起来就,就完事了!拉取Tomcat镜像:docker pull tomcatdocker images查看是否有拉取到的tomcatdocker image ls拉取到的Tomcat镜像:运行这个Tomcat镜像,生成容器docker run -it -p 9999:8080 tomcat我们可以发现,这个Tomcat运行起来了!(滚犊子,没运行起来,踩坑了!,一直卡在INFO: Deploying web application directory 这句话上了。我还一直想,为啥我访问不到呢,才发现没运行起来)3.1解决docker运行Tomcat卡住的问题原因:docker+tomcat 启动时非常慢,一般正常启动几十秒的,发现docker+tomcat启动竟需要几分钟,不可思议根本原因是 SecureRandom 这个 jre 的工具类的问题。那为什么 SecureRandom generateSeed 这么慢,甚至挂在 Linux 操作系统呢?Tomcat 7/8 都使用 org.apache.catalina.util.SessionIdGeneratorBase.createSecureRandom 类产生安全随机类 SecureRandom 的实例作为会话 ID。SecureRandom generateSeed 使用 /dev/random 生成种子。但是 /dev/random 是一个阻塞数字生成器,如果它没有足够的随机数据提供,它就一直等,这迫使 JVM 等待首先,我们先以后台的方式运行Tomcat镜像:docker run -tid tomcat以bash的方式进入到容器中:docker exec -it 4a471223bfc4(为你正在运行容器的id) /bin/bash为容器安装vim:apt-get updateapt-get install vim修改对应的java.security文件cd /docker-java-home/jre/lib/securityvim java.security找到 securerandom.source=file:/dev/random,修改为securerandom.source=file:/dev/./urandom在Tomcat镜像下创建一个属于我们自己的Tomcat镜像(这个镜像安装了vim,修改了配置文件)docker commit -m “change java.security” -a “3y” 4a471223bfc4(为当前容器的id) mytomcat于是我们就生成了mytomcat镜像,就可以使用mytomcat:docker run -it -p:9999:8080 mytomcat效果图:参考资料:docker 启动tomcat慢的解决方法https://my.oschina.net/lwenhao/blog/1830538JRE /dev/random阻塞https://www.cnblogs.com/lemon-flm/p/7396627.html除了Tomcat,大家还可以去拉个mysql、redis、centos等等镜像来玩玩四、命令说明Docker的命令无非就是对镜像、对容器进行增删改查。我是不太喜欢拉出每个命令来写文章的,所以这些命令还是大家自行学习吧之前收集资料的时候,我也找到了一张关于Docker的思维导图,对着来看看,感觉就没问题了:关注我的公众号,回复 脑图 即可获取原图(.xmind和.png格式)最后留下几个问题:我们知道Tomcat运行起来需要Java的支持,那么我们在Docker拉取下来的Tomcat镜像是不是也有Java环境呢?容器退出了(好比Linux进程退出了),容器的数据就没了,我们想要保留容器内的数据,怎么办?从上面我们可以看出,通过commit可以在原有的容器基础上,创建出属于我们自己的镜像,还有其他方式创建新的镜像吗?乐于输出干货的Java技术公众号:Java3y。公众号内有200多篇原创技术文章、海量视频资源、精美脑图,不妨来关注一下!觉得我的文章写得不错,不妨点一下赞! ...

January 21, 2019 · 1 min · jiezi

在docker中用Tomcat运行web项目

本文旨在用最通俗的语言讲述最枯燥的基本知识上一篇文章《为什么要用docker》已经讲述了什么是docker以及我们要用docker的原因,并且讲解了如何安装docker。这时候很多读者磨拳擦脚跃跃欲试但却发现安装好docker之后就无从下手了,那么,接下来,小编会从以下方面讲述docker的一些基础知识,当然,理论都是生硬的,所以小编选取了javaweb项目中最常用的一个软件–tomcat的安装和使用来引导学习一些docker相关的知识,借此让读者能够从实战的角度去理解docker为什么会有这些基础知识以及如何使用这些基础知识。我们知道,web开发和部署离不开tomcat,而在目前的实际情况是:一个项目中,每个开发者本机电脑都有自己的tomcat或者用idea的内置tomcat,当我们开发完成提交测试时,测试服务器上也有一个tomcat;当项目上线时,线上服务器也有一个tomcat,因此就很容易出现一些奇奇怪怪的问题,比如在同事A电脑上能正常运行的,到同事B电脑上就出问题的;或者是在测试环境里一切正常,到了线上bug一堆。这时候docker就有了用武之地,项目负责人把docker的tomcat镜像做好了之后上传到镜像仓库,项目成员的电脑环境、测试环境、线上环境均拉取这个tomcat使用,就能保持在所有的环境下tomcat的版本、设置都是一致的,避免了一些非技术的问题。那么,现在我们就用docker来做一个tomcat的环境。镜像的查找和拉取首先,我们需要去镜像仓库找到我们要的镜像,查找镜像的语法是docker search 镜像名称:镜像TAG因此,查找Tomcat镜像:docker search tomcat有读者会疑问:什么是镜像TAG?镜像的TAG值就是镜像的版本,就比如Tomcat有7.0、8.5、9.0等我们常用的版本,那么相应的这些软件被做成docker镜像之后,官方也会根据软件本身的版本对应出docker镜像的版本。下图为查找结果:可以看到,有很多的Tomcat镜像,那么我们应该怎么选用这些镜像呢?通常情况下,我们都知道官方的东西基本上代表安全无公害,因为可以看到右边有official标识为OK的就是官方的镜像,因此我们拉取第一个Tomcat就可以。拉取镜像的语法是:docker pull 镜像NAME:镜像TAG因为我们要拉取Tomcat时,就可以这样写:docker pull tomcat有人说你怎么没加上镜像TAG在拉取镜像时,如果加上镜像TAG,就会去查找是否有相应版本的镜像,如果有则拉取,如果没有则不拉去。而没有加TAG时,就默认代表拉取的是该镜像的最新版本。假如需要拉取固定版本如拉取Tomcat8.5:docker pull tomcat:8.5就会相应的拉取了该版本的Tomcat镜像。拉取成功之后,我们要查看我们拉取的版本,可以用命名:docker images可以看到,镜像列表里已经有了我们要拉取的Tomcat镜像镜像的使用镜像拉取下来之后,就如我们把从网上把下载到了本地电脑,但是没有任何生命的迹象,只有把它安装运行起来,才是一个活的容器,相应地,docker镜像只有在run起来的时候,才是一个有生命的容器。创建和运行一个Tomcat容器只需要一行命令: docker run –name my_tomcat tomcat:8.5其中 –name是指给容器起的名字,后面的tomcat是指下载的惊喜的NAME。8.5是镜像TAG。到这里,一个tomcat服务就安装并且启动好了,因为默认的Tomcat端口都是8080,因此通常情况下此时我们用ip:8080就应该可以访问到Tomcat的标志性主页了。但是!!!事实并不是如此因为我们是在docker下运行的容器,你们的脑海中可否还有docker的logo图?大海就相当于我们的服务器(宿主机),而在海中游走的鲸鱼就是docker服务,而鲸鱼上的每一个集装箱都是一个容器,也就是说:容器之间是互相隔离的,容器和宿主服务器也是互相隔离的。因此我们在docker中运行了一个Tomcat的服务器,虽然端口是8080,但是~那是容器中的8080端口,因此我们通过ip:8080访问到的其实只是宿主机上的8080端口,并不是容器中的端口。此时我们只需要把容器中的端口映射到宿主机上相应的端口即可。端口映射只需要在运行时加入指令 -p 映射的宿主机端口:容器运行端口如下,把容器运行的8080端口映射到宿主机的8099端口:名称设置为my_tomcat_1docker run -p 8099:8080 –name my_tomcat_1 tomcat:8.5此时,你会发现,控制台打出了Tomcat启动的日志,启动完成后,我们在浏览器上用ip:8099访问,就会发现,Tomcat主页就显示了,说明Tomcat已经正确地运行起来了。而用命令也可以查看容器是否起来,我们用docker ps 能查出本docker中运行的多有容器:docker ps在Tomcat运行的情况下,当你用ctrl+c回到命令行输入docker ps时,发现并没有找到my_tomcat_1这个Tomcat容器,而你重新用ip:8099发现Tomcat也不见了噢 no!什么原因?这是因为:在Linux下用指令的方式运行程序,如果没有加后台执行的指令,那么在切回到命令时,程序就被杀死了。因此,此时是因为我们运行Tomcat容器时,没有设置在后台运行,因此,需要在运行时,加上后台运行的指令,在docker中设置后台运行只需要在run命名中加入-d即可。因此我们重新运行Tomcat,名称设置为my_tomcat_2:docker run -d -p 8099:8080 –name my_tomcat_2 tomcat:8.5运行完成后,此时我们放心大胆的回到命名行。重新执行docker ps看控制台的输出:可以看到:my_tomcat_2已经在运行,IMAGE代表所用镜像以及镜像版本,PORTS显示的是0.0.0.0:8099->8080/tcp 。代表的是把本机的8080端口映射到宿主机上的8099端口中。但是,有读者又有疑问了:”Tomcat是运行了,那我怎么把我的项目丢进去docker的Tomcat中呢?我用FTP工具链接到服务器上也找不到Tomcat在哪里,怎么办?”上面提到,docker中容器和宿主机是互相隔离的,因此容器是不会在宿主机中有明确的文件夹路径,所以找不到才是对的。但是既然端口都能映射,为啥文件夹不能映射呢?在docker中,可以用 -v 指令指定把容器中的某个文件夹挂载到宿主机中它的语法为:-v 宿主机目录:容器目录所以我们可以在创建运行一个容器时,同时可以把指定的文件夹挂载到宿主机中通常情况下,Tomcat运行程序的文件是在webapps下的,那么可以在运行时把这个文件夹挂载到宿主机某个路径上(如:/data目录中),名称设置为my_tomcat_3,端口设置为8098,要把容器中的webapps文件夹挂载到宿主机中/data/my_tomcat_3/webapps文件夹:docker run -d -p 8098:8080 -v /data/my_tomcat_3/webapps:/usr/local/tomcat/webapps –name my_tomcat_3 tomcat:8.5此时我们再用FTP工具链接到宿主机服务器上,进入data文件夹,就会发现:my_tomcat_3/webapps文件夹已经躺在里边了,可见已经挂载成功。把文件夹挂载成功之后,此时我们把web项目丢到宿主机中的webapps下,重启Tomcat容器后,再用ip:8099访问,就会展示我们的web项目的内容了。到这里,docker中用Tomcat运行web项目的工作已经完成。但是,回过头来想想,留下很多问题:docker run是创建并且运行容器,那我怎么控制容器的状态呢?比如启动和停止为什么我在docker run时一直在重命名Tomcat的名字my_tomcat_1、my_tomcat_2、my_tomcat_3..拉取和运行了那么多镜像,占据很多磁盘空间,怎么处理?运行后的Tomcat,怎么查看日志?我想要修改一下Tomcat中的server.xml的设置,但是宿主机中有没有这个文件,难道我要挂载出来吗?看完此文之后,你有什么问题吗?欢迎读者把问题后台留言给小编或者加小编的微信,下一篇文章,小编会针对上面的问题以及读者提出的问题,做一个完整的解答以及把如何做好的Tomcat上传到镜像仓库做一个演示,所以:关!注!我!下期小编甚至会把常用的一些docker命名整理出来,方便大家集中记忆和使用!觉得本文对你有帮助?请分享给更多人关注「编程无界」,提升装逼技能

January 21, 2019 · 1 min · jiezi

容器监控实践—node-exporter

概述Prometheus从2016年加入CNCF,到2018年8月毕业,现在已经成为Kubernetes的官方监控方案,接下来的几篇文章将详细解读Promethues(2.x)Prometheus可以从Kubernetes集群的各个组件中采集数据,比如kubelet中自带的cadvisor,api-server等,而node-export就是其中一种来源Exporter是Prometheus的一类数据采集组件的总称。它负责从目标处搜集数据,并将其转化为Prometheus支持的格式。与传统的数据采集组件不同的是,它并不向中央服务器发送数据,而是等待中央服务器主动前来抓取,默认的抓取地址为http://CURRENT_IP:9100/metricsnode-exporter用于采集服务器层面的运行指标,包括机器的loadavg、filesystem、meminfo等基础监控,类似于传统主机监控维度的zabbix-agentnode-export由prometheus官方提供、维护,不会捆绑安装,但基本上是必备的exporter功能node-exporter用于提供NIX内核的硬件以及系统指标。如果是windows系统,可以使用WMI exporter如果是采集NVIDIA的GPU指标,可以使用prometheus-dcgm 根据不同的NIX操作系统,node-exporter采集指标的支持也是不一样的,如:diskstats 支持 Darwin, Linuxcpu 支持Darwin, Dragonfly, FreeBSD, Linux, Solaris等,详细信息参考:node_exporter我们可以使用 –collectors.enabled参数指定node_exporter收集的功能模块,或者用–no-collector指定不需要的模块,如果不指定,将使用默认配置。部署二进制部署:下载地址:从https://github.com/prometheus…解压文件:tar -xvzf .tar.gz开始运行:./node_exporter./node_exporter -h 查看帮助usage: node_exporter [<flags>]Flags: -h, –help –collector.diskstats.ignored-devices –collector.filesystem.ignored-mount-points –collector.filesystem.ignored-fs-types –collector.netdev.ignored-devices –collector.netstat.fields –collector.ntp.server=“127.0.0.1” ……/node_exporter运行后,可以访问http://${IP}:9100/metrics,就会展示对应的指标列表Docker安装:docker run -d \ –net=“host” \ –pid=“host” \ -v “/:/host:ro,rslave” \ quay.io/prometheus/node-exporter \ –path.rootfs /hostk8s中安装:node-exporter.yaml文件:apiVersion: v1kind: Servicemetadata: annotations: prometheus.io/scrape: ’true’ labels: app: node-exporter name: node-exporter name: node-exporterspec: clusterIP: None ports: - name: scrape port: 9100 protocol: TCP selector: app: node-exporter type: ClusterIP—-apiVersion: extensions/v1beta1kind: DaemonSetmetadata: name: node-exporterspec: template: metadata: labels: app: node-exporter name: node-exporter spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/tryk8s/node-exporter:latest name: node-exporter ports: - containerPort: 9100 hostPort: 9100 name: scrape hostNetwork: true hostPID: truekubectl create -f node-exporter.yaml得到一个daemonset和一个service对象,部署后,为了能够让Prometheus能够从当前node exporter获取到监控数据,这里需要修改Prometheus配置文件。编辑prometheus.yml并在scrape_configs节点下添加以下内容:scrape_configs: # 采集node exporter监控数据 - job_name: ’node’ static_configs: - targets: [’localhost:9100’]也可以使用prometheus.io/scrape: ’true’标识来自动获取service的metric接口- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]配置完成后,重启prometheus就能看到对应的指标查看指标:直接查看:如果是二进制或者docker部署,部署成功后可以访问:http://${IP}:9100/metrics会输出下面格式的内容,包含了node-exporter暴露的所有指标:# HELP go_gc_duration_seconds A summary of the GC invocation durations.# TYPE go_gc_duration_seconds summarygo_gc_duration_seconds{quantile=“0”} 6.1872e-05go_gc_duration_seconds{quantile=“0.25”} 0.000119463go_gc_duration_seconds{quantile=“0.5”} 0.000151156go_gc_duration_seconds{quantile=“0.75”} 0.000198764go_gc_duration_seconds{quantile=“1”} 0.009889647go_gc_duration_seconds_sum 0.257232201go_gc_duration_seconds_count 1187# HELP node_cpu Seconds the cpus spent in each mode.# TYPE node_cpu counternode_cpu{cpu=“cpu0”,mode=“guest”} 0node_cpu{cpu=“cpu0”,mode=“guest_nice”} 0node_cpu{cpu=“cpu0”,mode=“idle”} 68859.19node_cpu{cpu=“cpu0”,mode=“iowait”} 167.22node_cpu{cpu=“cpu0”,mode=“irq”} 0node_cpu{cpu=“cpu0”,mode=“nice”} 19.92node_cpu{cpu=“cpu0”,mode=“softirq”} 17.05node_cpu{cpu=“cpu0”,mode=“steal”} 28.1Prometheus查看:类似go_gc_duration_seconds和node_cpu就是metric的名称,如果使用了Prometheus,则可以在http://${IP}:9090/页面的指标中搜索到以上的指标:常用指标类型有:node_cpu:系统CPU使用量node_disk:磁盘IOnode_filesystem:文件系统用量node_load1:系统负载node_memeory*:内存使用量node_network*:网络带宽node_time:当前系统时间go_:node exporter中go相关指标process_:node exporter自身进程相关运行指标Grafana查看:Prometheus虽然自带了web页面,但一般会和更专业的Grafana配套做指标的可视化,Grafana有很多模板,用于更友好地展示出指标的情况,如Node Exporter for Prometheus在grafana中配置好变量、导入模板就会有上图的效果。深入解读node-exporter是Prometheus官方推荐的exporter,类似的还有HAProxy exporterCollectd exporterSNMP exporterMySQL server exporter….官方推荐的都会在https://github.com/prometheus下,在exporter推荐页,也会有很多第三方的exporter,由个人或者组织开发上传,如果有自定义的采集需求,可以自己编写exporter,具体的案例可以参考后续的[自定义Exporter]文章版本问题因为node_exporter是比较老的组件,有一些最佳实践并没有merge进去,比如符合Prometheus命名规范(https://prometheus.io/docs/pr…,目前(2019.1)最新版本为0.17一些指标名字的变化(详细比对)* node_cpu -> node_cpu_seconds_total* node_memory_MemTotal -> node_memory_MemTotal_bytes* node_memory_MemFree -> node_memory_MemFree_bytes* node_filesystem_avail -> node_filesystem_avail_bytes* node_filesystem_size -> node_filesystem_size_bytes* node_disk_io_time_ms -> node_disk_io_time_seconds_total* node_disk_reads_completed -> node_disk_reads_completed_total* node_disk_sectors_written -> node_disk_written_bytes_total* node_time -> node_time_seconds* node_boot_time -> node_boot_time_seconds* node_intr -> node_intr_total解决版本问题的方法有两种:一是在机器上启动两个版本的node-exporter,都让prometheus去采集。二是使用指标转换器,他会将旧指标名称转换为新指标对于grafana的展示,可以找同时支持两套指标的dashboard模板Collectornode-exporter的主函数:// Package collector includes all individual collectors to gather and export system metrics.package collectorimport ( “fmt” “sync” “time” “github.com/prometheus/client_golang/prometheus” “github.com/prometheus/common/log” “gopkg.in/alecthomas/kingpin.v2”)// Namespace defines the common namespace to be used by all metrics.const namespace = “node"可以看到exporter的实现需要引入github.com/prometheus/client_golang/prometheus库,client_golang是prometheus的官方go库,既可以用于集成现有应用,也可以作为连接Prometheus HTTP API的基础库。比如定义了基础的数据类型以及对应的方法:Counter:收集事件次数等单调递增的数据Gauge:收集当前的状态,比如数据库连接数Histogram:收集随机正态分布数据,比如响应延迟Summary:收集随机正态分布数据,和 Histogram 是类似的switch metricType { case dto.MetricType_COUNTER: valType = prometheus.CounterValue val = metric.Counter.GetValue() case dto.MetricType_GAUGE: valType = prometheus.GaugeValue val = metric.Gauge.GetValue() case dto.MetricType_UNTYPED: valType = prometheus.UntypedValue val = metric.Untyped.GetValue()client_golang库的详细解析可以参考:theory-source-code本文为容器监控实践系列文章,完整内容见:container-monitor-book ...

January 20, 2019 · 2 min · jiezi

docker搭建lnmp环境

<!– TOC –>docker搭建lnmp环境一、Dockerfile定制镜像二、docker-compose三、docker-compose编排lnmp环境1、mysql2、redis3、mongo4、nginx5、php6、完整版四、参考<!– /TOC –>有收获的话请加颗小星星,没有收获的话可以 反对 没有帮助 举报三连代码仓库docker搭建lnmp环境一、Dockerfile定制镜像# FROM 指定基础镜像FROM 镜像FROM php:7.2-fpm# RUN 执行RUN <命令>orRUN [“可执行文件”, “参数1”, “参数2”]RUN echo ‘<h1>Hello, Docker!</h1>’ > /usr/share/nginx/html/index.htmlRUN [“php”, “-S”, “0.0.0.0:8080”]# COPY 复制文件COPY <源路径>… <目标路径>COPY swoole-4.2.10.tgz /homeCOPY nginx.conf /etc/nginx/nginx.conf# ADD 复制文件或目录,如果是.tgz,会被解压缩ADD <源路径>… <目标路径>ADD nginx.conf /etc/nginx/nginx.conf# CMD 容器启动CMD echo $HOME => CMD [ “/bin/sh”, “-c”, “echo $HOME” ]CMD [ “redis-server”, “/usr/local/etc/redis/redis.conf” ]# ENTRYPOINT 入口点ENTRYPOINT [“docker-entrypoint.sh”]存在 ENTRYPOINT 后,CMD 的内容将会作为参数传给 ENTRYPOINT# ENV 环境变量ENV <key> <value>ENV MYSQL_ROOT_PASSWORD root# ARG与ENV差不多ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的ENV MYSQL_ROOT_PASSWORD root# VOLUME 匿名卷VOLUME ["<路径1>", “<路径2>”…]VOLUME ["/data"]# EXPOSE 暴露端口EXPOSE <端口1> [<端口2>…]EXPOSE 80 443# WOEKDIR 指定工作目录,进入容器后的落地目录WORKDIR <工作目录路径>WORKDIR /var/www# USER 指定当前用户USER <用户名>USER root二、docker-compose详细请查看 https://docker_practice.gitee…服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。三、docker-compose编排lnmp环境1、mysql这里我们使用了mysql5.5版本,没其它用意,相比5.7以上版本,占内存和硬盘最小的一个版本我们准备了一个my.cnf作为额外配置,这里我修改了数据库的时区[mysqld]default-time-zone = ‘+8:00’FROM mysql:5.5COPY my.cnf /etc/mysql/conf.dEXPOSE 33062、redis我们使用准备的配置文件redis.conf覆盖容器默认启动的配置文件,修改了ip绑定和密码bind 0.0.0.0requirepass rootFROM redis:latestCOPY redis.conf /usr/local/etc/redis/redis.confCMD [ “redis-server”, “/usr/local/etc/redis/redis.conf” ]EXPOSE 63793、mongomongodb我们没有特殊处理FROM mongo:latestEXPOSE 270174、nginx我们准备了一份nginx.conf和虚拟目录conf.d,为了以后可以动态的配置网站的代理和负载均衡还有一个日志目录,放在外层logs目录里面,记录nginx的访问日志特别注意的是fastcgi_pass php:9000;而不是fastcgi_pass 127.0.0.1:9000;,目前自己也没明白FROM nginx:alpineCOPY nginx.conf /etc/nginx/nginx.confEXPOSE 805、phpphp算是这里面最难搞定的,因为我们需要额外的添加php扩展,虽然php的docker官方提供了docker-php-ext-configure, docker-php-ext-install, docker-php-ext-enable,还是有些扩展需要通过手动编译或者pecl安装由于pecl官网下载慢,我们事先下载好了几个需要的扩展php-fpm用的是debian的linux系统,下载也很慢,我们备用了阿里云的镜像sources.list我们还准备了php的默认配置php.ini和opcache.ini比如swoole扩展安装,记得安装包用完后清理,还有得用COPY命令,ADD会解压缩# swooleCOPY swoole-4.2.10.tgz /homeRUN pecl install /home/swoole-4.2.10.tgz && \ docker-php-ext-enable swoole && \ rm /home/swoole-4.2.10.tgz6、完整版version: ‘3’networks: frontend: driver: bridge backend: driver: bridgevolumes: mysql: driver: local mongo: driver: local redis: driver: localservices: php: build: ./php volumes: - ${WORKER_DIR}:/var/www ports: - 9100:9000 depends_on: - mysql - redis - mongo networks: - backend nginx: build: ./nginx volumes: - ${WORKER_DIR}:/var/www - ./logs/nginx:/var/log/nginx - ./nginx/conf.d:/etc/nginx/conf.d ports: - 8000:80 depends_on: - php networks: - frontend - backend mysql: build: ./mysql environment: - MYSQL_ROOT_PASSWORD=root volumes: - ${DATA_PATH}/mysql:/var/lib/mysql ports: - 3310:3306 networks: - backend mongo: build: ./mongo environment: - MONGO_INITDB_ROOT_USERNAME=root - MONGO_INITDB_ROOT_PASSWORD=root ports: - 27010:27017 volumes: - ${DATA_PATH}/mongo:/data/db networks: - backend redis: build: ./redis volumes: - ${DATA_PATH}/redis:/data ports: - 6310:6379 networks: - backend四、参考Docker — 从入门到实践laradockDocker在PHP项目开发环境中的应用 ...

January 20, 2019 · 2 min · jiezi

简明docker教程

<!– TOC –>简明docker教程一、什么是docker二、docker与虚拟机比较三、安装docker四、基本概念1、镜像2、容器3、数据卷4、挂载五、参考资料<!– /TOC –>有收获的话请加颗小星星,没有收获的话可以 反对 没有帮助 举报三连代码仓库简明docker教程一、什么是dockerDocker是一个开放源代码软件项目,让应用程序布署在软件货柜下的工作可以自动化进行,借此在Linux操作系统上,提供一个额外的软件抽象层,以及操作系统层虚拟化的自动管理机制。Docker利用Linux核心中的资源分离机制,例如cgroups,以及Linux核心名字空间(namespaces),来创建独立的容器(containers)。这可以在单一Linux实体下运作,避免启动一个虚拟机造成的额外负担。Linux核心对名字空间的支持完全隔离了工作环境中应用程序的视野,包括进程树、网络、用户ID与挂载文件系统,而核心的cgroup提供资源隔离,包括CPU、存储器、block I/O与网络。从0.9版本起,Dockers在使用抽象虚拟是经由libvirt的LXC与systemd - nspawn提供界面的基础上,开始包括libcontainer库做为以自己的方式开始直接使用由Linux核心提供的虚拟化的设施,上面都是废话,简言之Docker的思想来自于集装箱,集装箱解决了什么问题?在一艘大船上,可以把货物规整的摆放起来。并且各种各样的货物被集装箱标准化了,集装箱和集装箱之间不会互相影响。那么我就不需要专门运送水果的船和专门运送化学品的船了。只要这些货物在集装箱里封装的好好的,那我就可以用一艘大船把他们都运走。二、docker与虚拟机比较特性容器虚拟机启动秒级分钟级硬盘使用一般为 MB一般为 GB性能接近原生弱于系统支持量单机支持上千个容器一般几十个三、安装docker我自己用的是Docker for Mac其它系统可以参考 http://docker_practice.gitee….四、基本概念镜像(Image)Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。容器(Container)镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。仓库(Repository)镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。1、镜像# 获取镜像docker pull ubuntu:14.04# 以ubuntu:14.04镜像为基础启动并运行一个容器docker run -it –rm \ ubuntu:14.04 \ bash-it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。–rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 –rm 可以避免浪费空间。ubuntu:14.04:这是指用 ubuntu:14.04 镜像为基础来启动容器。bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash。# 列出镜像docker image lsdocker images# 镜像占用docker system df# 清楚悬挂镜像docker image prune# 删除镜像docker image rmdocker rmi2、容器# 启动以守护模式创建的名字为demo-u的容器,并以交互模式进入容器docker run –name demo-u -t -i -d ubuntu:14.04 bash# 运行后就可以通过ID或名字进入容器,并输出hello worlddocker exec -it demo-u /bin/sh -c “echo hello world”# 查看运行中的容器docker container lsdocker ps# 所有容器docker container ls -adocker ps -a# 查看容器日志docker container logs demo-udocker logs demo-u# 终止容器docker container stop demo-udocker stop demo-u# 启动容器docker container start demo-udocker start demo-u# 重启容器docker container restart demo-udocker restart demo-u# 进入容器,退出后容器也停止docker attach demo-u# 导出容器docker export# 导入容器docker import# 删除容器docker container rmdocker rm# 清除所有容器docker container prune3、数据卷# 创建数据卷docker volume create# 列出数据卷docker volume ls# 删除数据卷docker volume rm# 清除没用的数据卷docker volume prune4、挂载也就是目录共享,两种方式:-v–mount 推荐# 使用php本地服务器查看php环境,加载主机的 ~/web 目录到容器的 /var/www/web目录mkdir -p ~/web && cd ~/web && echo “<?php phpinfo();” > index.phpdocker run -d \ –name web \ -p 8080:8080 \ –mount type=bind,source=pwd,target=/var/www/web \ php:7.2-fpm \ /bin/sh -c “cd /var/www/web && php -S 0.0.0.0:8080"或者docker run -d \ –name web \ -p 8080:8080 \ -v pwd:/var/www/web \ php:7.2-fpm \ /bin/sh -c “cd /var/www/web && php -S 0.0.0.0:8080"打开浏览器 0.0.0.0:8080# 查看数据卷docker volume inspect web五、参考资料Docker — 从入门到实践 ...

January 20, 2019 · 2 min · jiezi

Docker: 上传镜像至私有仓库

镜像可以很方便直接 push 到 docker 的公共仓库,就好像 github 一样,但是我们在开发中很多时候都不想公开镜像文件,这时就需要搭建 docker 的私有仓库,就好像 gitlab 一样。在 上一篇 构建出镜像后,我们可以部署一个私有镜像仓库用来存放我们的镜像。启动私有 Registry启动一个私有仓库也非常简单,在服务器上执行命令docker run -d -p 5000:5000 –name=“docker-registry” –restart=always -v /root/docker/registry/:/var/lib/registry/ registry 即后台启动 registry 镜像构建出来的容器,并命名为 docker-registry,端口号映射为 5000 到 5000。–restart=always 代表当容器因为某些原因停止时,不管退出码是什么都自动重启。除了 always 还有 on-failure 代表只有退出码不为 0 时才重启,并且接受重启次数参数:–restart=on-failture:5-v 指定将宿主机的 /root/docker/registry/ 目录挂载到容器的 /var/lib/registry/ 目录。这样我们不用进入容器,在宿主机上就能访问到容器内我们感兴趣的目录了。为什么是 /var/lib/registry/ 目录?仓库默认存放镜像等信息在容器的 /var/lib/registry/docker 目录下,可以进入该目录查看已上传镜像信息。执行 run 命令成功后使用 docker ps 能看到 registry 服务已经启动:上传镜像要上传镜像到私有仓库,需要在镜像的 tag 上加入仓库地址:docker tag express-app 111.111.111.111:5000/sunhengzhe/express-app:v1为了不与其他镜像冲突,可以加入命名空间如 sunhengzhe,另外最好给镜像打上 tag 如 v1。注意仓库地址没有加协议部分,docker 默认的安全策略需要仓库是支持 https 的,如果服务器只能使用 http 传输,那么直接上传会失败,需要在 docker 客户端的配置文件中进行声明。mac 配置更改完需要 Apply & Restartcentos 系统在 /etc/docker/daemon.json 文件中写入:{ “registry-mirror”: [ “https://registry.docker-cn.com” ], “insecure-registries”: [ “[私有仓库 ip:port]” ]}然后重启 dockersystemctl restart docker推送镜像打完 tag 后使用 push 命令推送即可:docker push 111.111.111.111:5000/sunhengzhe/express-app推送失败如果出现 Retrying in 5 seconds 然后上传失败的问题。可以首先在服务器上使用 logs 命令查看日志:docker logs -f docker-registry-f 代表持续输出文件内容。如果出现 filesystem: mkdir /var/lib/registry/docker: permission denied,可能是 一个 selinux 问题,需要在服务器上对挂载目录进行处理:chcon -Rt svirt_sandbox_file_t /root/docker/registry/此示例中即 /root/docker/registry/。拉取镜像使用 pull 命令即可docker pull 111.111.111.111:5000/sunhengzhe/express-app ...

January 20, 2019 · 1 min · jiezi

Docker: 编写 dockerfile 启动 node.js 应用

编写 Dockerfile以 express 自动创建的目录为例,目录结构如下:├── /bin│ └── www├── /node_modules├── /public├── /routes├── /views├── package-lock.json├── package.json├── ecosystem.config.js├── app.js└── Dockerfile在项目目录下新建 Dockerfile 文件FROM node:10.15MAINTAINER sunhengzhe@foxmail.comCOPY . /app/WORKDIR /appRUN npm install pm2 -gEXPOSE 8003CMD [“pm2-runtime”, “ecosystem.config.js”]FROM 指定基础镜像为 node 的 10.15 版本(node 官方版本可 在此查看)MAINTAINER 说明镜像的维护者COPY 命令将宿主机的文件拷贝到镜像中,格式为 COPY [–chown=<user>:<group>] <源路径>… <目标路径>,这里将项目目录下的所有文件都拷贝到镜像中的 /app 目录下。如果目标路径不存在,docker 将自动创建。WORKDIR 用来指定工作目录,即是 CMD 执行所在的目录。RUN 命令用来执行 shell 命令,这里用来安装 pm2EXPOSE 命令用来 声明 运行时容器提供服务端口,但要注意运行时并不会开启这个端口的服务。这个命令主要是帮助使用者理解这个镜像服务的守护端口,以方便配置映射;另外在使用随机端口映射时,会自动随机映射 EXPOSE 的端口CMD 是默认的容器主进程的启动命令构建镜像在项目目录下执行docker build -t express-app:v1 .如果构建成功,查看镜像列表docker images应该会输出镜像 express-app:v1运行容器docker run -d -p 8003:3000 –name=“express-app” express-app:v1docker run 是 docker create 和 docker start 两个命令的简写。-d 即 –detach,代表让容器后台运行。-p 指定宿主机和容器的端口映射,左边为宿主机的端口,右边为容器的端口,也就是说访问宿主机的 8003 端口,会映射到容器内的 3000 端口。–name 设置容器别名,如果不指定,docker 会随机生成一个名字,比如 tender_swirles 之类的。执行docker ps正常展示如下添加 -a 参数可以查看所有已启动容器。进入容器如果要进入容器进行操作,执行下面命令docker exec -it express-app bash-i 与 -t 一般结合使用,-i 启动交互模式,-t 指定需要分配终端,可以自行尝试不传其中一个的效果。与 exec 类似的还有 attach 命令,命令为 docker attach express-app,但从这个 stdin 中 exit,会导致容器的停止,所以推荐使用 exec 命令。关闭操作停止容器docker stop express-app删除容器docker rm express-app如果删除时容器还在运行,需要加上 -f 参数删除镜像docker rmi express-app:v1 ...

January 19, 2019 · 1 min · jiezi

k8s与etcd--备份etcd数据到s3

前言整个k8s诸多组件几乎都是无状态的,所有的数据保存在etcd里,可以说etcd是整个k8s集群的数据库。可想而知,etcd的重要性。因而做好etcd数据备份工作至关重要。这篇主要讲一下我司的相关的实践。备份etcd数据到s3能做etcd的备份方案很多,但是大同小异,基本上都是利用了etcdctl命令来完成。为什么选择s3那?因为我们单位对于aws使用比较多,另外我们希望我们备份到一个高可用的存储中,而不是部署etcd的本机中。此外,s3支持存储的生命周期的设置。设置一下,就可以aws帮助我们定时删除旧数据,保留新的备份数据。具体方案我们基本上用了etcd-backup这个项目,当然也fork了,做了稍微的更改,主要是更改了dockerfile。将etcdctl 修改为我们线上实际的版本。修改之后的dockerfile如下:FROM alpine:3.8RUN apk add –no-cache curl# Get etcdctlENV ETCD_VER=v3.2.24RUN \ cd /tmp && \ curl -L https://storage.googleapis.com/etcd/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz | \ tar xz -C /usr/local/bin –strip-components=1COPY ./etcd-backup /ENTRYPOINT ["/etcd-backup"]CMD ["-h"]之后就是docker build之类了。k8s部署方案选择k8s中的cronjob比较合适,我的备份策略是每三小时备份一次。cronjob.yaml:apiVersion: batch/v1beta1kind: CronJobmetadata: name: etcd-backup namespace: kube-systemspec: schedule: “* */3 * * *” successfulJobsHistoryLimit: 2 failedJobsHistoryLimit: 2 jobTemplate: spec: # Job timeout activeDeadlineSeconds: 300 template: spec: tolerations: # Tolerate master taint - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule # Container creates etcd backups. # Run container in host network mode on G8s masters # to be able to use 127.0.0.1 as etcd address. # For etcd v2 backups container should have access # to etcd data directory. To achive that, # mount /var/lib/etcd3 as a volume. nodeSelector: node-role.kubernetes.io/master: "" containers: - name: etcd-backup image: iyacontrol/etcd-backup:0.1 args: # backup guest clusters only on production instalations # testing installation can have many broken guest clusters - -prefix=k8s-prod-1 - -etcd-v2-datadir=/var/lib/etcd - -etcd-v3-endpoints=https://172.xx.xx.221:2379,https://172.xx.xx.83:2379,https://172.xx.xx.246:2379 - -etcd-v3-cacert=/certs/ca.crt - -etcd-v3-cert=/certs/server.crt - -etcd-v3-key=/certs/server.key - -aws-s3-bucket=mybucket - -aws-s3-region=us-east-1 volumeMounts: - mountPath: /var/lib/etcd name: etcd-datadir - mountPath: /certs name: etcd-certs env: - name: ETCDBACKUP_AWS_ACCESS_KEY valueFrom: secretKeyRef: name: etcd-backup key: ETCDBACKUP_AWS_ACCESS_KEY - name: ETCDBACKUP_AWS_SECRET_KEY valueFrom: secretKeyRef: name: etcd-backup key: ETCDBACKUP_AWS_SECRET_KEY - name: ETCDBACKUP_PASSPHRASE valueFrom: secretKeyRef: name: etcd-backup key: ETCDBACKUP_PASSPHRASE volumes: - name: etcd-datadir hostPath: path: /var/lib/etcd - name: etcd-certs hostPath: path: /etc/kubernetes/pki/etcd/ # Do not restart pod, job takes care on restarting failed pod. restartPolicy: Never hostNetwork: true 注意:容忍 和 nodeselector配合,让pod调度到master节点上。然后secret.yaml:apiVersion: v1kind: Secretmetadata: name: etcd-backup namespace: kube-systemtype: Opaquedata: ETCDBACKUP_AWS_ACCESS_KEY: QUtJTI0TktCT0xQRlEK ETCDBACKUP_AWS_SECRET_KEY: aXJ6eThjQnM2MVRaSkdGMGxDeHhoeFZNUDU4ZGRNbgo= ETCDBACKUP_PASSPHRASE: ““总结之前我们尝试过,etcd-operator来完成backup。实际使用过程中,发现并不好,概念很多,组件复杂,代码很多写法太死。最后选择etcd-backup。主要是因为简单,less is more。看源码,用golang编写,扩展自己的一些需求,也比较简单。 ...

January 18, 2019 · 2 min · jiezi

Kong Api 网关使用 docker 部署

Kong 镜像: https://hub.docker.com/_/kong官网给定的用户安装手册上并没有设置 PG 的密码,导致如下问题无法启动nginx: [error] init_by_lua error: /usr/local/share/lua/5.1/kong/init.lua:277: [PostgreSQL error] failed to >retrieve server_version_num: connection refusedstack traceback:[C]: in function ‘assert’/usr/local/share/lua/5.1/kong/init.lua:277: in function ‘init’init_by_lua:3: in main chunk后在 issues 中找到问题原因及解决方法(里面还有个docker-compose):https://github.com/Kong/docke…使用 docker 的安装Kong 使用 postgresql 或 cassandra 存储数据,这里我们选用 PG# 创建 pg 数据库 容器docker run -d –name kong-database -p 5432:5432 -e “POSTGRES_USER=kong” -e “POSTGRES_DB=kong” -e “POSTGRES_PASSWORD=your_pg_password” \postgres:9.6# kong 数据迁移到 pgdocker run –rm --link kong-database:kong-database \ #将 kong-database 容器的地址引入注册到本容器-e “KONG_DATABASE=postgres” -e “KONG_PG_HOST=kong-database” -e “KONG_PG_PASSWORD=your_pg_password” \ # 官方文档未给出此参数 PG 可能不支持无密登录了-e “KONG_CASSANDRA_CONTACT_POINTS=kong-database” \kong kong migrations bootstrap#创建 kong 容器docker run -d –name kong --link kong-database:kong-database -e “KONG_DATABASE=postgres” -e “KONG_PG_HOST=kong-database” -e “KONG_PG_PASSWORD=your_pg_password” -e “KONG_CASSANDRA_CONTACT_POINTS=kong-database” -e “KONG_PROXY_ACCESS_LOG=/dev/stdout” -e “KONG_ADMIN_ACCESS_LOG=/dev/stdout” -e “KONG_PROXY_ERROR_LOG=/dev/stderr” -e “KONG_ADMIN_ERROR_LOG=/dev/stderr” -e “KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl” -p 8000:8000 \ # http 代理端口-p 8443:8443 \ # https 代理端口-p 8001:8001 \ # http 管理接口-p 8444:8444 \ # https 管理接口kong#查看是否启动docker ps#如未启动通过日志查看问题docker logs kong#如正常启动 可访问管理Api(替换成你的IP)curl -X GET http://192.168.20.6:8001因为 kong 服务是在容器中,所以设 KONG_ADMIN_LISTEN 为全局监听才能通过宿主机IP代理访问,宿主机则需对相应的映射端口做访问限制,如本机/内网访问,且不应该对外网可访问,不然谁都可以改你的Api网关策略了。自定义 kong 配置文件kong docker 镜像的配置文件路径为 /etc/kong/kong.conf如需自定义配置文件,自行挂载即可。kong 配置项手册:https://docs.konghq.com/1.0.x……-v /opt/kong/kong.conf:/etc/kong/kong.conf…管理网关的API的使用教程这里就不写了,自行觅食吧~简单的看看下面这篇可以的Kong 集成 Jwt 插件:https://www.cnkirito.moe/kong…kong服务网关API:https://www.jianshu.com/p/ef6… ...

January 17, 2019 · 1 min · jiezi