个推基于 Docker 和 Kubernetes 的微服务实践

一、微服务化

微服务架构

未分类

微服务 是将单一的应用程序拆分成多个微小的服务,各个小服务之间松耦合,高内聚,每个小的服务可以单独进行开发,不依赖于具体的编程语言,也可以使用不同的数据存储技术,各个服务可以独立部署,拥有各自的进程,相互之间通过轻量化的机制进行通信(如基于HTTP的API接口),所有的服务共同实现具体的业务功能。

未分类

客户端与服务端通信有2种方式,第一种是客户端直接与各个微服务进行通信,这样的架构有4个缺点:

(1)多次服务请求,效率低;

(2)对外暴露服务接口;

(3)接口协议无法统一;

(4)客户端代码复杂,服务端升级困难。

第二种方式是由API网关统一代理各个服务,对外提供统一的接口协议,该架构有3 个优势:

(1)封装服务接口细节,减少通信次数;

(2)统一通信协议,减少客户端代码耦合;

(3)统一鉴权,流控,防攻击;

在该架构下,网关也有可能成为系统瓶颈。

未分类

相应地,这2种架构也带来了2种服务注册发现的方式,第一种是客户端通过向服务的注册中心查询微服务的地址与其通信,第二种是增加统一的API网关来查询。前者会增加客户端的复杂度,开发成本高,第二种操作会显得更加简洁,因此我们在实践的时候选择了第二种架构方式。

微服务数量增加以后,服务之间的调用关系易产生耦合,甚至出现循环调用的情况,最好的应对方法是对服务进行分层,即将相互依赖的服务通过消息队列等技术进行异步解耦,减少服务间的依赖。

未分类

-服务分层-

微服务的具体实践

1. 技术选型

在实践中,我们的API Gateway使用的是OpenResty, OpenResty基于Nginx并扩展了对Lua的支持,可构建高并发的Web服务。我们通过HTTP接口实现客户端通信,数据基本封装成JSON格式,服务间的通信接口也是基于HTTP,并利用消息队列进行异步解耦;至于服务注册发现,我们使用的是Consul;我们选择了Lua(扩展API Gateway的功能),Node.js(用于开发后端服务),Java(用于密集计算和与大数据通信的场景)作为主要的开发语言。

2. 具体实现过程

未分类

在实践过程中,我们使用Lua开发了自己的微服务框架——WebLua,其封装服务之间的通信协议和访问外部资源(如Mysql、Redis等)的方法和依赖,同时提供了应用插槽。我们可以将每一个APP看成一个功能模块,每个APP都需要插到WebLua中才能运行。WebLua可以方便地将模块进行组合,既可以一个APP运行一个微服务,也可以多个APP一起对外提供服务。如此,开发者只需关注业务APP开发,很大程度上提高了开发效率。上图右侧是具体的代码目录结构,每个APP可分为Action,Page,Data三层,Action层在请求处理前后进行拦截,可做某些特殊处理,如请求前进行权限校验等;Page层主要对请求的参数进行解析和校验;Data层负责具体业务处理,同时提供了Shell脚本,可实现APP打包和部署安装。

二、 API网关

在架构中一个重要角色就是API网关,下面来做一个介绍。

未分类

从上面的对比图中可以看到,左侧是没有API Gateway的,很多的模块如Auth,Logging等,这些代码都需要自己去实现,造成了模块的重复建设,同时侵入了服务,功能扩展比较困难;右侧的图是使用了API Gateway之后的架构图,所有通用模块均在API Gateway实现,维护简单,一处建设,各处受益。在这种情况下,对API Gateway也提出了更高要求——其功能必须可以很方便地扩展。

为了实现这样的API网关,我们基于 OpenResty,借鉴了Kong和Orange的插件机制,通过插件来扩展API网关功能。

未分类

从上面的API Gateway架构图中可以看到,网关安装诸多插件,每个插件会在请求的一个或多个阶段发挥作用。插件配置会在Consul上更新,实时生效,插件规则可灵活配置。在操作中,我们为插件开发者提供了更多的自由,开发者可以自己定义格式。

三、容器化

在微服务落地实践时我们选择了Docker,下面将详细介绍个推基于Docker的实践。

首先网络组件选择的是Calico,服务注册发现和配置管理选择的是Consul。Consul-Template可实时监测Consul配置和服务的变化。

未分类

个推镜像体系是以CentOS为基础系统镜像,安装OpenResty,Nodejs,JDK,由此得到环境镜像,再在这个基础上安装微服务框架,获得Gorp镜像。再在这个基础上安装具体应用服务,得到应用服务镜像。

未分类

-服务注册发现和配置更新流程-

在API网关中,服务注册通过Consul-Agent来实现,配置更新通过Consul-Template实现。Consul-Template主要更新3类配置,包括:Services:代理的所有微服务的服务地址;Products:简言之即请求到微服务的映射表,如左上所示,所有请求都有统一个规范,从Host中可以获取Prod,从URI中可以获取APP,这 2个信息可将请求动态路由到具体服务;Nging-Conf:产品的Nginx配置。

未分类

应用服务容器,服务注册的方式跟API网关一致。首先,服务通过容器内部运行的Consul Agent将服务注册到Consul上,其次通过Consul-Template来监测观察 Consul上配置的变化,并更新配置文件。OpenResty或者WebNode配置的更新是直接覆盖相应的配置文件,然后重启对应的服务。

未分类

上图是个推基于Docker的集群架构,从中可看到,Docker集群包括3个节点,整个微服务分为3层,最上层是API Gateway,中间是业务层,最下层是一些多产品公用的基础的微服务。

四、Kubernetes实践

微服务虽然有很多好处,但也带来了很多问题,其中一个就是运维复杂。以前运维只需要面对一个单体应用即可,现在可能面临的是几十甚至上百的微服务。在这种情况下,我们需要借助Kubernetes来解决问题。Kubernetes是Google开源的一个容器编排工具,可用于协助管理容器。

一开始,我们将容器向Kubernetes集群迁移时,没做任何改变,只是采用Pod将所有的服务体系在Kubernetes集群运行。但随着深入使用Kubernetes,我们对微服务做了一些改变。

1.首先我们换成用Deployment的方式来部署服务,Deployment会保证服务时刻有一定的副本存活,提高了服务稳定性。

2.其次,我们使用了Service,它可以代理Pod实现负载的均衡。

  1. Kube-DNS可以将Service名解析成具体的ClusterIP,并且当Service没有删除重建时,其ClusterIP不变,如此DNS解析的缓存就不存在失效问题。基于Kube-DNS和Service的特性,后续我们改造了服务注册发现体系。

未分类

上图是我们当前的服务部署方式,Pod用Deployment的方式创建,用Service来进行代理。

在实践过程中,我们还遇到了另一个问题,即配置管理问题。

(1)微服务化后配置文件多而分散;

(2)不同环境之间有很多不必要的差异,如数据库名;

(3)在很多不同环境中,相同的配置项暴露给测试和运维;

(4)没有版本控制,回滚比较麻烦;

(5)基于Consul的Web UI无法对非法的输入进行校验。

针对这些问题我们做了以下调整:

(1) 统一不同环境间不必要的差异;

(2) 对配置文件进行模板化,只暴露差异部分,同时可实现不同配置文件集中配置;

(3)基于Consul开发配置中心,对产品配置集中管理;对输入进行合法性校验;增加版本控制,方便回滚。

未分类

-配置中心流程图-

未分类

关于日志服务,我们在应用容器中集成了Fluent-Bit,配置了2个输入源,TCP和tail, 输出也有2个,一个是Elasticsearch,所有的日志都会上传到ES通过Kibana展示查询,另一个是日志审计服务,有些需要进行审计的操作日志会发送到日志审计服务进行进一步的分析处理。

微服务数量增加以后,请求链路可能延长,开发者在追踪问题和排查性能瓶颈时会很不方便,因此我们引入了Zipkin,其主要用于分布式链路追踪,在API Gateway实现了一个插件进行Span收集,后端服务则通过开源的中间件来实现。

未分类

上图是个推目前的整体架构图,最底层是K8S集群,上面部署了Kube-DNS,Consul用于服务注册发现和配置管理,再者是我们分层的微服务体系,右侧是一些辅助的管理系统。

五、总结

上述是个推基于Docker和Kubernetes的整个微服务实践过程,我们在实践微服务过程中做了9件重要的事情, 简化了操作流程,提高了工作效率 。个推设计实现了自己的微服务框架,完成微服务的容器化部署,自研API网关,并基于Consul的服务注册和配置管理,使用Kubernetes对容器进行编排,基于Service和Kube-DNS对服务注册和发现体系进行改造,搭建了自己的配置中心,优化了日志服务,实现了Zipkin链路追踪。

在 Kubernetes 中手动部署 Prometheus

未分类

从今天开始我们就和大家一起来学习 Kubernetes 中监控系统的搭建,我们知道监控是保证系统运行必不可少的功能,特别是对于 Kubernetes 这种比较庞大的系统来说,监控报警更是不可或缺,我们需要时刻了解系统的各种运行指标,也需要时刻了解我们的 Pod 的各种指标,更需要在出现问题的时候有报警信息通知到我们。

在早期的版本中 Kubernetes 提供了 heapster、influxDB、grafana 的组合来监控系统,所以我们可以在 Dashboard 中看到 heapster 提供的一些图表信息,在后续的版本中会陆续移除掉 heapster,现在更加流行的监控工具是 prometheus,prometheus 是 Google 内部监控报警系统的开源版本,是 Google SRE 思想在其内部不断完善的产物,它的存在是为了更快和高效的发现问题,快速的接入速度,简单灵活的配置都很好的解决了这一切,而且是已经毕业的 CNCF 项目。

这里推荐一本书了解 Goolge 运维的秘密:《SRE: Google运维解密》

简介

Prometheus 最初是 SoundCloud 构建的开源系统监控和报警工具,是一个独立的开源项目,于2016年加入了 CNCF 基金会,作为继 Kubernetes 之后的第二个托管项目。

特征

Prometheus 相比于其他传统监控工具主要有以下几个特点:

  • 具有由 metric 名称和键/值对标识的时间序列数据的多维数据模型
  • 有一个灵活的查询语言
  • 不依赖分布式存储,只和本地磁盘有关
  • 通过 HTTP 的服务拉取时间序列数据
  • 也支持推送的方式来添加时间序列数据
  • 还支持通过服务发现或静态配置发现目标
  • 多种图形和仪表板支持

组件

Prometheus 由多个组件组成,但是其中许多组件是可选的:

  • Prometheus Server:用于抓取指标、存储时间序列数据
  • exporter:暴露指标让任务来抓
  • pushgateway:push 的方式将指标数据推送到该网关
  • alertmanager:处理报警的报警组件
  • adhoc:用于数据查询

大多数 Prometheus 组件都是用 Go 编写的,因此很容易构建和部署为静态的二进制文件。

架构

下图是 Prometheus 官方提供的架构及其一些相关的生态系统组件:

未分类

整体流程比较简单,Prometheus 直接接收或者通过中间的 Pushgateway 网关被动获取指标数据,在本地存储所有的获取的指标数据,并对这些数据进行一些规则整理,用来生成一些聚合数据或者报警信息,Grafana 或者其他工具用来可视化这些数据。

安装

由于 Prometheus 是 Golang 编写的程序,所以要安装的话也非常简单,只需要将二进制文件下载下来直接执行即可,前往地址:https://prometheus.io/download 下载我们对应的版本即可。

Prometheus 是通过一个 YAML 配置文件来进行启动的,如果我们使用二进制的方式来启动的话,可以使用下面的命令:

$ ./prometheus --config.file=prometheus.yml

其中 prometheus.yml 文件的基本配置如下:

global:
  scrape_interval: 15s  
  evaluation_interval: 15s
rule_files:
  # - "first.rules"
  # - "second.rules"
scrape_configs:
  - job_name: prometheus    
    static_configs:
      - targets: ['localhost:9090']

上面这个配置文件中包含了3个模块:global、rule_files 和 scrape_configs。

其中 global 模块控制 Prometheus Server 的全局配置:

  • scrape_interval:表示 prometheus 抓取指标数据的频率,默认是15s,我们可以覆盖这个值
  • evaluation_interval:用来控制评估规则的频率,prometheus 使用规则产生新的时间序列数据或者产生警报

rule_files 模块制定了规则所在的位置,prometheus 可以根据这个配置加载规则,用于生成新的时间序列数据或者报警信息,当前我们没有配置任何规则。

scrape_configs 用于控制 prometheus 监控哪些资源。由于 prometheus 通过 HTTP 的方式来暴露的它本身的监控数据,prometheus 也能够监控本身的健康情况。在默认的配置里有一个单独的 job,叫做prometheus,它采集 prometheus 服务本身的时间序列数据。这个 job 包含了一个单独的、静态配置的目标:监听 localhost 上的9090端口。prometheus 默认会通过目标的/metrics路径采集 metrics。所以,默认的 job 通过 URL:http://localhost:9090/metrics采集 metrics。收集到的时间序列包含 prometheus 服务本身的状态和性能。如果我们还有其他的资源需要监控的话,直接配置在该模块下面就可以了。

由于我们这里是要跑在 Kubernetes 系统中,所以我们直接用 Docker 镜像的方式运行即可。

为了方便管理,我们将所有的资源对象都安装在kube-ops的 namespace 下面,没有的话需要提前安装。

为了能够方便的管理配置文件,我们这里将 prometheus.yml 文件用 ConfigMap 的形式进行管理:(prometheus-cm.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config  
  namespace: kube-ops
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      scrape_timeout: 15s
    scrape_configs:
    - job_name: 'prometheus'
      static_configs:
      - targets: ['localhost:9090']

我们这里暂时只配置了对 prometheus 的监控,然后创建该资源对象:

$ kubectl create -f prometheus-cm.yaml
configmap "prometheus-config" created

配置文件创建完成了,以后如果我们有新的资源需要被监控,我们只需要将上面的 ConfigMap 对象更新即可。现在我们来创建 prometheus 的 Pod 资源:(prometheus-deploy.yaml)

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: prometheus  
  namespace: kube-ops  
  labels:
    app: prometheus
spec:
  template:
    metadata:
      labels:
        app: prometheus    
    spec:
      serviceAccountName: prometheus      
      containers:
      - image: prom/prometheus:v2.4.3        
        name: prometheus        
        args:
        - "--config.file=/etc/prometheus/prometheus.yml"
        - "--storage.tsdb.path=/prometheus"
        - "--storage.tsdb.retention=24h"
        - "--web.enable-admin-api"  # 控制对admin HTTP API的访问,其中包括删除时间序列等功能
        - "--web.enable-lifecycle"  # 支持热更新,直接执行localhost:9090/-/reload立即生效
        ports:
        - containerPort: 9090
          protocol: TCP          
          name: http        
        volumeMounts:
        - mountPath: "/prometheus"
          subPath: prometheus          
          name: data        
        - mountPath: "/etc/prometheus"
          name: config-volume        
        resources:
          requests:
            cpu: 100m            
            memory: 512Mi          
          limits:
            cpu: 100m            
          memory: 512Mi      
      securityContext:
        runAsUser: 0
      volumes:
      - name: data        
        persistentVolumeClaim:
          claimName: prometheus      
      - configMap:
          name: prometheus-config        
        name: config-volume

我们在启动程序的时候,除了指定了 prometheus.yml 文件之外,还通过参数storage.tsdb.path指定了 TSDB 数据的存储路径、通过storage.tsdb.retention设置了保留多长时间的数据,还有下面的web.enable-admin-api参数可以用来开启对 admin api 的访问权限,参数web.enable-lifecycle非常重要,用来开启支持热更新的,有了这个参数之后,prometheus.yml 配置文件只要更新了,通过执行localhost:9090/-/reload就会立即生效,所以一定要加上这个参数。

未分类

我们这里将 prometheus.yml 文件对应的 ConfigMap 对象通过 volume 的形式挂载进了 Pod,这样 ConfigMap 更新后,对应的 Pod 里面的文件也会热更新的,然后我们再执行上面的 reload 请求,Prometheus 配置就生效了,除此之外,为了将时间序列数据进行持久化,我们将数据目录和一个 pvc 对象进行了绑定,所以我们需要提前创建好这个 pvc 对象:(prometheus-volume.yaml)

apiVersion: v1
kind: PersistentVolume
metadata:
  name: prometheus
spec:
  capacity:
    storage: 10Gi  
  accessModes:
  - ReadWriteOnce  
  persistentVolumeReclaimPolicy: Recycle  
  nfs:
    server: 10.151.30.57    
    path: /data/k8s

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: prometheus  
  namespace: kube-ops
spec:
  accessModes:
  - ReadWriteOnce  
  resources:
    requests:
      storage: 10Gi

我们这里简单的通过 NFS 作为存储后端创建一个 pv、pvc 对象:

$ kubectl create -f prometheus-volume.yaml

除了上面的注意事项外,我们这里还需要配置 rbac 认证,因为我们需要在 prometheus 中去访问 Kubernetes 的相关信息,所以我们这里管理了一个名为 prometheus 的 serviceAccount 对象:(prometheus-rbac.yaml)

apiVersion: v1
kind: ServiceAccount
metadata:
  name: prometheus  
  namespace: kube-ops

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus
rules:
- apiGroups: [""]
  resources:
  - nodes  
  - services  
  - endpoints  
  - pods  
  - nodes/proxy  
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources:
  - configmaps  
  verbs: ["get"]
- nonResourceURLs: ["/metics"]  # 对非资源型 endpoint metrics 进行 get 操作
  verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: prometheus
roleRef:
  apiGroup: rbac.authorization.k8s.io  
  kind: ClusterRole  
  name: prometheus
subjects:
- kind: ServiceAccount  
  name: prometheus  
  namespace: kube-ops

由于我们要获取的资源信息,在每一个 namespace 下面都有可能存在,所以我们这里使用的是 ClusterRole 的资源对象,值得一提的是我们这里的权限规则声明中有一个nonResourceURLs的属性,是用来对非资源型 metrics 进行操作的权限声明,这个在以前我们很少遇到过,然后直接创建上面的资源对象即可:

$ kubectl create -f prometheus-rbac.yaml
serviceaccount "prometheus" created
clusterrole.rbac.authorization.k8s.io "prometheus" created
clusterrolebinding.rbac.authorization.k8s.io "prometheus" created

还有一个要注意的地方是我们这里必须要添加一个securityContext的属性,将其中的runAsUser设置为0,这是因为现在的 prometheus 运行过程中使用的用户是 nobody,否则会出现下面的permission denied之类的权限错误:

level=error ts=2018-10-22T14:34:58.632016274Z caller=main.go:617 err="opening storage failed: lock DB directory: open /data/lock: permission denied"

现在我们就可以添加 promethues 的资源对象了:

$ kubectl create -f prometheus-deploy.yaml
deployment.extensions "prometheus" created
$ kubectl get pods -n kube-ops
NAME                          READY     STATUS    RESTARTS   AGE
prometheus-6dd775cbff-zb69l   1/1       Running   0          20m
$ kubectl logs -f prometheus-6dd775cbff-zb69l -n kube-ops......level=info ts=2018-10-22T14:44:40.535385503Z caller=main.go:523 msg="Server is ready to receive web requests."

Pod 创建成功后,为了能够在外部访问到 prometheus 的 webui 服务,我们还需要创建一个 Service 对象:(prometheus-svc.yaml)

apiVersion: v1
kind: Service
metadata:
  name: prometheus  
  namespace: kube-ops  
  labels:
    app: prometheus
spec:
  selector:
    app: prometheus  
  type: NodePort  
  ports:
  - name: web      
    port: 9090
    targetPort: http

为了方便测试,我们这里创建一个NodePort类型的服务,当然我们可以创建一个Ingress对象,通过域名来进行访问:

$ kubectl create -f prometheus-svc.yamlservice "prometheus" created
$ kubectl get svc -n kube-ops
NAME         TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)                          AGE
prometheus   NodePort   10.111.118.104   <none>        9090:30987/TCP                   24s

然后我们就可以通过http://任意节点IP:30987访问 prometheus 的 webui 服务了。

未分类

为了数据的一致性,prometheus 所有的数据都是使用的 UTC 时间,所以我们默认打开的 dashboard 中有这样一个警告,我们需要在查询的时候指定我们当前的时间才可以。然后我们可以查看当前监控系统中的一些监控目标:

未分类

由于我们现在还没有配置任何的报警信息,所以 Alerts 菜单下面现在没有任何数据,隔一会儿,我们可以去 Graph 菜单下面查看我们抓取的 prometheus 本身的一些监控数据了,其中- insert metrics at cursor -下面就是我们搜集到的一些监控数据指标:

未分类

比如我们这里就选择scrape_duration_seconds这个指标,然后点击Execute,如果这个时候没有查询到任何数据,我们可以切换到Graph这个 tab 下面重新选择下时间,选择到当前的时间点,重新执行,就可以看到类似于下面的图表数据了:

未分类

除了简单的直接使用采集到的一些监控指标数据之外,这个时候也可以使用强大的 PromQL 工具,PromQL其实就是 prometheus 便于数据聚合展示开发的一套 ad hoc 查询语言的,你想要查什么找对应函数取你的数据好了。

如何在Kubernetes里创建一个Nginx应用

使用命令行kubectl run --image=nginx nginx-app --port=80 创建一个名为nginx-app的应用

未分类

结果: deployment.apps/nginx-app created

使用命令行kubectl get pods查看创建结果,状态已经为running:

未分类

使用命令行kubectl describe pods查看pod明细:

未分类

未分类

未分类

把pod id记下来: nginx-app-f75d46bd9-q6c76

使用该pod id可以执行一些命令:

  • kubectl exec nginx-app-f75d46bd9-q6c76 ps aux

  • kubectl describe pod nginx-app-f75d46bd9-q6c76

  • kubectl logs nginx-app-f75d46bd9-q6c76

未分类

未分类

kubernetes pvc与nfs

此前在存储卷类型中介绍过pvc,以及其他的存储卷类型,本节笔记介绍pvc和nfs的使用

PVC

在pod中只需要定义存储卷,定义时只需要说明需要用到的大小,这个类型就是pvcl类型存储卷
而pvc存储卷必须与当前名称空间中的pvc,建立直接绑定关系,而pvc必须与pv建立绑定关系,而pv则是某个真正存储设备的存储空间,如下:

未分类

pv与pvs是kubernetes上抽象的,且标准的资源,与创建其他的pod中资源一样

用法

在存储类型中有很多存储类型被划分成预想的存储空间,这些存储空间被kubernetes映射到pv,在创建资源时,定义pod,且定义pod中的pvc即可。前提是pvc已经被创建好。pvc与pv之间没有被调用则空载,当pvc调用时则会与某个pv进行绑定(数据存放在pv之上)

pvc绑定那个pv取决于pod创建的用户定义volumes请求,如下:

1. 满足空间大小则绑定,不满足则不绑定
​2. 满足访问类型:单读,单写,单读/写(pv只有)。多读写,多读,多写,

这样一来就分成大小三组来定义kubernetes。如下:

未分类

对于存储设备由存储管理员进行管理,pv与pvc由k8s集群管理员或者用户来管理。集群管理员将存储空间引入到集群内定义为PV,随后用户(创建pod用户)创建pvc(pv create),(创建pod之前创建pvc)pvc根据配置信息查找合适pv申请(如果没有适当的pv则绑定失败,直到创建一个合适的符合条件的pv)。

  • pv与pvc是一 一对应的,一旦pv被某个pvc占用,显示绑定状态,就不会被其他pvc使用。但是,一旦pvc创建完成(与pv绑定),pvc就相当于一个存储卷,而存储卷(绑定过pv的pvc)能够被多个pod使用。而多个pod挂载一个pvc存储卷,这种方式就是多路访问(是否支持取决于accessMode)。

claimName: pvc名称
可以使用kubectl explain pvc查看标准使用,其中pvc kind是PersistentVolumeClaim,在spec中需要指明:

accessMode(访问模型。是否支持多人读写,或者单人读写等。accessmode支持列表,意味着可以支持多中模式。这里定义的权限只能是存储设备的子集)
ReadWriteOnce – 卷可以由单个节点以读写方式挂载
ReadOnlyMany – 卷可以由许多节点以只读方式挂载
ReadWriteMany – 卷可以由许多节点以读写方式挂载
​ RWO – 单路读写
​ ROX – 多路只读
​ RWX – 多路读写
并不是每个卷都支持多路,参考官网的表格,NFS多路单路都支持
resources:资源限制。(至少多少空间)
selector: 标签选择器。使用标签选择器进行绑定
storageClassName:存储类名称
volumeMode: 后端存储卷模式。PV类型限制。如果有合适的pv则会绑定,如果没有则会挂起
volumeName: 存储卷名称。填写则直接绑定。
capacity : 指定空间大小通过资源访问模型进行定义,关注resources,我们可以定义成Ei,Pi,Ti,Gi,Mi,Ki

storage: 5Gi表示5G

apiSersion: v1
kind: PersistentVolume
metadata:
  name: linuxea-5
  labels: 
    name: v5
spec:
  nfs:
    path: /data/volumes/linuxea-5
    server: 10.0.1.61
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 5Gi

创建

1. 准备存储(NFS)

10.0.1.61 创建几个目录进行单独的挂载,分别是linuxea-1-5

[root@Linuxea-VM-Node_10_0_1_61 /data/linuxea-volumes]# mkdir linuxea-{1,2,3,4,5}
[root@Linuxea-VM-Node_10_0_1_61 /data/linuxea-volumes]# ls
index.html  linuxea-1  linuxea-2  linuxea-3  linuxea-4  linuxea-5  linuxea.html
[root@Linuxea-VM-Node_10_0_1_61 /data/linuxea-volumes]# cat /etc/exports
/data/linuxea-volumes/linuxea-1 10.0.0.0/8(rw,no_root_squash)
/data/linuxea-volumes/linuxea-2 10.0.0.0/8(rw,no_root_squash)
/data/linuxea-volumes/linuxea-3 10.0.0.0/8(rw,no_root_squash)
/data/linuxea-volumes/linuxea-4 10.0.0.0/8(rw,no_root_squash)
/data/linuxea-volumes/linuxea-5 10.0.0.0/8(rw,no_root_squash)
[root@Linuxea-VM-Node_10_0_1_61 /data/linuxea-volumes]# exportfs -arv
exporting 10.0.0.0/8:/data/linuxea-volumes/linuxea-5
exporting 10.0.0.0/8:/data/linuxea-volumes/linuxea-4
exporting 10.0.0.0/8:/data/linuxea-volumes/linuxea-3
exporting 10.0.0.0/8:/data/linuxea-volumes/linuxea-2
exporting 10.0.0.0/8:/data/linuxea-volumes/linuxea-1
[root@Linuxea-VM-Node_10_0_1_61 /data/linuxea-volumes]# showmount -e
Export list for Linuxea-VM-Node_10_0_1_61.dwhd.org:
/data/linuxea-volumes/linuxea-5 10.0.0.0/8
/data/linuxea-volumes/linuxea-4 10.0.0.0/8
/data/linuxea-volumes/linuxea-3 10.0.0.0/8
/data/linuxea-volumes/linuxea-2 10.0.0.0/8
/data/linuxea-volumes/linuxea-1 10.0.0.0/8

2. 创建pv

这里定义pv时候,不能定义在namespace。pv是集群级别的,不在名称空间中,所有的名称空间都可以用。但是pvc是属于名称空间的

namespace不能嵌套,namespcace属于集群资源 ,不能定义在名称空间中。所谓集群资源就是不能定义在名称空间中的。而pod,service必须定义在名称空间中。属于名称空间级别

我们之前,事先分别定义了5个pv,分别是1-5Gi的pv大小,支持单点读写和多路读写(稍后方便写入数据测试)。yaml如下:

[root@linuxea volume]# cat pv-demo.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: linuxea-1
  labels: 
    name: v1
spec:
  nfs:
    path: /data/volumes/linuxea-1
    server: 10.0.1.61
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: linuxea-2
  labels: 
    name: v2
spec:
  nfs:
    path: /data/volumes/linuxea-2
    server: 10.0.1.61
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: linuxea-3
  labels: 
    name: v3
spec:
  nfs:
    path: /data/volumes/linuxea-3
    server: 10.0.1.61
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 3Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: linuxea-4
  labels: 
    name: v4
spec:
  nfs:
    path: /data/volumes/linuxea-4
    server: 10.0.1.61
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: linuxea-5
  labels: 
    name: v5
spec:
  nfs:
    path: /data/volumes/linuxea-5
    server: 10.0.1.61
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 5Gi

定义完成后apply启动

[root@linuxea volume]# kubectl apply -f pv-demo.yaml 
persistentvolume/linuxea-1 created
persistentvolume/linuxea-2 created
persistentvolume/linuxea-3 created
persistentvolume/linuxea-4 created
persistentvolume/linuxea-5 created

启动后使用kubectl get pv查看状态信息

[root@linuxea volume]# kubectl get pv
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
linuxea-1   1Gi        RWO,RWX        Retain           Available                                      3s
linuxea-2   2Gi        RWO,RWX        Retain           Available                                      3s
linuxea-3   3Gi        RWO,RWX        Retain           Available                                      3s
linuxea-4   4Gi        RWO,RWX        Retain           Available                                      3s
linuxea-5   5Gi        RWO,RWX        Retain           Available                                      3s

其中RECLAIM POLICY 叫回收策略:当一个pvc绑定某个pv,并且存储了数据,如果pv删掉,那么绑定就会失效。删除前存放有数据就会丢失,Retain保留。Recycle(回收),删除数据,将pv制空,让其他pv绑定。当然还有delelte,默认delete。参考https://kubernetes.io/docs/tasks/administer-cluster/change-pv-reclaim-policy/。Available表示当前处于可用状态。

3. 创建pvc

resources的选择是要大于等于定义的resources值
当创建好pvc后,就算删除pvc,pod数据也会保留,因为pv的回收策略是Retain(取决于回收策略),也不会删除。因此,只要不删除pvc,而删除pod,就算pv策略是Recycle(回收),也不会被删除数据。

pvc是标准的k8s资源,存储在etcd中,只要etcd没有问题,pvc就没有问题,而pod是节点资源,运行在节点之上。其他资源保存在api server集群状态存储etcd当中。

  • 在最新的版本中pv被绑定的状态下是不能够单独删除的。kubectl delete pv PVNAME
    PersistentVolumeClaim
    是独特的kind,pvc需要定义namespace
    在pvc中的accessModes是pv accessModes的子集,也就是说pv有权限,这里才有权限。在之前定义了PV的大小是1-5Gi,在这里定义的spec中storage: 5Gi(这里打大小至少要大于等于才行),假如这里定义的大小在pv中不存在则会失效。这里仍然可以使用标签管理器管理。而后在pod中定义pvc名称和volumes挂载到相应的目录

  • 我们事先已经定了pv大小,现在才能进行绑定

yaml

[root@linuxea volume]# cat pvc-demo.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: linuxea-pvc
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]
  resources: 
    requests: 
      storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata: 
  name: linuxea-pvc-pod
  namespace: default
spec:
  containers:
  - name: linuxea-pod1-pvc
    image: "marksugar/nginx:1.14.a"
    ports:
      - containerPort: 88
    volumeMounts:
    - name: linuxea-image
      mountPath: /data/wwwroot/
  volumes:
  - name: linuxea-image
    persistentVolumeClaim:
      claimName: linuxea-pvc

apply创建

[root@linuxea volume]# kubectl apply -f pvc-demo.yaml 
persistentvolumeclaim/linuxea-pvc created
pod/linuxea-pvc-pod created

可使用kubectl get pvc查看已经创建好的pvc已经被Bound

[root@linuxea volume]# kubectl get pvc
NAME          STATUS    VOLUME      CAPACITY   ACCESS MODES   STORAGECLASS   AGE
linuxea-pvc   Bound     linuxea-5   5Gi        RWO,RWX                       6s

以及pod

[root@linuxea volume]# kubectl get pods -o wide
NAME              READY     STATUS    RESTARTS   AGE       IP           NODE                 NOMINATED NODE
linuxea-pvc-pod   1/1       Running   0          22s       172.16.4.7   linuxea.node-2.com   <none>

而后创建pvc之后,可查看pv已经被绑定到linuxea-5上的pv上(大于等于5G)

[root@linuxea volume]# kubectl get pv
NAME        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                 STORAGECLASS   REASON    AGE
linuxea-1   1Gi        RWO,RWX        Retain           Available                                                  2m
linuxea-2   2Gi        RWO,RWX        Retain           Available                                                  2m
linuxea-3   3Gi        RWO,RWX        Retain           Available                                                  2m
linuxea-4   4Gi        RWO,RWX        Retain           Available                                                  2m
linuxea-5   5Gi        RWO,RWX        Retain           Bound       default/linuxea-pvc                            2m

也可以使用kubectl describe pods linuxea-pvc-pod|grep root查看信息

[root@linuxea volume]# kubectl describe pods linuxea-pvc-pod|grep root
      /data/wwwroot/ from linuxea-image (rw)

pv写入测试

在集群内访问

[root@linuxea volume]# curl 172.16.4.7
linuxea-linuxea-pvc-pod.com-127.0.0.1/8 172.16.4.7/24

而后回到nfs修改

[root@Linuxea-VM-Node_10_0_1_61 ~]# echo `date` >> /data/linuxea-volumes/linuxea-5/index.html 

在集群内第二次访问查看

[root@linuxea volume]# curl 172.16.4.7
linuxea-linuxea-pvc-pod.com-127.0.0.1/8 172.16.4.7/24
2018年 09月 23日 星期日 18:22:49 CST
[root@linuxea volume]# 

由此可见,创建多大的pv,可能需要事先设定好,pvc才能适配,这种方式有些麻烦,可以考虑使用动态供给

如何使用 Kubernetes 轻松部署深度学习模型

未分类

这简直太容易了,连你的老板都能做到!

本文展示了如何用 Keras 构建深度学习模型的简单示例,将其作为一个用 Flask 实现的 REST API,并使用 Docker 和 Kubernetes 进行部署。本文给出的并不是一个鲁棒性很好的能够用于生产的示例,它只是为那些听说过 Kubernetes 但没有动手尝试过的人编写的快速上手指南。

为此,我在这个过程的每个步骤中都使用了 Google Cloud。这样做的原因很简单——我并不想在我的 Windows 10 家用笔记本上安装 Docker 和 Kubernetes。而谷歌云能很好地支持这二者的工作。此外,你可以准确地按照我接下来所使用的规范流程进行操作,这可以帮助你更容易地复现我的步骤。而且,你也不用担心实践这篇文章的成本。谷歌为新账户提供了几百美金的免费额度,而实现本文示例所需的费用只不过是九牛一毛。

为什么要将 Kubernetes 用于机器学习和数据科学?

Kubernetes 及其从属的流行概念「云原生」(cloud-native)正席卷全球。别担心——你有所怀疑是对的。我们都见证过铺天盖地的「人工智能」、「大数据」、「云计算」等术语的技术泡沫。Kubernetes 是否也会发生相同的情况,还有待观察。

但是,如今许多对数据科学一知半解的人会误导我们,所以我对转而使用 Kubernetes 的原因并不感兴趣也不理解。我的动机很简单,我希望部署、扩展、管理一个能够提供预测能力的 REST API。在下文中,你会看到 Kubernetes 会使这一切非常容易。

让我们开始吧!

大纲

  1. 使用 Google Cloud 创建你的环境。

  2. 使用 Keras、Flask 和 Docker 提供深度学习模型接口。

  3. 使用 Kubernetes 部署上述模型。

  4. 享受你所掌握的新知识吧!

步骤 1:使用 Google Cloud 创建你的环境

我在谷歌计算引擎上使用一个小型虚拟机来构建、部署、docker 化深度学习模型。你并不一定非要这么做。我曾试过在我的 Windows 10 笔记本上安装最新版本的 Docker CE(Community Edition),但是失败了。因此我决定直接使用免费的 Google Cloud 额度,这比弄清如何安装 Docker 能更好地利用我的时间。你可以选择是否要这样做。

未分类

要想启动一台 Google Cloud 虚拟机,你可以打开屏幕左侧的工具栏。选择 Compute Engine。接着,选择「Create Instance」。如下图所示,我已经拥有了一个正在工作的虚拟机实例。

未分类

下一步,你需要选择你想要使用的计算规模。默认的(最便宜的)机器设置也可以很好地工作,但是考虑到我们最多只需要使用这个虚拟机大约 1 小时,我选择了内存为 15GB 的 4vCPU 配置。

未分类

接下来,我将选择要使用的操作系统和磁盘空间。选择「Boot Disk」来编辑默认值。这里我选择了 Centos 7 作为操作系统,并将磁盘的大小从 10GB 增加到了 100GB。实际上,并不一定要像我一样选择 Centos 操作系统。但是,我建议将磁盘大小增加到 10GB 以上,因为我们创建的每个 Docker 容器的大小都大约为 1GB。

未分类

创建虚拟机的最后一步是设置防火墙允许使用 HTTP/S。诚然,我并不知道是否需要这个步骤。在部署 Kubernetes 之前,我将展示如何编辑防火墙设置以在虚拟机上测试我们的 API。因此,仅仅查看这些对话框是不够的,我们还有更多的工作要做。以前我并没有查看这些对话框,现在我重新试试按照这个教程去做。

未分类

我并不确定是否需要此步骤。

现在单击「Creat」按钮。很好,困难的部分基本上已经完成了。

未分类

步骤 2:使用 Keras 构建深度学习模型

现在,让我们使用 SSH 连接到虚拟机,并开始构建模型。最简单的方法是单击下图所示的虚拟机旁边的 SSH 图标。这个操作会在你的浏览器中打开一个终端。

未分类

1. 卸载已有的 Docker 版本

sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine

请注意,如果你使用的操作系统不是 Centos 7,指令可能不同。

2. 安装最新版 Docker

sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager — add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce

3. 启动 Docker 并运行测试脚本

sudo systemctl start docker
sudo docker run hello-world

如果你看到下图所示的返回结果,你就完成了 Docker 的部署。

Hello from Docker!
This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.

4. 创建我们的深度学习模型

我们将复制一段 Adrian Rosebrock 写的脚本。Adrian 写了一篇很棒的教程,关于如何利用 Keras 构建深度学习模型并使用 Flask 部署它。教程参见:https://blog.keras.io/building-a-simple-keras-deep-learning-rest-api.html

我们需要对 Adrian 的脚本进行两处关键的修改,才能使其运行。如果你不关心 Docker 和 TensorFlow 的技术细节,请跳过下面两段。

我们要修改的第一个地方与 Docker 有关。在本地运行应用程序时,默认的 flask behavior 会在本地主机(127.0.0…)上提供应用程序服务。在 Docker 容器内运行时,这可能会产生一些问题。解决的方法很简单。当调用 app.run() 时,使用 app.run(host=’0.0.0.0′) 将 URL 设置为 0.0.0.0。这样,我们的应用就可以在本地主机和外部 IP 上同时使用了。

下一个问题涉及 TensorFlow。当我运行 Adrian 的原始脚本时,我无法成功调用模型。于是,我阅读了下面这个 Github issue(https://github.com/tensorflow/tensorflow/issues/14356),并对代码进行了修改。

global graph
graph = tf.get_default_graph()
...
with graph.as_default():
 preds = model.predict(image)

说实话,我也不知道为什么这样做就行得通。但它确实做到了。所以就这样运行吧。

首先,创建一个名为 keras-app 的新文件夹,并将当前的路径移动到该文件夹中。

mkdir keras-app
cd keras-app

现在我们创建一个名为 app.py 的文件。你可以自己选择要使用的编辑器。我在这里选用 vim,输入下面的指令创建并打开 app.py:

vim app.py

打开文件后,敲击键盘上的「i」键,进入插入模式。现在你可以把下面的代码粘贴进去:

# USAGE
# Start the server:
# python app.py
# Submit a request via cURL:
# curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict'

# import the necessary packages
from keras.applications import ResNet50
from keras.preprocessing.image import img_to_array
from keras.applications import imagenet_utils
from PIL import Image
import numpy as np
import flask
import io
import tensorflow as tf

# initialize our Flask application and the Keras model
app = flask.Flask(__name__)
model = None

def load_model():
 # load the pre-trained Keras model (here we are using a model
 # pre-trained on ImageNet and provided by Keras, but you can
 # substitute in your own networks just as easily)
 global model
 model = ResNet50(weights="imagenet")
 global graph
 graph = tf.get_default_graph()

def prepare_image(image, target):
 # if the image mode is not RGB, convert it
 if image.mode != "RGB":
 image = image.convert("RGB")

 # resize the input image and preprocess it
 image = image.resize(target)
 image = img_to_array(image)
 image = np.expand_dims(image, axis=0)
 image = imagenet_utils.preprocess_input(image)

 # return the processed image
 return image

@app.route("/predict", methods=["POST"])
def predict():
 # initialize the data dictionary that will be returned from the
 # view
 data = {"success": False}

 # ensure an image was properly uploaded to our endpoint
 if flask.request.method == "POST":
 if flask.request.files.get("image"):
 # read the image in PIL format
 image = flask.request.files["image"].read()
 image = Image.open(io.BytesIO(image))

 # preprocess the image and prepare it for classification
 image = prepare_image(image, target=(224, 224))

 # classify the input image and then initialize the list
 # of predictions to return to the client
 with graph.as_default():
 preds = model.predict(image)
 results = imagenet_utils.decode_predictions(preds)
 data["predictions"] = []

 # loop over the results and add them to the list of
 # returned predictions
 for (imagenetID, label, prob) in results[0]:
 r = {"label": label, "probability": float(prob)}
 data["predictions"].append(r)

 # indicate that the request was a success
 data["success"] = True

 # return the data dictionary as a JSON response
 return flask.jsonify(data)

# if this is the main thread of execution first load the model and
# then start the server
if __name__ == "__main__":
 print(("* Loading Keras model and Flask starting server..."
 "please wait until server has fully started"))
 load_model()
 app.run(host='0.0.0.0')

当你复制以上代码后,敲击「Esc」键退出插入模式。

然后输入 :x,保存并关闭文件。

5. 创建一个 requirements.txt 文件

现在回到正题。我们将在 Docker 容器中运行这段代码。为了做到这一点,我们首先要创建一个 requirements.txt 文件。这个文件将包含代码需要运行的程序包(如 keras、flask 等)。这样一来,无论我们将 Docker 容器装载在哪里,底层的服务器都能够安装代码所需的依赖。

keras
tensorflow
flask
gevent
pillow
requests

6. 创建 Dockerfile

很好!现在让我们创建 Dockerfile。这是 Docker 将要读取的文件,用它来构建和运行我们的项目。

FROM python:3.6
WORKDIR /app
COPY requirements.txt /app
RUN pip install -r ./requirements.txt
COPY app.py /app
CMD ["python", "app.py"]~

现在,我们正引导 Docker 下载一个 Python 3 的基础镜像。然后,要求 Docker 使用 Python 程序包管理器 pip 安装 requirements.txt 文件中详细指定的包。

接着,我们让 Docker 通过 python app.py 指令运行我们的脚本。

7. 创建 Docker 容器

目前一切进展顺利,现在让我们构建并测试我们的应用程序。

为了构建我们的 Docker 容器,我们需要运行如下指令:

sudo docker build -t keras-app:latest .

该指令将引导 Docker 为我们当前工作空间的文件夹 keras-app 中的代码构建一个容器。

这个指令需要一到两分钟才能运行完成。在此过程中,Docker 会下载一个 python 3.6 的镜像并且安装 requirements.txt 中列出的包。

8. 运行 Docker 容器

现在,让我们运行 Docker 容器来测试我们的应用程序。

sudo docker run -d -p 5000:5000 keras-app

注:通过上面的指令中的数字 5000:5000,我们告诉 Docker 让端口 5000 处于外部可用状态,并把我们的本地应用程序指向该端口(它也在端口 5000 上本地运行)。

你可以通过运行 sudo docker ps -a 查看 Docker 容器的状态。你应该看到如下图所示的结果:

[gustafcavanaugh@instance-3 ~]$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d82f65802166 keras-app "python app.py" About an hour ago Up About an hour 0.0.0.0:5000->5000/tcp nervous_northcutt

9. 测试模型

我们的模型能够成功运行后,是时候测试一下它的性能了。该模型将狗的图片作为输入,并返回狗的品种。在 Adrian 的 repo 中,他提供了一个示例图片,我们在这里也将使用它。

未分类

在终端中运行:

curl -X POST -F image=@dog.jpg 'http://localhost:5000/predict'

确保你当前的文件夹中有狗狗的图片「dog.jpg」(或提供正确的文件路径),你会看到下面的运行结果:

{"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true}

我们可以看到,模型正确地将狗狗分类为小猎犬。太棒了!你已经成功地用 Keras 运行了一个预训练好的深度学习模型,并且使用 Flask 部署其服务、用 Docker 将其封装了起来。至此,我们已经完成了困难的部分。现在让我们用 Kubernetes 部署该容器。

步骤 3:用 Kubernetes 部署我们的模型

1. 创建一个 Docker Hub 账户(如果你没有的话)

我们要做的第一件事是将模型上传到 Docker Hub 上。如果你还没有 Docker 账户,请创建一个,别担心,这是免费的。我们这样做的原因是,我们不会将容器物理移动到 Kubernetes 集群上,而是引导 Kubernetes 在集中托管服务器(即 Docker Hub)上安装我们的容器。

2. 登录 Docker Hub 账户

创建好 Docker Hub 账户后,你可以通过 sudo docker login 指令从命令行登录。你需要提供用户名、密码,就像你登录网站一样。

如果你看到下面的信息:

Login Succeeded

那么你就成功登录了。现在让我们进行下一步。

3. 对容器命名

在上传容器之前,我们需要为容器打标签。你可以将此步骤看做为容器命名。

首先,运行 sudo docker images,并定位 keras-app 容器的镜像 id。

输出应该如下所示:

REPOSITORY TAG IMAGE ID CREATED SIZE keras-app latest ddb507b8a017 About an hour ago 1.61GB

现在,我们可以为 keras-app 打标签了。请务必遵循我的格式,并将镜像 id 和 docker hub id 的值替换为你自己指定的值。

#Format
sudo docker tag <your image id> <your docker hub id>/<app name>
#My Exact Command - Make Sure To Use Your Inputs
sudo docker tag ddb507b8a017 gcav66/keras-app

4. 将容器 push 到 Docker Hub 上

现在可以 push 我们的容器了。在 shell 中运行以下行:

#Format
sudo docker push <your docker hub name>/<app-name>
#My exact command
sudo docker push gcav66/keras-app

现在,如果你返回到 Docker Hub 网站,你就可以看到你的 keras-app repo 了。很好,接下来,我们将进入最后一步。

5. 创建一个 Kubernetes 集群

在 Google Cloud 的主页上选择 Kubernetes Engine:

未分类

接着创建一个新的 Kubernetes 集群:

未分类

接下来,我们将自定义该集群中节点的规模。我选择了内存为 15GB、4vCPU 的配置。你可以在更小的集群上进行尝试。请记住,默认设置包含 3 个节点,所以整个集群会拥有 3 倍于你所选择的资源(即,在本例中为 45GB 内存)。我在这里偷个懒,选择了更大的规模,这样我们的 Kubernetes 集群不会运行太长时间。

未分类

接着,只需点击 Creat。等上一两分钟,你的集群就能运转了。

现在让我们连接到集群。点击 Run in Cloud Shell,就可以为 Kubernetes 集群提供控制台。请注意,这是虚拟机中的一个单独 shell 环境,你在这里可以创建并测试 Docker 容器。我们可以在虚拟机上安装 Kubernetes,谷歌的 Kubernetes 服务会自动为我们完成这个步骤。

未分类

现在,在 Kubernetes 上运行我们的 docker 容器。请注意,镜像标签仅指向我们在 Docker Hub 上托管的 docker 镜像。此外,我们通过——port 指定我们想在端口 5000 上运行应用。

kubectl run keras-app --image=gcav66/keras-app --port 5000

在 Kubernetes 中,容器都在 pod(容器集合)中运行。我们可以输入 kubectl get pods 来验证我们的 pod 是否正在运行。如果你看到下面的结果,你就完成了配置。

gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get pods
NAME READY STATUS RESTARTS AGE
keras-app-79568b5f57-5qxqk 1/1 Running 0 1m

此时,我们的 pod 正在运行,我们需要将我们的 pod 暴露给 80 端口从而与外界相连。这意味着任何访问我们部署的 IP 地址的人都可以访问我们的 API。这也意味着我们不必在 URL 后面指定一个麻烦的端口号(与 :5000 说再见!)。

kubectl expose deployment keras-app --type=LoadBalancer --port 80 --target-port 5000

就要完成了!现在,运行 kubectl get service 来确定我们的部署(以及我们调用 API 所需的 URL)的状态。同样地,如果命令的输出结果和下图所示的结果类似,你就完成了这一步!

gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
keras-app LoadBalancer 10.11.250.71 35.225.226.94 80:30271/TCP 4m
kubernetes ClusterIP 10.11.240.1 <none> 443/TCP 18m

现在是关键时刻了!请获取 keras 应用程序的 cluster-ip。打开本地终端(或者存有狗狗照片的地方),运行 curl -X POST -F image=@dog.jpg ‘http:///predict’ 指令调用 API。

享受你的实验结果吧!

如下所示,API 正确地为该图返回了「小猎犬」的标签。

$ curl -X POST -F image=@dog.jpg 'http://35.225.226.94/predict'
{"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true}

步骤 4:封装

在本教程中,我们使用 Keras 和 Flask 实现了一个深度学习模型,并将其部署为 REST API。然后我们把这个应用程序放在 Docker 容器中,将该容器上传至 Docker Hub,并且使用 Kubernetes 对其进行部署。

只需要两个指令,Kubernetes 就部署好了我们的应用程序并向外部提供服务。你应该为此而感到自豪。

现在,我们可以对这个项目做出很多改进。首先,我们应该将运行 flask 应用程序的 python web 服务器从本地 python 服务器替换为 gunicorn 这样的生产级服务器。我们还应该探索 Kubernetes 的扩展和管理特性,这是本文中几乎没有涉及到的。最后,我们可以尝试从头开始创建一个 kuberenetes 环境。

Kubernetes存储之persistent volumes简介

说明

管理存储和管理计算有着明显的不同。PersistentVolume给用户和管理员提供了一套API,抽象出存储是如何提供和消耗的细节。在这里,我们介绍两种新的API资源:PersistentVolume(简称PV)和PersistentVolumeClaim(简称PVC)。

  • PersistentVolume(持久卷,简称PV)是集群内,由管理员提供的网络存储的一部分。就像集群中的节点一样,PV也是集群中的一种资源。它也像Volume一样,是一种volume插件,但是它的生命周期却是和使用它的Pod相互独立的。PV这个API对象,捕获了诸如NFS、ISCSI、或其他云存储系统的实现细节。

  • PersistentVolumeClaim(持久卷声明,简称PVC)是用户的一种存储请求。它和Pod类似,Pod消耗Node资源,而PVC消耗PV资源。Pod能够请求特定的资源(如CPU和内存)。PVC能够请求指定的大小和访问的模式(可以被映射为一次读写或者多次只读)。

PVC允许用户消耗抽象的存储资源,用户也经常需要各种属性(如性能)的PV。集群管理员需要提供各种各样、不同大小、不同访问模式的PV,而不用向用户暴露这些volume如何实现的细节。因为这种需求,就催生出一种StorageClass资源。

StorageClass提供了一种方式,使得管理员能够描述他提供的存储的等级。集群管理员可以将不同的等级映射到不同的服务等级、不同的后端策略。

pv和pvc的区别

PersistentVolume(持久卷)和PersistentVolumeClaim(持久卷申请)是k8s提供的两种API资源,用于抽象存储细节。管理员关注于如何通过pv提供存储功能而无需关注用户如何使用,同样的用户只需要挂载pvc到容器中而不需要关注存储卷采用何种技术实现。

pvc和pv的关系与pod和node关系类似,前者消耗后者的资源。pvc可以向pv申请指定大小的存储资源并设置访问模式,这就可以通过Provision -> Claim 的方式,来对存储资源进行控制。

volume和claim的生命周期

PV是集群中的资源,PVC是对这些资源的请求,同时也是这些资源的“提取证”。PV和PVC的交互遵循以下生命周期:

  • 供给
    有两种PV提供的方式:静态和动态。

  • 静态
    集群管理员创建多个PV,它们携带着真实存储的详细信息,这些存储对于集群用户是可用的。它们存在于Kubernetes API中,并可用于存储使用。

  • 动态
    当管理员创建的静态PV都不匹配用户的PVC时,集群可能会尝试专门地供给volume给PVC。这种供给基于StorageClass:PVC必须请求这样一个等级,而管理员必须已经创建和配置过这样一个等级,以备发生这种动态供给的情况。请求等级配置为“”的PVC,有效地禁用了它自身的动态供给功能。

  • 绑定
    用户创建一个PVC(或者之前就已经就为动态供给创建了),指定要求存储的大小和访问模式。master中有一个控制回路用于监控新的PVC,查找匹配的PV(如果有),并把PVC和PV绑定在一起。如果一个PV曾经动态供给到了一个新的PVC,那么这个回路会一直绑定这个PV和PVC。另外,用户总是至少能得到它们所要求的存储,但是volume可能超过它们的请求。一旦绑定了,PVC绑定就是专属的,无论它们的绑定模式是什么。
    如果没找到匹配的PV,那么PVC会无限期得处于unbound未绑定状态,一旦PV可用了,PVC就会又变成绑定状态。比如,如果一个供给了很多50G的PV集群,不会匹配要求100G的PVC。直到100G的PV添加到该集群时,PVC才会被绑定。

  • 使用
    Pod使用PVC就像使用volume一样。集群检查PVC,查找绑定的PV,并映射PV给Pod。对于支持多种访问模式的PV,用户可以指定想用的模式。一旦用户拥有了一个PVC,并且PVC被绑定,那么只要用户还需要,PV就一直属于这个用户。用户调度Pod,通过在Pod的volume块中包含PVC来访问PV。

  • 释放
    当用户使用PV完毕后,他们可以通过API来删除PVC对象。当PVC被删除后,对应的PV就被认为是已经是“released”了,但还不能再给另外一个PVC使用。前一个PVC的属于还存在于该PV中,必须根据策略来处理掉。

  • 回收
    PV的回收策略告诉集群,在PV被释放之后集群应该如何处理该PV。当前,PV可以被Retained(保留)、 Recycled(再利用)或者Deleted(删除)。保留允许手动地再次声明资源。对于支持删除操作的PV卷,删除操作会从Kubernetes中移除PV对象,还有对应的外部存储(如AWS EBS,GCE PD,Azure Disk,或者Cinder volume)。动态供给的卷总是会被删除。

Recycled(再利用)

如果PV卷支持再利用,再利用会在PV卷上执行一个基础的擦除操作(rm -rf /thevolume/*),使得它可以再次被其他PVC声明利用。

管理员可以通过Kubernetes controller manager的命令行工具,来配置自定义的再利用Pod模板。自定义的再利用Pod模板必须包含PV卷的详细内容,如下示例:

apiVersion: v1
kind: Pod
metadata:
  name: pv-recycler-
  namespace: default
spec:
  restartPolicy: Never
  volumes:
  - name: vol
    hostPath:
      path: /any/path/it/will/be/replaced
  containers:
  - name: pv-recycler
    image: "gcr.io/google_containers/busybox"
    command: ["/bin/sh", "-c", "test -e /scrub && rm -rf /scrub/..?* /scrub/.[!.]* /scrub/*  && test -z "$(ls -A /scrub)" || exit 1"]
    volumeMounts:
    - name: vol
      mountPath: /scrub

如上,在volumes部分的指定路径,应该被替换为PV卷需要再利用的路径。

PV类型

PV类型使用插件的形式来实现。Kubernetes现在支持以下插件:

  • GCEPersistentDisk
  • AWSElasticBlockStore
  • AzureFile
  • AzureDisk
  • FC (Fibre Channel)
  • Flocker
  • NFS
  • iSCSI
  • RBD (Ceph Block Device)
  • CephFS
  • Cinder (OpenStack block storage)
  • Glusterfs
  • VsphereVolume
  • Quobyte Volumes
  • HostPath (仅测试过单节点的情况——不支持任何形式的本地存储,多节点集群中不能工作)
  • VMware Photon
  • Portworx Volumes
  • ScaleIO Volumes

PV介绍

每个PV都包含一个spec和状态,即说明书和PV卷的状态。

  apiVersion: v1
  kind: PersistentVolume
  metadata:
    name: pv0003
  spec:
    capacity:
      storage: 5Gi
    accessModes:
      - ReadWriteOnce
    persistentVolumeReclaimPolicy: Recycle
    storageClassName: slow
    nfs:
      path: /tmp
      server: 172.17.0.2

Capacity(容量)

一般来说,PV会指定存储的容量,使用PV的capacity属性来设置。当前,存储大小是唯一能被设置或请求的资源。未来可能包含IOPS,吞吐率等属性。

访问模式

PV可以使用存储资源提供商支持的任何方法来映射到host中。如下的表格中所示,提供商有着不同的功能,每个PV的访问模式被设置为卷支持的指定模式。比如,NFS可以支持多个读/写的客户端,但可以在服务器上指定一个只读的NFS PV。每个PV有它自己的访问模式。

访问模式包括:

  • ReadWriteOnce — 该volume只能被单个节点以读写的方式映射
  • ReadOnlyMany — 该volume可以被多个节点以只读方式映射
  • ReadWriteMany — 该volume只能被多个节点以读写的方式映射

在CLI中,访问模式可以简写为:

  • RWO – ReadWriteOnce
  • ROX – ReadOnlyMany
  • RWX – ReadWriteMany

注意:即使volume支持很多种访问模式,但它同时只能使用一种方式来映射。比如,GCEPersistentDisk可以被单个节点映射为ReadWriteOnce,或者多个节点映射为ReadOnlyMany,但不能同时使用这两种方式来映射。

未分类

Class

一个PV可以有一种class,通过设置storageClassName属性来选择指定的StorageClass。有指定class的PV只能绑定给请求该class的PVC。没有设置storageClassName属性的PV只能绑定给未请求class的PVC。

过去,使用volume.beta.kubernetes.io/storage-class注解,而不是storageClassName属性。该注解现在依然可以工作,但在Kubernetes的未来版本中已经被完全弃用了。

回收策略

当前的回收策略有:

  • Retain:手动回收
  • Recycle:需要擦出后才能再使用
  • Delete:相关联的存储资产,如AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷都会被删除
    当前,只有NFS和HostPath支持回收利用,AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷支持删除操作。

阶段

一个volume卷处于以下几个阶段之一:

  • Available:空闲的资源,未绑定给PVC
  • Bound:绑定给了某个PVC
  • Released:PVC已经删除了,但是PV还没有被集群回收
  • Failed:PV在自动回收中失败了
    CLI可以显示PV绑定的PVC名称。

映射选项

当PV被映射到一个node上时,Kubernetes管理员可以指定额外的映射选项。可以通过使用标注volume.beta.kubernetes.io/mount-options来指定PV的映射选项。

比如:

apiVersion: "v1"
kind: "PersistentVolume"
metadata:
  name: gce-disk-1
  annotations:
    volume.beta.kubernetes.io/mount-options: "discard"
spec:
  capacity:
    storage: "10Gi"
  accessModes:
    - "ReadWriteOnce"
  gcePersistentDisk:
    fsType: "ext4"
    pdName: "gce-disk-1

映射选项是当映射PV到磁盘时,一个可以被递增地添加和使用的字符串。
注意,并非所有的PV类型都支持映射选项。在Kubernetes v1.6中,以下的PV类型支持映射选项。

  • GCEPersistentDisk
  • AWSElasticBlockStore
  • AzureFile
  • AzureDisk
  • NFS
  • iSCSI
  • RBD (Ceph Block Device)
  • CephFS
  • Cinder (OpenStack block storage)
  • Glusterfs
  • VsphereVolume
  • Quobyte Volumes
  • VMware Photon

PersistentVolumeClaims(PVC)

每个PVC都包含一个spec和status,即该PVC的规则说明和状态。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
  storageClassName: slow
  selector:
    matchLabels:
      release: "stable"
    matchExpressions:
      - {key: environment, operator: In, values: [dev]}

访问模式

当请求指定访问模式的存储时,PVC使用的规则和PV相同。

资源

PVC,就像pod一样,可以请求指定数量的资源。请求资源时,PV和PVC都使用相同的资源样式。

选择器(Selector)

PVC可以指定标签选择器进行更深度的过滤PV,只有匹配了选择器标签的PV才能绑定给PVC。选择器包含两个字段:

  • matchLabels(匹配标签) – PV必须有一个包含该值得标签
  • matchExpressions(匹配表达式) – 一个请求列表,包含指定的键、值的列表、关联键和值的操作符。合法的操作符包含In,NotIn,Exists,和DoesNotExist。

所有来自matchLabels和matchExpressions的请求,都是逻辑与关系的,它们必须全部满足才能匹配上。

等级(Class)

PVC可以使用属性storageClassName来指定StorageClass的名称,从而请求指定的等级。只有满足请求等级的PV,即那些包含了和PVC相同storageClassName的PV,才能与PVC绑定。

PVC并非必须要请求一个等级。设置storageClassName为“”的PVC被理解为请求一个无等级的PV,因此它只能被绑定到无等级的PV(未设置对应的标注,或者设置为“”)。未设置storageClassName的PVC不太相同,DefaultStorageClass的权限插件打开与否,集群也会区别处理PVC。

  • 如果权限插件被打开,管理员可能会指定一个默认的StorageClass。所有没有指定StorageClassName的PVC只能被绑定到默认等级的PV。要指定默认的StorageClass,需要在StorageClass对象中将标注storageclass.kubernetes.io/is-default-class设置为“true”。如果管理员没有指定这个默认值,集群对PVC创建请求的回应就和权限插件被关闭时一样。如果指定了多个默认等级,那么权限插件禁止PVC创建请求。

  • 如果权限插件被关闭,那么久没有默认StorageClass的概念。所有没有设置StorageClassName的PVC都只能绑定到没有等级的PV。因此,没有设置StorageClassName的PVC就如同设置StorageClassName为“”的PVC一样被对待。

根据安装方法的不同,默认的StorageClass可能会在安装过程中被插件管理默认的部署在Kubernetes集群中。

当PVC指定selector来请求StorageClass时,所有请求都是与操作的。只有满足了指定等级和标签的PV才可能绑定给PVC。当前,一个非空selector的PVC不能使用PV动态供给。

过去,使用volume.beta.kubernetes.io/storage-class注解,而不是storageClassName属性。该注解现在依然可以工作,但在Kubernetes的未来版本中已经被完全弃用了。

使用PVC

Pod通过使用PVC(使用方式和volume一样)来访问存储。PVC必须和使用它的pod在同一个命名空间,集群发现pod命名空间的PVC,根据PVC得到其后端的PV,然后PV被映射到host中,再提供给pod。

kind: Pod
apiVersion: v1
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: dockerfile/nginx
      volumeMounts:
      - mountPath: "/var/www/html"
        name: mypd
  volumes:
    - name: mypd
      persistentVolumeClaim:
        claimName: myclaim

命名空间注意事项

PV绑定是独有的,因为PVC是命名空间对象,映射PVC时只能在同一个命名空间中使用多种模式(ROX,RWX)。

StorageClass

每个StorageClass都包含字段provisioner和parameters,在所属的PV需要动态供给时使用这些字段。

StorageClass对象的命名是非常重要的,它是用户请求指定等级的方式。当创建StorageClass对象时,管理员设置等级的名称和其他参数,但对象不会在创建后马上就被更新。

管理员可以指定一个默认的StorageClass,用于绑定到那些未请求指定等级的PVC。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
Provisioner

StorageClass都有存储供应商provisioner,用来决定哪种volume插件提供给PV使用。必须制定该字段。

你不限于指定此处列出的“内部”供应商(其名称前缀为“kubernetes.io”并与Kubernetes一起分发)。你还可以运行和指定外部供应商,它们是遵循Kubernetes定义的规范的独立程序。外部提供者的作者对代码的生命周期,供应商的分发方式,运行状况以及使用的卷插件(包括Flex)等都有充分的自主权。库kubernetes-incubator/external-storage存放了一个库,用于编写外部存储供应商,而这些提供者实现了大量的规范,并且是各种社区维护的。

参数

StorageClass有一些参数用于描述归属于该StorageClass的volume。不同的存储提供商可能需要不同的参数。比如,参数type对应的值io1,还有参数iopsPerGB,都是EBS专用的参数。当参数省略时,就会使用它的默认值。

AWS

GCE

Glusterfs

OpenStack Cinder

vSphere

Ceph RBD

  apiVersion: storage.k8s.io/v1
  kind: StorageClass
  metadata:
    name: fast
  provisioner: kubernetes.io/rbd
  parameters:
    monitors: 10.16.153.105:6789
    adminId: kube
    adminSecretName: ceph-secret
    adminSecretNamespace: kube-system
    pool: kube
    userId: kube
    userSecretName: ceph-secret-user
  • monitors:Ceph的monitor,逗号分隔。该参数是必须的。
  • adminId:Ceph的客户端ID,可在pool中创建镜像。默认的是“admin”。
  • adminSecretNamespace:adminSecret的命名空间,默认值是“default”。
  • adminSecretName:adminId的Secret Name。改参数是必须的,提供的秘钥必须有类型“kubernetes.io/rbd”。
  • pool:Ceph的RBD pool,默认值是“rbd”。
  • userId:Ceph的客户ID,用于映射RBD镜像的,默认值和adminId参数相同。
  • userSecretName:Ceph Secret的名称,userId用该参数来映射RBD镜像。它必须和PVC在相同的命名空间。该参数也是必须的。提供的秘钥必须有类型“kubernetes.io/rbd”。比如,按照下面的方式来创建:
$ kubectl create secret generic ceph-secret --type="kubernetes.io/rbd" --from-literal=key='QVFEQ1pMdFhPUnQrSmhBQUFYaERWNHJsZ3BsMmNjcDR6RFZST0E9PQ==' --namespace=kube-system

Quobyte

Azure Disk

Portworx Volume

ScaleIO

配置

如果你在写配置模板和示例,用于在需要持久化存储的集群中使用,那么,我们建议你使用以下的一些模式:

  • 在你的捆绑配置(如Deployment、ConfigMap胖)中包含PVC对象。
  • 在配置中不要包含PersistentVolume对象,因为实例化配置的用户可能没有创建PersistentVolumes的权限
  • 当用户提供实例化模板时,给用户提供存储类名称的选项。
    • 如果用户提供了一个StorageClass名称,并且Kubernetes版本是1.4及以上,那么将该值设置在PVC的volume.beta.kubernetes.io/storage-class标注上。这会使得PVC匹配到正确的StorageClass。
    • 如果用户没有提供StorageClass名称,或者集群版本是1.3,那么就需要在PVC配置中设置volume.alpha.kubernetes.io/storage-class: default标注。
      — 这会使得在一些默认配置健全的集群中,PV可以动态的提供给用户。
      — 尽管在名称中包含了alpha单词,但是该标注对应的代码有着beta级别的支持。
      — 不要使用volume.beta.kubernetes.io/storage-class,无论设置什么值,甚至是空字符串。因为它会阻止DefaultStorageClass许可控制器。
  • 在你的工具中,要监视那些一段时间后还没有获得绑定的PVC,并且展示给用户。因为这可能表明集群没有支持动态存储(此时我们应该创建匹配的PV),或者集群没有存储系统(此时用户不能部署需要PVC的情况)。

  • 未来,我们期望大多数集群都可以使能DefaultStorageClass,并且能有一些可用的存储形式。然而,可能没有行在所有集群都能运的StorageClass,所以默认情况下不要只设置一种。在某些时候,alpha标注将不再具有意义,但复位PVC的storageClass字段将具有所需的效果。

Cloud Foundry和Kubernetes结合的过去与未来

过去的几年中,在云计算领域的开源社区中最有争议的话题莫过于Cloud Foundry和Kubernetes的关系。大家的疑问紧紧围绕着三个问题:“它们会互相取代对方吗?”,“它们是互斥的吗?” ,“还是说它们是可以融合的?”。放眼望去在目前的商业产品中,两者几乎没什么关联和集成,都可以运行在各种IaaS之上。

多年以来,我们一直从事Cloud Foundry部署的相关工作,从原来传统的BOSH CPI到现在的容器化Cloud Foundry的工作。在这期间,我们对两者结合的多种技术选型的进行探索,包括可行性分析、最佳实践、经验总结和价值意义等方面,旨在最大程度上利用和发挥两者的优势,并提升开发者体验。

本文将从Cloud Foundry部署的层面,来介绍业界和我们如何去把两套系统进行整合的。当然任何整合方案都会有各自的优缺点,而且所有的整合还在一个快速发展的阶段,所以在这里算是给大家抛砖引玉,为以后的工作和学习提供一些帮助。

Cloud Foundry和Kubernetes简介

Cloud Foundry

Cloud Foundry是一个独立于云的平台即服务解决方案,也是业界最成功的PaaS平台。Cloud Foundry提供了一个可轻松运行、扩展和维护应用程序的环境和快速便捷的开发者体验。Cloud Foundry支持Java、NodeJS、Ruby、Python等大多数语言和环境。

开源的Cloud Foundry由Cloud Foundry基金会开发并支持,基金会包括Pivotal、IBM、VMware以及其它许多厂商。商业版本的Cloud Foundry,如IBM Bluemix和Pivotal Cloud Foundry,是基于开源的Cloud Foundry项目并在其基础上提供企业级的支持。

Kubernetes

Kubernetes是一个来源于谷歌Borg项目的开源云平台。它由Cloud Native Computing Foundation发起,该基金会的成员包括了许多行业巨头,如AWS、Azure、Intel、IBM、RedHat、Pivotal等许多公司。

Kubernetes首要的功能是一个容器编排和容器生命周期的管理。尽管不限于此,但它通常是被用来运行Docker容器,它的受众人群更广泛一些,比如想要构建在容器服务之上的应用和服务开发人员。有一些解决方案基于Kubernetes提供了PaaS体验,比如IBM的Container Service和RedHat的OpenShift等。

两个平台的比较

两个平台都很成熟和完整,而且随着不断的开发和改进,所以两个平台的相同和不同之处也会随之变。当然两大项目有很多相似之处,包括容器化,命名空间和身份验证等机制。而两者也有各自的优势:

Cloud Foundry的优势在于:

  1. 成熟的身份验证系统UAA,用户组和multi-tenancy的支持
  2. 方便快捷的cf push
  3. 自带负载均衡Router
  4. 强大的日志和metrics整合
  5. 成熟的部署工具BOSH

Kubernetes的优势在于:

  1. 大量社区和第三方支持,提供强大的扩展性
  2. 完善的容器生命周期和自动伸缩管理
  3. 方便快捷的容器化应用部署
  4. 良好、多样的持久层支持
  5. 多种开源UI支持

现阶段两者的关系及结合情况

从上面的比较,我们可以看到,两大平台各有各的优势及强大之处。业界有很多厂商都想尝试将两者融合,从而将优势发挥到最大化。从现在来看两者整合主要有以下三种方式:

Kubernetes CPI

我们知道BOSH是Cloud Foundry官方指定的部署工具,它是一个针对大规模分布式系统的部署和生命周期管理的开源工具。但是BOSH不仅仅局限于部署Cloud Foundry,也可以应用于别的分布式系统,只需要其提供符合要求的Release即可。CPI全称Cloud Provider Interface,是BOSH用来与IaaS通信完成虚拟机实例和模板的创建和管理的一个API接口,CPI目前能够支持Amazon的AWS、微软的Azure、IBM的SoftLayer等IaaS平台,国内阿里云也提供了CPI的支持。BOSH的主控制器Director通过CPI与底层的IaaS层交互,将BOSH manifest.yaml 文件定义任务及组件部署到IaaS层的VM上,如下图所示:

未分类

所以,第一种整合,也就是开发一套Kubernetes的CPI,通过BOSH和manifest.yaml的配置将Cloud Foundry部署到Kubernetes上。现在有一些大的厂商如IBM、SAP在开发相应的Kubernetes CPI,大家可以在GitHub中搜索到。我个人觉得,这种方式虽然容易上手,但还是以IaaS的角度来看待Kubernetes,底层还是通过BOSH来管理的,没能最大地发挥Kubernetes平台的优势的。

Cloud Foundry Container Runtime(CFCR)

Cloud Foundry基金会在2017年底宣布把Pivotal和谷歌捐献的Kubo项目改名为CFCR(Cloud Foundry Container Runtime)。总体来说是利用BOSH来部署Cloud Foundry和Kubernetes,并通过Application Runtime管理Cloud Foundry的Application Service;通过Container Runtime管理Kubernetes的Container Service,比如一些无法部署在Cloud Foundry内的服务,如数据库、监控等。然后通过Open Service Broker将两者连接起来,如下图所示:

未分类

容器化Cloud Foundry

将Cloud Foundry所有组件容器化,并部署到Kubernetes上是一种比较新型的整合方式。现在有一些大的厂商做这方面的工作,比如SUSE、IBM和SAP。其核心就是通过将Cloud Foundry的BOSH Release转化成Docker镜像,然后通过Kubernetes的部署工具Helm来将Cloud Foundry更加自然地部署到Kubernetes上。只有在生成Cloud Foundry组件的Docker镜像是需要用到BOSH Cli去转化BOSH release,部署及部署之后的管理,都不需要BOSH,而是交给Kubernetes来对所有Cloud Foundry组件的Pod、服务及相应配置资源的生命周期进行管理。这样既享受到了Cloud Foundry带来的良好的开发者体验,又用到了Kubernetes强大的管理和扩展能力:

未分类

IBM在两者结合上做的工作和成果

IBM主要是和SUSE合作,在上面第三种方案的基础上,创建一套可以在IBM Cloud上快速部署的企业级Cloud Foundry。这个新的服务名称叫做IBM Cloud Foundry Enterprise Environment(CFEE),他的主要构件流程如下:

  1. 首先我们需要将BOSH的Release通过SUSE的转换工具Fissile将其编译并制作成CF组件相应的Docker镜像。
  2. 之后我们需要通过Fissile将预设的参数转换成Helm或者Kubernetes的配置资源文件。
  3. 利用IBM Cloud强大的服务资源,增加企业级服务的支持,比如数据库服务,安全服务和负载均衡服务。
  4. 最后发布新的IBM CF版本,用户可以直接在IBM Cloud的Catalog里面找到并自动部署出一套CFEE环境。

和传统的BOSH CPI的部署方式的比较,新的Cloud Foundry版本发布后,管理员只需执行一次来构建任务来创建新版本Docker镜像和Helm配置,之后就可以重复使用了。这种部署Cloud Foundry的方式时间大概在15-20分钟,和之前BOSH部署4-6个小时相比,快了很多。

IBM已于2018上半年的Cloud Foundry Summit上发布了Experimental版本:

未分类

在刚过去的2018年10月初,IBM又发布了CFEE的GA版,主要为企业级客户提供更好的服务、带来更大的价值。部署出来的CFEE环境不但与其他环境是相互隔离的,而且CFEE环境中部署的Cloud Foundry应用还可以很轻松地使用IBM Cloud上丰富的服务资源,如AI,大数据,IoT和Blockchain等:

未分类

感兴趣的用户可以通过IBM Cloud的Catalog找到CFEE的介绍:https://console.bluemix.net/docs/cloud-foundry/index.html#creating。

在部署页面中,用户可以配置CFEE的域名,Diego-cell的个数以及CF版本等信息,然后CFEE可以实现全自动化一键部署,大概1个小时左右,一个企业级的CF环境就部署成功了:

未分类

当然在部署完成后,CFEE还提供了强大的管理,监控和命令行等功能,如图所示,用户可以很轻松地在页面上查看Cloud Foundry和应用的使用情况,创建相应的资源或者绑定IBM的外部服务等:

未分类

未来两者结合的发展方向

当然两者结合的探索工作不止于此,现在越来越多的厂商和开发者加入到两者整合的研究中。其中比较火的有IBM主导的Cloud Foundry孵化项目Eirini,其想法是想将Cloud Foundry中的Diego-cell容器Garden替换成Kubernetes的容器,从而将两者更紧密地连接在一起:

未分类

还有像SUSE主导的Fissile项目,未来其想法是想通过BOSH命令可以直接生成CF的Docker镜像和Helm配置,从而可以更好地和社区融合到一起。

总结

综上所述,Kubernetes之于Cloud Foundry的关系不是挑战也不是竞争关系,Cloud Foundry希望与其更好地融合,就像Cloud Foundry Foundation执行董事Abby Kearns说的:“Cloud Foundry是结构化的PaaS平台,其他平台是非结构化的,用户的需求是多元化的,并不是一定要如何容器化,而是希望平台能够更开放、支持更多的类型。”

不管未来两者怎么发展,两者的结合绝对是适应市场的趋势,同时又能给用户带来多方面的价值提升。未来两者还能碰撞出什么样的火花?让我们拭目以待!

Kubernetes使用过程中遇到的一些问题与解决方法

说明

这里记录Kubernetes使用过程中遇到的一些比较简单的问题与解决方法,比较复杂的问题会单独分析:https://www.lijiaocn.com/tags/kubernetes.html

kubectl exec登陆容器后,shell终端窗口宽度太小

这个其实不是kubectl的问题,而是shell终端的问题,在shell终端中可以通过变量COLUMNS修改宽度:

export COLUMNS=210

这个变量可以进入容器后设置,也可以在执行exec命令时传入,例如:

kubectl exec -ti busybox -- env COLUMNS=500 LINES=100 bash

使用 Kubeadm 升级 Kubernetes 版本

升级最新版 kubelet kubeadm kubectl (阿里云镜像)

cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
setenforce 0
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet

查看此版本的容器镜像版本

$ kubeadm config images list

k8s.gcr.io/kube-apiserver:v1.12.1
k8s.gcr.io/kube-controller-manager:v1.12.1
k8s.gcr.io/kube-scheduler:v1.12.1
k8s.gcr.io/kube-proxy:v1.12.1
k8s.gcr.io/pause:3.1
k8s.gcr.io/etcd:3.2.24
k8s.gcr.io/coredns:1.2.2

查询可用的版本

$ yum list --showduplicates | grep 'kubeadm|kubectl|kubelet'

拉取容器镜像(github)

echo ""
echo "=========================================================="
echo "Pull Kubernetes v1.12.1 Images from aliyuncs.com ......"
echo "=========================================================="
echo ""

MY_REGISTRY=registry.cn-hangzhou.aliyuncs.com/openthings

## 拉取镜像
docker pull ${MY_REGISTRY}/k8s-gcr-io-kube-apiserver:v1.12.1
docker pull ${MY_REGISTRY}/k8s-gcr-io-kube-controller-manager:v1.12.1
docker pull ${MY_REGISTRY}/k8s-gcr-io-kube-scheduler:v1.12.1
docker pull ${MY_REGISTRY}/k8s-gcr-io-kube-proxy:v1.12.1
docker pull ${MY_REGISTRY}/k8s-gcr-io-etcd:3.2.24
docker pull ${MY_REGISTRY}/k8s-gcr-io-pause:3.1
docker pull ${MY_REGISTRY}/k8s-gcr-io-coredns:1.2.2


## 添加Tag
docker tag ${MY_REGISTRY}/k8s-gcr-io-kube-apiserver:v1.12.1 k8s.gcr.io/kube-apiserver:v1.12.1
docker tag ${MY_REGISTRY}/k8s-gcr-io-kube-scheduler:v1.12.1 k8s.gcr.io/kube-scheduler:v1.12.1
docker tag ${MY_REGISTRY}/k8s-gcr-io-kube-controller-manager:v1.12.1 k8s.gcr.io/kube-controller-manager:v1.12.1
docker tag ${MY_REGISTRY}/k8s-gcr-io-kube-proxy:v1.12.1 k8s.gcr.io/kube-proxy:v1.12.1
docker tag ${MY_REGISTRY}/k8s-gcr-io-etcd:3.2.24 k8s.gcr.io/etcd:3.2.24
docker tag ${MY_REGISTRY}/k8s-gcr-io-pause:3.1 k8s.gcr.io/pause:3.1
docker tag ${MY_REGISTRY}/k8s-gcr-io-coredns:1.2.2 k8s.gcr.io/coredns:1.2.2

echo ""
echo "=========================================================="
echo "Pull Kubernetes v1.12.1 Images FINISHED."
echo "into registry.cn-hangzhou.aliyuncs.com/openthings, "
echo "           by openthings@https://my.oschina.net/u/2306127."
echo "=========================================================="

echo ""

备注:或参考https://www.cnblogs.com/Irving/p/9818440.html的镜像脚本

查询需要升级的信息

$ kubeadm upgrade plan

升级 Master 节点

$ kubeadm upgrade apply v1.12.1

Node 节点升级

升级对应的 kubelet kubeadm kubectl 的版本,拉取对应版本的镜像即可。

查询各节点信息与 pod 信息

$ kubectl get nodes
$ kubectl get pod --all-namespaces -o wide

k8s(十三)、企业级docker仓库Harbor在kubernetes上搭建使用

前言

Harbor是Docker镜像仓库管理系统,用于企业级管理容器镜像,支持对接ldap进行权限管理,由VMVare中国团队开发。

项目地址:
https://github.com/goharbor/harbor/

安装方式:
1.Docker Compose方式部署:
https://github.com/goharbor/harbor/blob/master/docs/installation_guide.md

2.通过kubernetes直接部署:
新版本已废弃

3.通过helm/kubernetes方式部署:
https://github.com/goharbor/harbor-helm/blob/master/README.md
推荐使用,本文将采用此方式

Helm安装

helm简介

Helm是一个用于Kubernetes的包管理工具,每一个安装包构成一个chart基本单位.想象一下,一个完整的部署,可能包含前端后端中间件DB的部署实例,以及k8s管理对应的secret/configmap/svc/ing/pv/pvc等众多资源类型,在部署管理或者卸载之前,如果逐一编辑管理这些资源的yaml文件,工作量比较庞大且容易疏漏.helm的出现很好的解决了这个问题,使用模板以及变量替换的方式,按需生成部署yaml文件包,并且向kubernetes api发送调度请求,虽然额外地引入了一些学习成本,但对于部署包的管理无疑是带来了很大的便利性的.

helm组件

Helm

Helm是一个cli客户端,可以完成如下内容:

1.创建/打包/调试Chart
2.创建本地Chart仓库,管理本地和远程Chart仓库
3.与Tiller交互并完成Chart的安装,升级,删除,回滚,查看等操作

Tiller

1.监听helm客户端的请求,根据chart生成相应的release
2.将release的资源信息发送给kubernetes api,实现资源增删改查.
3.追踪release的状态,实现资源的更新/回滚操作

安装:
安装较为简单,参考官方文档,不做复述:
https://docs.helm.sh/using_helm/#installing-helm

Harbor安装

这里采用次新版本的v1.5.3版本

root@h1:# wget https://github.com/goharbor/harbor/archive/v1.5.3.tar.gz
root@h1:# tar -xf https://github.com/goharbor/harbor/archive/v1.5.3.tar.gz 
root@h1:# cd harbor-1.5.3/contrib/helm/harbor/

helm方式安装的harbor默认是https的,因此需要k8s集群ingress网关开启https协议,traefik开启https参考此前文章:
https://blog.csdn.net/ywq935/article/details/79886793

更新helm dependency

harbor的helm部署依赖了postgresql的helm,在官方的安装文档没有明确说明,直接按照官方文档说明安装,就会缺失postgresql的部署,导致helm install failed

helm dependency update

修改value.yaml文件,添加pvc持久存储并将相应组件上下文中的volume部分注释打开:

mysql:
  volumes:
    data:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce

adminserver:
  volumes:
    config:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 1G

registry:
  volumes:
    data:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 5G

完整value.yaml文件如下:

# Configure persisten Volumes per application
## Applications that require storage have a `volumes` definition which will be used
## when `persistence.enabled` is set to true.
## example
# mysql:
#   volumes:
#     data:
## Persistent Volume Storage Class
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
##   set, choosing the default provisioner.  (gp2 on AWS, standard on
##   GKE, AWS & OpenStack)
##
#     storageClass: "-"
#     accessMode: ReadWriteOnce
#     size: 1Gi

mysql:
  volumes:
    data:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
adminserver:
  volumes:
    config:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 1G

registry:
  volumes:
    data:
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 5G

## Configure resource requests and limits per application
## ref: http://kubernetes.io/docs/user-guide/compute-resources/
##
# mysql:
#   resources:
#     requests:
#       memory: 256Mi
#       cpu: 100m

persistence:
  enabled: true

# The tag for Harbor docker images.
harborImageTag: &harbor_image_tag v1.4.0

# The FQDN for Harbor service.
externalDomain: harbor.my.domain
# If set to true, you don't need to set tlsCrt/tlsKey/caCrt, but must add
# Harbor FQDN as insecure-registries for your docker client.
insecureRegistry: false
#insecureRegistry: true
# The TLS certificate for Harbor. The common name of tlsCrt must match the externalDomain above.
tlsCrt: 
#tlsCrt: root/traefik/ssl/kokoerp.crt
tlsKey:
#tlsKey: root/traefik/ssl/kokoerp.key
caCrt:

# The secret key used for encryption. Must be a string of 16 chars.
secretKey: not-a-secure-key

# These annotations allow the registry to work behind the nginx
# ingress controller.
ingress:
  annotations:
    ingress.kubernetes.io/ssl-redirect: "true"
    ingress.kubernetes.io/body-size: "0"
    ingress.kubernetes.io/proxy-body-size: "0"

adminserver:
  image:
    repository: vmware/harbor-adminserver
    tag: *harbor_image_tag
    pullPolicy: IfNotPresent
  emailHost: "smtp.mydomain.com"
  emailPort: "25"
  emailUser: "sample_admin@mydomain.com"
  emailSsl: "false"
  emailFrom: "admin <sample_admin@mydomain.com>"
  emailIdentity: ""
  emailInsecure: "False"
  emailPwd: not-a-secure-password
  adminPassword: Harbor12345
  authenticationMode: "db_auth"
  selfRegistration: "on"
  ldap:
    url: "ldaps://ldapserver"
    searchDN: ""
    baseDN: ""
    filter: "(objectClass=person)"
    uid: "uid"
    scope: "2"
    timeout: "5"
    verifyCert: "True"
  ## Persist data to a persistent volume
  volumes:
    config:
      storageClass: "cephrbd"
      accessMode: ReadWriteOnce
      size: 1Gi
  # resources:
  #  requests:
  #    memory: 256Mi
  #    cpu: 100m

## jobservice
#
jobservice:
  image:
    repository: vmware/harbor-jobservice
    tag: *harbor_image_tag
    pullPolicy: IfNotPresent
  secret: not-a-secure-secret
# resources:
#   requests:
#     memory: 256Mi
#     cpu: 100m

## UI
#
ui:
  image:
    repository: vmware/harbor-ui
    tag: *harbor_image_tag
    pullPolicy: IfNotPresent
  secret: not-a-secure-secret
  privateKeyPem: |
    -----BEGIN RSA PRIVATE KEY-----
    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
    -----END RSA PRIVATE KEY-----
# resources:
#  requests:
#    memory: 256Mi
#    cpu: 100m

## MySQL Settings. Currently Harbor does not support an external
## MySQL server, only their own image. Until this is fixed, do not
## Change the settings below.
#
mysql:
  image:
    repository: vmware/harbor-db
    tag: *harbor_image_tag
    pullPolicy: IfNotPresent
  # If left blank will use the included mysql service name.
  host: ~
  port: 3306
  user: "root"
  pass: "registry"
  database: "registry"
  volumes:
    data:
      storageClass: "cephrbd"
      accessMode: ReadWriteOnce
      size: 1Gi
  # resources:
  #  requests:
  #    memory: 256Mi
  #    cpu: 100m

registry:
  image:
    repository: vmware/registry-photon
    tag: v2.6.2-v1.4.0
    pullPolicy: IfNotPresent
  httpSecret: not-a-secure-secret
  logLevel:
# comment out one of the below to use your cloud's object storage.
#  objectStorage:
#    gcs:
#      keyfile: ""
#      bucket: ""
#      chunksize: "5242880"
#    s3:
#      region: ""
#      accesskey: ""
#      secretkey: ""
#      bucket: ""
#      encrypt: "true"
#    azure:
#      accountname: ""
#      accountkey: ""
#      container: ""
  rootCrt: |
    -----BEGIN CERTIFICATE-----
    MIIE0zCCArugAwIBAgIJAIgs3S+hsjhmMA0GCSqGSIb3DQEBCwUAMAAwHhcNMTcx
    MTA5MTcyNzQ5WhcNMjcxMTA3MTcyNzQ5WjAAMIICIjANBgkqhkiG9w0BAQEFAAOC
    Ag8AMIICCgKCAgEA4WYbxdrFGG6RnfyYKlHYML3lEqtA9cYWWOynE9BeaEr/cMnM
    bBr1dd91/Nm6RiYhQvTDU2Kc6NejqjdliW5B9xUoVKayri8OU81a8ViXeNgKwCPR
    AiTTla1zoX5DnvoxpO9G3lxyNvTKXc0cw8NjQDAXpaDbzJYLkshCeuyD9bco8R96
    /zrpBEX8tADN3+3yA3fMcZzVXsBm4BTpHJRk/qBpHYEPSHzxyH3iGMNKk3vMUBZz
    e0EYkK8NCA2CuEKMnC3acx9IdRwkx10abGvHQCLRCVY7rGoak+b0oZ99RJIRQ9Iq
    YXsn8fsMBQly6xxvSeY5XuSP7Xb6JKDt3y8Spi4gR1M/5aEzhuOyu201rMna7Rs/
    GPfaKjBlbX0jiLDa7v4zjsBPsPaf/c4uooz3ICLsdukaom+E538R0EiOkXt/wyw2
    2YmaWNCsYlEpke7cVC33e/0dPBq4IHsVflawSF9OWS23ikVAs/n+76KjuucEDmbT
    aKUYAJjvAmZL14j+EKc/CoplhCe6pKhavjmNIOfCSdlreIPBhOVbf1f817wKoSIZ
    qVyCA1AYNkI9RYS00axtJGBGMlKbdQqCNpLL58c6To2awmckIZCEcATKOp++NoGm
    Ib0bhdSasdGB5VCtwZVluN8bLl13zBKoxTGjNlEatUGDRnDAnLdZbXXffjsCAwEA
    AaNQME4wHQYDVR0OBBYEFCMYYMOL0E/Uyj5wseDfIl7o4ELsMB8GA1UdIwQYMBaA
    FCMYYMOL0E/Uyj5wseDfIl7o4ELsMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL
    BQADggIBABG8fPvrrR+erpwQFuB/56j2i6sO+qoOJPpAMYwkzICrT0eerWAavwoy
    f0UAKN7cUeEJXjIR7s7CogGFijWdaWaQsXUD0zJq5aotLYZLimEc1O0uAmJEsfYC
    v7mG07eU6ge22sSo5hxhVplGt52hnXnT0DdgSRZpq2mvgd9lcopAidM+KHlaasXk
    IecHKM99KX9D8smr0AcQ6M/Ygbf2qjO9YRmpBIjyQWEake4y/4LWm+3+v08ecg4B
    g+iMC0Rw1QcPqgwaGaWu71RtYhyTg7SnAknb5nBcHIbLb0hdLgQTa3ZdtXgqchIi
    GuFlEBmHFZP6bLJORRUQ0ari5wpXIsYfrB4T8PybTzva3OCMlEsMjuysFr9ewhzM
    9UGLiSQNDyKA10J8WwlzbeD0AAW944hW4Dbg6SWv4gAo51T+6AukRdup5y6lfQ5a
    h4Lbo6pzaA369IsJBntvKvia6hUf/SghnbG7pCHX/AEilcgTb13HndF/G+7aZgKR
    mi9qvNRSDsE/BrgZawovp81+j6aL4y6UtXYspHr+SuWsKYsaH7pl5HspNCyJ5vV6
    dpJAwosFBqSEnI333wAunpMYmi/jKHH/j4WqjLnCInp0/wouzYu42l8Pmz591BSp
    Jag500bEBxqI2RLELgMt/bUdjp4N2M7mrxdrN+2579HTzb6Hviu9
    -----END CERTIFICATE-----
  ## Persist data to a persistent volume
  volumes:
    data:
      storageClass: "cephrbd"
      accessMode: ReadWriteOnce
      size: 5Gi
  # resources:
  #  requests:
  #    memory: 256Mi
  #    cpu: 100m

clair:
  enabled: true
  image:
    repository: vmware/clair-photon
    tag: v2.0.1-v1.4.0
    pullPolicy: IfNotPresent
## The following needs to match the credentials
## in the `postgresql` configuration under the
## `postgresql` namespace below.
  postgresPassword: not-a-secure-password
  postgresUser: clair
  postgresDatabase: clair
# resources:
#  requests:
#    memory: 256Mi
#    cpu: 100m
# pgResources:
#  requests:
#    memory: 256Mi
#    cpu: 100m
#  volumes:
#    pgData:
#      storageClass: "cephrbd"
#      accessMode: ReadWriteOnce
#      size: 1Gi
  # resources:
  #  requests:
  #    memory: 256Mi
  #    cpu: 100m

## Notary support is not yet fully implemented in the Helm Charts
## Enabling it will just break things.
#
notary:
  enabled: false

## Settings for postgresql dependency.
## see https://github.com/kubernetes/charts/tree/master/stable/postgresql
## for further configurables.
postgresql:
  postgresUser: clair
  postgresPassword: not-a-secure-password
  postgresDatabase: clair
  persistence:
    # enabled: false
    enabled: true
    storageClass: "cephrbd"
    accessMode: ReadWriteOnce
    size: 8Gi

部署

helm install . --debug --name hub --set externalDomain=hub.test.com

等待一段时间的镜像拉取后,查看pod:

hub-harbor-adminserver-0                 1/1       Running            1          3h
hub-harbor-clair-6c7d9dcdb7-q4lv4        1/1       Running            2          3h
hub-harbor-jobservice-75f7fbcc9c-ggwp4   1/1       Running            2          3h
hub-harbor-mysql-0                       1/1       Running            0          3h
hub-harbor-registry-0                    1/1       Running            1          3h
hub-harbor-ui-57b4674ff9-kcfq6           1/1       Running            0          3h
hub-postgresql-ccf8d56d5-jg4wq           1/1       Running            1          3h

添加dns,指向ingress gateway(即traefik的node ip):

echo "192.168.1.238  hub.test.com"  >> /etc/hosts

浏览器打开测试,默认登录口令为admin/Harbor12345

未分类

可以看到默认有一个公开项目library,尝试往这里推送镜像

Docker推送测试

找一台安装了docker的机器,修改docker服务脚本:

~# cat /lib/systemd/system/docker.service

#ExecStart=/usr/bin/dockerd -H fd://
ExecStart=/usr/bin/dockerd  --insecure-registry hub.kokoerp.com

重启docker并登录测试:

~# systemctl daemon-reload
~# systemctl restart docker

root@h2:~# docker login hub.test.com
Username (admin): admin
Password: 
Login Succeeded

推送镜像测试:

root@h2:~# docker tag busybox hub.test.com/library/busybox:test1
root@h2:~# docker push hub.test.com/library/busybox:test1
The push refers to a repository [hub.test.com/library/busybox]
8a788232037e: Pushed 
test1: digest: sha256:915f390a8912e16d4beb8689720a17348f3f6d1a7b659697df850ab625ea29d5 size: 527

可以看到推送成功

ldap配置

未分类

harbor可以基于企业内部的ldap来获取人员信息,可以在ui上创建非公开项目,将ldap中获取的人员加入项目并赋予相应pull push权限,ui操作界面比较友好,这里就不展示了.