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操作界面比较友好,这里就不展示了.

k8s v1.11.1高可用集群的问题排查

k8s集群环境基于阿里云VPC网络,采用kubeadm工具快速部署k8s高可用集群。

服务器:

  • ubuntu 16.04.4 LTS (Xenial Xerus),4.4.0-117-generic

  • kubelet –version: Kubernetes v1.11.1

  • docker -v: Docker version 17.03.3-ce, build e19b718

问题现象是通过kubectl get nodes -w监控node节点的状态时,发现偶尔会出现NotReady状态。这时候集群访问正常,外部访问应用服务正常,只是自动化接口测试时,偶尔报出 http接口请求的错误:java.net.ConnectException: 拒绝连接 (Connection refused)。初步怀疑是Node如果是NotReady状态,则对应节点上的服务会受到影响,导致请求被拒绝。

未分类

未分类

通过kubectl describe node查看node的信息,发现Conditions status Unknown, Kubelet stopped posting node status信息。

通过google得知,node挂了后到底多久会notready 多久开始调度? 40s判死刑,缓期5min善后!

注意:这个由kube-controller-manager的两个参数决定的

  • –pod-eviction-timeout:缺省为 5m,五分钟,在 Pod 驱逐行为的超时时间。
  • –node-monitor-grace-period:缺省为 40s,也就是 40 秒,无响应 Node 在标记为 NotReady 之前的等候时间。
    初步怀疑原因是,node的notready状态,触发了Kubernetes的Pod重调度流程。

首先分析一下该重调度流程:

  1. kubelet周期性的调用rest apiserver接口将Node状态更新至etcd,周期为node-status-update-frequency,默认值10s

  2. controller manager每node-monitor-period通过apiserver查询etcd,检查kubelet上报上来的状态,默认值为5s

  3. node的状态将会在node-monitor-grace-period周期内更新,controller manager中设置默认值为40s

  4. kubelet在更新自身状态的时候有nodeStatusUpdateRetry次重试,nodeStatusUpdateRetry的默认值为5次

说明: controller manager会在node-monitor-grace-period 40s之内获取etcd中的node状态,而期间kubelet更新了40s/10s=4次node状态。

通过查看/etc/kubernetes/manifests/kube-controoller-manager.yaml,发现配置是

- --node-monitor-grace-period=10s
- --pod-eviction-timeout=10s

因为kubelet默认更新Node状态的时间是10s,而controller manager判断Node是否health的时间也是10s,但是考虑到kubelet,controller manager通过apiserver,操作etcd之间是异步访问,实际更新Node的时间应该可能超过10s,他们之间的delay将会受到network latency, apiserver latency ,etcd latency,以及master node上的负载的影响。根据Kubernetes的list-watch机制,每次watch之前需要按照本地最大version list所有etcd中更新的logs,所以在delay的情况下,controller manager可能获取不到Node的状态更新,从而认为其Unhealthy,且10s之后根据eviction 10s,如果Node仍然为Unhealthy,则触发Pod的重调度,从而导致服务震荡,出现以上的connection refuse的问题。

通过上面解释,我们知道导致服务偶尔中断的原因是controller manager设置Node状态的时间和kubelet更新时间设置的太短,解决办法也很简单,去除这两个配置,采用默认值即可,经测试,没有发现NotReady现象,且接口自动化测试也通过。

社区默认的配置

未分类

快速更新和快速反应

该模式下会产生频繁的node update事件,加重etcd的负担

未分类

这种场景下20s之后,会认为node down了,接着–pod-eviction-timeout=30s之后,pod将会被驱逐,也就是50s会发生evict。但是会增加etcd的负载,每个node将会每2s更新一次etcd的状态。

如果环境有1000个节点,1分钟内将有15000个节点更新,这可能需要大型的etcd容器甚至是etcd的专用节点。

中等更新和平均反应

未分类

将会20s更新一次node状态,也就是说controller manager认为node状态不正常之前,会有2m60/205=30次更新node的状态。Node状态为down之后1m,就会触发evict。
如果1000node的场景,1分钟内将会有60s/20s*1000=3000次 etcd node状态的更新。

低更新和慢反应

未分类

Kubelet将会1m更新一次node的状态,在认为不健康之后会有5m/1m*5=25次重试更新的机会。Node为不健康的时候,1m之后pod开始evict。

目前我们采用的是默认的配置。

这里需要先了解kubelet和apiserver之间的交互过程。

k8s集群采用kubeadm init –config kubeadm-init.yaml进行高可用部署。

apiserver的高可用和etcd的高可用,采用的是static pod方式运行,基于容器方式运行k8s相关组件。

附录!

Kubernetes作为容器化应用集群管理系统,关键系统服务有 api-server, scheduler, controller-manager 三个。api-server 一方面作为 Kubernetes 系统的访问入口点,一方面作为背后 etcd 存储的代理服务器,Kubernetes 里的所有资源对象,包括 Service、Deployment、ReplicaSet、DaemonSet,Pod、Endpoint、ConfigMap、Secret 等等,都是通过 api-server 检查格式后,以 protobuf 格式序列化并存入 etcd。

未分类

Kubernetes为容器化应用提供了便利的资源调度, 部署运行,服务发现, 扩容缩容,自动运维等功能。

  1. 系统各个组件分工明确(APIServer是所有请求入口,CM是控制中枢,Scheduler主管调度,而Kubelet负责运行),配合流畅,整个运行机制一气呵成。

  2. 可以看出除了配置管理和持久化组件ETCD,其他组件并不保存数据。意味除ETCD外其他组件都是无状态的。因此从架构设计上对kubernetes系统高可用部署提供了支撑。

  3. 同时因为组件无状态,组件的升级,重启,故障等并不影响集群最终状态,只要组件恢复后就可以从中断处继续运行。

  4. 各个组件和kube-apiserver之间的数据推送都是通过list-watch机制来实现。所以理解list-watch机制是深入理解kubernetes的关键。

  5. 整体运行机制基于声明式数据(如nginx-depolyment.yaml)而非用户输入各种命令来工作,这是kubernetes最核心的设计理念。

list-watch机制分析

kubernetes中结合watch请求增加了list请求,主要做如下两件事情:

  1. watch请求开始之前,先发起一次list请求,获取集群中当前所有该类数据(同时得到最新的ResourceVersion),之后基于最新的ResourceVersion发起watch请求。

  2. 当watch出错时(比如说网络闪断造成客户端和服务端数据不同步),重新发起一次list请求获取所有数据,再重新基于最新ResourceVersion来watch。

kubernetes中基于ResourceVersion信息采用list-watch(http streaming)机制来保证组件间的数据实时可靠传送。

2 个 K8S 命令轻松部署深度学习模型

如今利用 Keras 构建深度学习模型已然成为一种风尚。Kubernetes 无需人工干预即可全部自动化完成任务的特点,使之成为部署学习模型的绝佳选择。今天我们就从 Keras 构建深度学习模型角度出发,通过 Flask 提供 REST API 服务,看看如何利用 Kubernetes 两个命令轻松部署深度学习模型。接下来,我将在 Google Cloud 上进行全程部署。

主要内容包括以下四部分:

  • 使用 Google Cloud 创建环境;
  • 使用 Keras、Flask 和 Docker 作为 API 提供深度学习模型;
  • 使用 Kubernetes 部署所述模型;
  • 结语。

使用 Google Cloud 创建环境

首先在 Google Compute Engine 上使用一个小型虚拟机来构建、服务和定位深度学习模型。我曾试图在 Windows 10 笔记本电脑上安装最新版本的 Docker CE(Community Edition),但我失败了。

所以我决定利用 Google Cloud 来进行部署,这要比弄清如何安装 Docker 更高效。

未分类

启动 Google Cloud VM,打开屏幕左侧的功能区,选择 “Select Compute” 再选择 “Create Instance”,你将看到已经运行了一个实例。

未分类

下一步选择使用的计算资源大小。默认(read: cheapest)设置可以正常工作,但鉴于我们最多只需要这个 VM 约 1 小时,我选择了 4vCPU 和 15GB 内存。

未分类

接下来,选择使用的操作系统和磁盘空间。我们选择 “Boot Disk” 以编辑默认值,将 CentOS 7 作为操作系统,并将磁盘容量从 10GB 增加到 100GB(CentOS 操作系统不是必要的)。我建议将磁盘大小增加到 10GB 以上,因为我们创建的 Docker 容器每个大约 1GB。

未分类

创建 VM 前需要将防火墙规则设置为:Allow HTTP traffic、Allow HTTPS traffic 。在模型部署到 Kubernetes 之前,我将进行防火墙设置,保证在 VM 上能测试到我们的 API。因此,检查下图这些方框是不够的 —— 还有更多工作要做。

未分类

现在点击 “Create”, Google Cloud 创建环境成功!

未分类

使用 Keras 构建深度学习模型

通过 SSH 进入到我们的 VM 并开始构建模型。最简单的方法是只需单击 VM 旁边的 SSH 图标(如下所示),浏览器会打开一个终端。

未分类

删除现有版本 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 以外的操作系统,这些命令会有所不同。

安装最新版本 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

启动 Docker 并运行测试脚本

sudo systemctl start docker
sudo docker run hello-world

如果你看到的输出如下所示,则完成设置。

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.

创建深度学习模型

对 Adrian Rosebrock 编写的脚本进行复制。我对 Adrian 脚本进行两次编辑才能让它运行起来。如果你不关心 Docker 和 Tensorflow 的细节,请跳过这两段。

与 Docker 有关问题:在本地运行应用程序的默认行为是在本地主机(127.0.0 …)上提供应用程序,但应用程序在 Docker 容器内运行时会出现问题。解决方法:当调用 app.run() 时,将 url 指定为 0.0.0.0,类似于 app.run( host=’0.0.0.0′ )。现在,我们的应用程序可在本地和外部 IP 上使用。

与 Tensorflow 有关问题:当我运行 Adrian 原始脚本时,我无法成功调用模型。利用下面的解决方案,对代码进行后续更改。

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 serverif __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')
view rawapp.py hosted with ❤ by GitHub

复制上述代码后,按 “Esc” 键退出编辑模式,键入保存并关闭文件 :x。

创建 requirements.txt 文件

我们将在 Docker 容器中运行上述代码。现在先创建一个 requirements.txt 文件。该文件将包含代码所需的运行包,例如 Keras、Flask 等。这样保证无论我们将 Docker 容器放置在哪,底层服务器都能安装代码所需依赖项。

创建并打开一个名为 “requirements” 的文件。通过键入 vim requirements.txt,用 vim 创建 txt 。将以下内容复制到 requirements.txt 中,进行保存并关闭。

keras
tensorflow
flask
gevent
pillow
requests

创建 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。

构建 Docker 容器

构建并测试应用程序,首先要构建 Docker 容器,请运行:

sudo docker build -t keras-app:latest .

这条代码指示 Docker 为当前工作中的代码构建容器 director keras-app。此命令将需要一两分钟才能完成。后台的 Docker 也会同时下载 Python 3.6 的镜像并安装requirements.txt 中列出的软件包。

运行 Docker 容器

运行 Docker 容器来测试应用:

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

关于 5000:5000 的简单说明 ——设置的 Docker 让外部端口 5000 可用,并将本地应用程序转发到该端口(也在本地端口 5000 上运行),通过检查运行容器的状态,运行命令 sudo docker ps -a 可看到类似如下内容:

[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

测试模型

让该模型接收一张狗的照片作为输入,命令返回狗的品种作为测试。

未分类

从终端运行:

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 部署模型

创建 Docker Hub 帐户

将构建的模型上传到 Docker Hub。(如果你没有 Docker Hub 帐户,请立即创建一个帐户)。我们不会将容器移动到 Kubernetes 集群中,而是指示 Kubernetes 从集中托管的服务器中(即 Docker Hub)安装我们的容器。

登录 Docker Hub 帐户

创建 Docker Hub 帐户后,从命令行登录 sudo docker login。你需要提供用户名和密码,就像登录网站一样。

如果你看到如下消息:

Login Succeeded

登录成功!

标记容器

在上传之前标记容器的命名。运行 sudo docker images 并找到 keras-app 容器的镜像 ID。

输出如下所示:

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

标记 keras-app,请务必遵循我的格式,将 image 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

容器传送到 Docker Hub

从 shell 运行容器:

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

当你导航回 Docker Hub 网站,将会看到 keras-app 存储库。

创建 Kubernetes 集群

在 Google Cloud 主屏幕上,选择 “Kubernetes Engine”。

未分类

再创建一个新的 Kubernetes 集群。

未分类

接下来,我们将自定义集群中的 Node 大小。我将选择 4vCPU 和 15 GB 的 RAM。你可以使用较小的集群尝试此操作。请记住,默认设置会启动 3 个 Node,因此你的集群将拥有你提供资源的 3 倍,即在我的情况下,45 GB 的 RAM。

未分类

单击进行创建,你需要一到两分钟才能启动集群。单击 “Click Run in Cloud Shell” 以显示 Kubernetes 集群的控制台。请注意,在你创建和测试的 Docker 容器中,这是一个独立于 VM 的 shell 环境。我们可以在 VM 上安装 Kubernetes,但 Google 的 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 正在运行,我们需要将端口 80 上的 Pod 暴露给外网。这意味着访问我们部署的 IP 地址的同时也可以访问我们的 API。也就是说我们不必在网址之后再指定一个端口号(告别 :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,打开本地终端(或任何有狗照片的地方)并运行以下命令来调用 API curl -X POST -F image=@dog.jpg 'http://<your service IP>/predict'

结果如下所示,API 正确返回图片的 beagle 标签:

$ 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}

结语

在本教程中,我们使用 Keras 和 Flask 作为 REST API 提供深度学习模型,而后我们将该应用程序放在 Docker 容器中,将容器上传到 Docker Hub,并使用 Kubernetes 进行部署。

只需两个命令,Kubernetes 就可以部署我们的应用程序并将其暴露给外网。是不是感觉很自豪?当然这个方案还有很多可以改进的空间。比如,我们可以将 Flask 中的 Python Web 服务器从本地 Python 服务器改为类似 gunicorn 产品级别的服务器;我们还可以探索 Kubernetes 的扩展性和管理功能等等。

备份 k8s yaml 配置

备份 k8s yaml 配置

for n in $(kubectl get -o=name pvc,configmap,serviceaccount,secret,ingress,service,deployment,statefulset,hpa,job,cronjob)
do
    mkdir -p $(dirname $n)
    kubectl get -o=yaml --export $n > $n.yaml
done

Apache Spark on K8s的安全性和性能优化

前言

Apache Spark是目前最为流行的大数据计算框架,与Hadoop相比,它是替换MapReduce组件的不二选择,越来越多的企业正在从传统的MapReduce作业调度迁移到Spark上来,Spark的生态圈支持者越来越多,当然它出众的内部API设计,让它也非常容易和现有既成事实的Hadoop组件(YARN/HDFS)集成。

容器技术的兴起,各种分布式的容器编排技术也应运而生,其中的佼佼者包括Apache Mesos和Google发起的Kubernetes,虽然Mesos系出名门(UC Berkely),但是K8s最近1-2年逐渐有拉大领先差距的趋势。

大数据计算框架,存储框架和云计算容器技术,这两者在数据中心的技术栈中,早期都是各自独立发展,并无太大关联关系,伴随各自技术生态圈日益成熟,几个大的技术框架各自领地划分也非常清楚,大数据计算服务与其他云计算服务并无二致,需要统一的资源调度与协调框架,让它的部署更加方便,资源利用率更加高效;从Mesos或者K8S的角度,作为致力于成为数据中心的OS(操作系统)的资源调度框架,放弃掉数据中心最大的应用场景之一的大数据计算服务,貌似也说不过去,于是,这两者慢慢也走在了一起。这篇文章,我们来探讨它们的重要代表,Apache Spark和K8s,当它们走在一起的时候,会碰到什么样的问题,以及社区的最新进展。

Apache Spark的分布式调度框架

回顾历史,Spark的资源调度器有这么几个实现:

  • Local模式,主要用于Spark应用程序开发调试使用,毕竟是单机单进程,设置断点,单步跟踪都十分方便;
  • Standalone模式,独占模式,启动应用程序时告诉调度器需要多少资源(多少CPU线程,多少内存),在应用程序的完整生命周期内,独享资源,哪怕资源闲置也无法分配给其他用户使用;
  • Yarn模式,Yarn模式本质功能和Standalone没有太大差别,只不过YARN是Hadoop生态系统中的重要组件,有着更好的多租户的概念,且有很多其他组件对于YARN的支持很完备,Spark为了和其他Hadoop生态组件共存,支持YARN模式也是大势所趋;好在YARN发展出了Dynamic Resource Allocation(动态资源分配),让类似Spark这种大数据计算框架,在部分计算任务结束时就能提前释放资源,让给同一集群中的其它用户或者程序使用,极大提高了系统资源利用率。
  • Mesos模式,由于和Spark师出同门,Spark在很早期就已经支持它了,当然Mesos在动态资源分配上也做得比YARN要早,并且支持容器技术,当初的理念是很先进的。
  • K8s模式,确实Mesos当初的理念是很先进的,但是架不住Google力推Kubernetes,K8s在容器编排和软件生态建设上,充分发挥了google工程师团队强大的工程能力,Mesos能提供的功能,基本K8s都能给。

为什么容器技术(和K8s)对于Spark很重要?

  • 在共享云计算平台多租户的应用场景下,每个用户都希望有独立的,隔离的应用环境,减少彼此调用都干扰,比如做Python调用时,不同用户对于Python的版本可能都会有不同要求。容器技术可以为不同用户和应用构建完全隔离,独立,可简单维护的运行环境。这是Hadoop时代的YARN,利用虚拟的资源分配技术所满足不了的。

  • YARN是大数据资源调度框架,而数据中心软件系统往往还包括数据库服务,web服务,消息服务等等其它应用程序,让这些完全不相干的应用友好共存,最大化资源利用率,是数据中心维护者的最大心愿,K8s碰巧又是可以完成这一使命的有力候选人。可以参见https://www.slidestalk.com/s/SparkonKubernetesforQcon_rev213672

Spark on K8S面临的问题和调整

作为最为流行的大数据计算框架Apache Spark,与Kubernetes的集成是成为当前比较热门的话题,这个工作目前是有Google的工程师在力推。除了计算需求以外,大数据还会有大量的数据存储在HDFS之上,当Kubernetes可以轻易调度Apache Spark,为它提供一个安全可靠的运行隔离环境时,数据在多用户之间的安全性又变得非常棘手,本文正是探讨了两个话题:如何利用Kerboros这样的认证体系,打通HDFS和Spark的作业执行的权限控制;出于性能的考虑,在Spark的Executor Pod如何仍然保持数据本地性调度优化。

具体细节可以参见示说网上的ppt文档: https://www.slidestalk.com/s/apache_spark_on_k8s_and_hdfs_security

基于k8s、docker、jenkins构建springboot服务

Jenkins + github + docker + k8s + springboot

本文介绍基于k8s、docker、jenkins、springboot构建docker服务。

环境准备

server-1 k8s-master Centos7 ip地址10.12.5.110server-2 k8s-node Centos7 ip地址10.12.5.115

两台服务执行如下命令

$ setenforce 0$ systemctl stop firewalld$ systemctl disable firewalld

server-1 k8s-master 安装

k8s-master节点安装kubernets、docker、etcd、git、maven等软件。

安装docker

$ yum install docker// 修改docker配置文件 /etc/sysconfig/dockerOPTIONS='--registry-mirror=https://docker.mirrors.ustc.edu.cn --selinux-enabled --log-driver=journald --signature-verification=false'ADD_REGISTRY='--add-registry 10.12.5.110:5000'INSECURE_REGISTRY='--insecure-registry 10.12.5.110:5000'$ systemctl enable docker$ service docker start

安装registry

$ docker pull registry$ mkdir -p /data/docker/registry$ docker run -d -p 5000:5000 -v /data/docker/registry:/var/lib/registry registry

安装etcd

$ yum install etcd$ vi /etc/etcd/etcd.conf// 修改如下配置ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"$ systemctl enable etcd$ systemctl start etcd

安装kubernetes

$ yum install kubernetes// 修改配置文件apiserver$ vi /etc/kubernetes/apiserver// 修改如下配置KUBE_API_ADDRESS="--insecure-bind-address=0.0.0.0"KUBE_API_PORT="--port=8080"KUBELET_PORT="--kubelet-port=10250"KUBE_ADMISSION_CONTROL="--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ResourceQuota"// 启动服务$ systemctl enable kube-apiserver kube-controller-manager kube-scheduler$ systemctl start kube-apiserver kube-controller-manager kube-scheduler

安装git和mvn

$ yum install git$ yum install maven

server-2 k8s-node 安装
k8s-master节点安装kubernets、docker等软件。

安装docker

$ yum install docker// 修改docker配置文件 /etc/sysconfig/dockerADD_REGISTRY='--add-registry 10.12.5.110:5000'INSECURE_REGISTRY='--insecure-registry 10.12.5.110:5000'$ systemctl enable docker$ systemctl start docker

安装kubernetes

$ yum install kubernetes// 修改配置文件kubelet$ vi /etc/kubernetes/kubelet// 修改如下配置KUBELET_ADDRESS="--address=0.0.0.0"KUBELET_PORT="--port=10250"KUBELET_HOSTNAME="--hostname-override=10.12.5.115"KUBELET_API_SERVER="--api-servers=http://10.12.5.110:8080"// 修改配置文件config$ vi /etc/kubernetes/config// 修改如下配置KUBE_MASTER="--master=http://10.12.5.110:8080"// 启动服务$ systemctl enable kube-proxy kubelet$ systemctl start kube-proxy kubelet

检查k8s集群运行情况

// 在k8s-master节点执行$ kubectl get nodesNAME          STATUS    AGE10.12.5.115   Ready     3m

在k8s-master节点builder springcloudenv镜像

* 下载jdk8$ mdkir -p /home/docker/docker-jdk8$ wget http://javadl.oracle.com/webapps/download/AutoDL?BundleId=233162_512cd62ec5174c3487ac17c61aaa89e8 -O jre-8u161-linux-x64.tar.gz$ tar zxvf jre-8u161-linux-x64.tar.gz* 删除jdk中用不到的文件,尽量减少docker镜像文件大小$ cd jre1.8.0_171$ rm -rf lib/plugin.jar lib/ext/jfxrt.jar bin/javaws lib/javaws.jar lib/desktop plugin lib/deploy* lib/*javafx* lib/*jfx* lib/amd64/libdecora_sse.so lib/amd64/libprism_*.so lib/amd64/libfxplugins.so lib/amd64/libglass.so lib/amd64/libgstreamer-lite.so lib/amd64/libjavafx*.so lib/amd64/libjfx*.so* 编写Dockerfile文件$ cd /home/docker/docker-jdk8$ vi DockerfileFROM centosMAINTAINER by wangtwRUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&     echo 'Asia/Shanghai' >/etc/timezone &&     yum -y install kde-l10n-Chinese &&     localedef -c -f UTF-8 -i zh_CN zh_CN.utf8 &&     mkdir -p /usr/javaCOPY jre1.8.0_171 /usr/java/jre1.8.0_171ENV LANG zh_CN.UTF-8ENV JAVA_HOME /usr/java/jre1.8.0_171ENV PATH $JAVA_HOME/bin:$PATH* 创建镜像springcloudenv,并上传到registry,确保docker registry已经启动$ docker build -t springcloudenv .$ docker tag springcloudenv springcloudenv:v1$ docker push springcloudenv:v1// 查看registry中的镜像$ curl http://10.12.5.110:5000/v2/_catalog{"repositories":["springcloudenv"]}

安装nfs

在k8s-master节点安装nfs server

$ yum install -y nfs-utils rpcbind$ mkdir -p /data/mysql-pv$ chmod 777 /data/mysql-pv/$ mkdir -p /data/nfs$ chmod 666 /data/nfs/$ vi /etc/exports// 增加如下行/data/nfs 10.12.0.0/16(rw,no_root_squash,no_all_squash,sync)/data/mysql-pv 10.12.0.0/16(rw,no_root_squash,no_all_squash,sync)$ systemctl enable rpcbind nfs$ systemctl start rpcbind nfs$ mkdir -p /data/mysql$ mount -t nfs 10.12.5.110:/data/nfs /data/mysql/ -o proto=tcp -o nolock

在k8s-node节点安装nfs

$ yum install -y nfs-utils$ mkdir -p /data/mysql$ mount -t nfs 10.12.5.110:/data/nfs /data/mysql/ -o proto=tcp -o nolock

安装mysql docker

以持久化方式运行myql

在k8s-master节点创建相关文件

$ mkdir -p /home/k8s/yml/services/mysql$ cd /home/k8s/yml/services/mysql$ vi mysql-pv.yamlapiVersion: v1kind: PersistentVolumemetadata:    name: mysql-pvspec:    accessModes:    - ReadWriteOnce    capacity:    storage: 1Gi    persistentVolumeReclaimPolicy: Retain#storageClassName: nfs    nfs:    path: /data/mysql-pv    server: 10.12.5.110$ vi mysql-pvc.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata:    name: mysql-pvcspec:  accessModes:    - ReadWriteOnce  resources:    requests:      storage: 1Gi  #storageClassName: nfs$ vi mysql.yamlapiVersion: v1kind: Servicemetadata:  name: mysqlspec:  ports:  - port: 3306  selector:    app: mysql---apiVersion: extensions/v1beta1kind: Deploymentmetadata:  name: mysqlspec:  replicas: 1  selector:    matchLabels:      app: mysql  template:    metadata:      labels:        app: mysql    spec:      containers:      - name: mysql        image: mysql:5.6        env:        - name: MYSQL_ROOT_PASSWORD          value: password        ports:        - containerPort: 3306          name: mysql        volumeMounts:         - name: mysql-persistent-storage          mountPath: /var/lib/mysql      volumes:      - name: mysql-persistent-storage        persistentVolumeClaim:          claimName: mysql-pvc

创建mysql service和pod

$ cd /home/k8s/yml/services/mysql$ kubectl create -f mysq-pv.yaml$ kubectl get pvNAME       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM               REASON    AGEmysql-pv   1Gi        RWO           Retain          Bound     default/mysql-pvc             2d$ kubectl create -f mysq-pvc.yaml$ kubectl get pvcNAME        STATUS    VOLUME     CAPACITY   ACCESSMODES   AGEmysql-pvc   Bound     mysql-pv   1Gi        RWO           2d$ kubectl create -f mysq.yaml$ kubectl get serviceNAME         CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGEmysql        10.254.151.113   <none>        3306/TCP   2d$ kubectl get podNAME                           READY     STATUS             RESTARTS   AGEmysql-3827607452-hd5ct         1/1       Running            80         19h

k8s-master节点安装和配置jenkins

$ mkdir /home/jenkins$ cd$ vi .bash_profileexport JENKINS_HOME=/home/jenkins$ cd /home/jenkins$ wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war$ java -jar jenkins.war --httpPort=9090&浏览器打开地址:http://10.12.5.110:9090把文件/home/jenkins/secrets/initialAdminPassword的内容复制到安装页面解决插件安装离线问题打开地址:http://10.12.5.110:9090/pluginManager/advanced把https://updates.jenkins.io/update-center.json修改为http://updates.jenkins.io/update-center.json安装插件:Publish Over SSH

创建jenkins任务,源代码在github上

* 任务名称:springboottest* 任务类型:自由风格* 创建github访问CredentialCredential类型:username with password* 源码管理 选择gitRepository URL 值为https://github.com/wangtiewu/springboottest.gitCredentials 值为上一步创建的 Credential* 创建构建脚本mvn clean packagetag=$( date +'%Y%m%d%H%M' )app=springcloudtestecho $tag > version_$app.txtdocker_name=$appdocker build -t $docker_name:$tag .docker push $docker_name:$tag* 创建ssh server系统管理-〉系统设置菜单-〉publish over ssh* 增加构建后操作,Send build artifacts over SSHSSH Server:name 值为k8s masterTransfer Set:Source files 值为version_springcloudtest.txtTransfer Set:Remote directory   值为springcloudtestTransfer Set:Exec command 值为cd /home/k8s/yml/services/springcloudtestapp=springcloudtestdeploy_file=$app.yamlversion_file=version_$app.txtpatch_file=$app.patchnew_version=$( cat $version_file )old_version=$( cat $deploy_file | grep image | awk -F ":" '{print $3}' )sed -i "s/$old_version/$new_version/" $deploy_filedeployment_num=$( /usr/bin/kubectl get deployment | grep springcloudtest | wc -l )if [ $deployment_num -eq 1 ];then    p_old_version=$( cat $patch_file | jq '.spec.template.spec.containers[0].image' )    p_new_version="$app:$new_version"    sed -i "s/$p_old_version/$p_new_version/" $patch_file    patch=$( cat $patch_file )    /usr/bin/kubectl patch deployment $app -p $patchelse    /usr/bin/kubectl create -f $deploy_filefi

创建springclouttest service和pod

$ mkdir -p /home/k8s/yml/services/springcloudtest$ cd /home/k8s/yml/services/springcloudtest$ vi springcloudtest-svr.yamlapiVersion: v1kind: Servicemetadata:  name: springcloudtestspec:  ports:  - name: springcloudtest-svr    port: 9091    nodePort: 30000    targetPort: 9091  selector:    app: springcloudtest  type: NodePort$ vi springcloudtest.yamlapiVersion: extensions/v1beta1kind: Deploymentmetadata:  name: springcloudtestspec:  replicas: 1  template:    metadata:      labels:        app: springcloudtest    spec:      containers:      - name: springclouttest        image: springcloudtest:201805272108        ports:         - containerPort: 9091          protocol: TCP$ vi springcloudtest.patch{"spec":{"template":{"spec":{"containers":[{"name":"springclouttest","image":"springcloudtest:201805052039"}]}}}}

构建jenkins任务:springclouttest

构建完成后,kubectl get pod 查看springcloudtest-1053936621-w19sh   1/1       Running   1          1d

创建springclouttest service

$ cd /home/k8s/yml/services/springcloudtest$ kubectl create -f springcloudtest-svr.yaml查看服务状态$ kubectl get servicespringcloudtest   10.254.126.13   <nodes>       9091:30000/TCP   45m在k8s-node查看容器情况$ docker psCONTAINER ID        IMAGE                                                        COMMAND                  CREATED             STATUS              PORTS               NAMES269366fa1b49        mysql:5.6                                                    "docker-entrypoint..."   About an hour ago   Up About an hour                        k8s_mysql.affc4af4_mysql-3827607452-lp1cp_default_15ace557-60ed-11e8-aa6f-0800276c68b7_404d6f6a2ff21fb61984        springcloudtest:201805272108                                 "/bin/sh -c 'java ..."   About an hour ago   Up About an hour                        k8s_springclouttest.c0ee0b5d_springcloudtest-1053936621-w19sh_default_a320f883-61b2-11e8-aa6f-0800276c68b7_a99e1b3788e9ba47faf1        registry.access.redhat.com/rhel7/pod-infrastructure:latest   "/usr/bin/pod"           About an hour ago   Up About an hour                        k8s_POD.389b0ddb_mysql-3827607452-lp1cp_default_15ace557-60ed-11e8-aa6f-0800276c68b7_7387fdc6130a60acd94d        registry.access.redhat.com/rhel7/pod-infrastructure:latest   "/usr/bin/pod"           About an hour ago   Up About an hour                        k8s_POD.2aaa0bac_springcloudtest-1053936621-w19sh_default_a320f883-61b2-11e8-aa6f-0800276c68b7_fa8aba88解决 NodePort方式下,Node所在节点可以访问服务,节点外客户端请求服务都会失败问题$ iptables -P FORWARD ACCEPT

创建数据库和表

$ docker exec -it 269366fa1b49 /bin/bash$ mysql -uroot -p输入密码mysql>create database test1 default charset utf8 COLLATE utf8_general_ci;mysql>use test1mysql>create table HELLO(ID bigint, NAME varchar(128), primary key (ID));

测试服务

浏览器打开 http://10.12.5.115:30000/浏览器显示 Hello World!$ curl --data '{"id":1,"name":"Hello World"}' -H "Content-type: Application/json" http://10.12.5.115:30000/create/hello

k8s master 节点和 node 节点 启动分析

Master 节点

  • coredns 2 个 ,对应的pause 容器 2 个
  • flannel 1 个 ,对应的pause 容器 1 个
  • proxy 1 个 ,对应的pause 容器 1 个
  • scheduler 1 个 ,对应的pause 容器 1 个
  • controller 1 个 ,对应的pause 容器 1 个
  • apiserver 1 个 ,对应的pause 容器 1 个
  • etcd 1 个 ,对应的pause 容器 1 个

未分类

master 节点 端口监听情况

kubelet TCP:10248
kubelet TCP: 42006
TCP6 10250

kube-proxy TCP 10249
TCP6 10256

kube-scheduler TCP: 10251

etcd TCP: 2379
etcd TCP: 2380

kube-controller TCP: 10252
TCP6 10257

kube-apiserver TCP6:6443

未分类

Node 节点

默认启动flannel ,proxy两个容器和它们对应的pause 容器,总共是四个

未分类

Node 节点 端口监听情况

kubelet TCP 41895
TCP 10248
TCP6 10250

kube-proxy TCP 10249
TCP6 10256

未分类

Kubernetes是什么?为什么也称为K8S?

这是一篇 Kubernetes 的概览。Kubernetes 是一个自动化部署、伸缩和操作应用程序容器的开源平台。

使用 Kubernetes,你可以快速、高效地满足用户以下的需求:

  • 快速精准地部署应用程序

  • 即时伸缩你的应用程序

  • 无缝展现新特征

  • 限制硬件用量仅为所需资源

目标是培育一个工具和组件的生态系统,以减缓在公有云或私有云中运行的程序的压力。

Kubernetes的优势

  • 可移动: 公有云、私有云、混合云、多态云

  • 可扩展: 模块化、插件化、可挂载、可组合

  • 自修复: 自动部署、自动重启、自动复制、自动伸缩

Google 公司于 2014 年启动了 Kubernetes 项目。Kubernetes 是在 Google 的长达 15 年的成规模的产品级任务的经验下构建的,结合了来自社区的最佳创意和实践经验。

为什么选择容器?

想要知道你为什么要选择使用容器?

未分类

程序部署的传统方法是指通过操作系统包管理器在主机上安装程序。这样做的缺点是,容易混淆程序之间以及程序和主机系统之间的可执行文件、配置文件、库、生命周期。为了达到精准展现和精准回撤,你可以搭建一台不可变的虚拟机镜像。但是虚拟机体量往往过于庞大而且不可转移。

容器部署的新的方式是基于操作系统级别的虚拟化,而非硬件虚拟化。容器彼此是隔离的,与宿主机也是隔离的:它们有自己的文件系统,彼此之间不能看到对方的进程,分配到的计算资源都是有限制的。它们比虚拟机更容易搭建。并且由于和基础架构、宿主机文件系统是解耦的,它们可以在不同类型的云上或操作系统上转移。

正因为容器又小又快,每一个容器镜像都可以打包装载一个程序。这种一对一的“程序 – 镜像”联系带给了容器诸多便捷。有了容器,静态容器镜像可以在编译/发布时期创建,而非部署时期。因此,每个应用不必再等待和整个应用栈其它部分进行整合,也不必和产品基础架构环境之间进行妥协。在编译/发布时期生成容器镜像建立了一个持续地把开发转化为产品的环境。相似地,容器远比虚拟机更加透明,尤其在设备监控和管理上。这一点,在容器的进程生命周期被基础架构管理而非被容器内的进程监督器隐藏掉时,尤为显著。最终,随着每个容器内都装载了单一的程序,管理容器就等于管理或部署整个应用。

容器优势总结

  • 敏捷的应用创建与部署:相比虚拟机镜像,容器镜像的创建更简便、更高效。

  • 持续的开发、集成,以及部署:在快速回滚下提供可靠、高频的容器镜像编译和部署(基于镜像的不可变性)。

  • 开发与运营的关注点分离:由于容器镜像是在编译/发布期创建的,因此整个过程与基础架构解耦。

  • 跨开发、测试、产品阶段的环境稳定性:在笔记本电脑上的运行结果和在云上完全一致。

  • 在云平台与OS上分发的可转移性:可以在 Ubuntu、RHEL、CoreOS、预置系统、Google 容器引擎,乃至其它各类平台上运行。

  • 以应用为核心的管理:从在虚拟硬件上运行系统,到在利用逻辑资源的系统上运行程序,从而提升了系统的抽象层级。

  • 松散耦联、分布式、弹性、无拘束的微服务:整个应用被分散为更小、更独立的模块,并且这些模块可以被动态地部署和管理,而不再是存储在大型的单用途机器上的臃肿的单一应用栈。

  • 资源隔离:增加程序表现的可预见性。

  • 资源利用率:高效且密集。

为什么我需要 Kubernetes,它能做什么?

至少,Kubernetes 能在实体机或虚拟机集群上调度和运行程序容器。而且,Kubernetes 也能让开发者斩断联系着实体机或虚拟机的“锁链”,从以主机为中心的架构跃至以容器为中心的架构。该架构最终提供给开发者诸多内在的优势和便利。Kubernetes 提供给基础架构以真正的以容器为中心的开发环境。

Kubernetes 满足了一系列产品内运行程序的普通需求,诸如:

  • 协调辅助进程,协助应用程序整合,维护一对一“程序 – 镜像”模型。

  • 挂载存储系统

  • 分布式机密信息

  • 检查程序状态

  • 复制应用实例

  • 使用横向荚式自动缩放

  • 命名与发现

  • 负载均衡

  • 滚动更新

  • 资源监控

  • 访问并读取日志

  • 程序调试

  • 提供验证与授权

以上兼具平台即服务(PaaS)的简化和基础架构即服务(IaaS)的灵活,并促进了在平台服务提供商之间的迁移。

Kubernetes 是一个什么样的平台?

虽然 Kubernetes 提供了非常多的功能,总会有更多受益于新特性的新场景出现。针对特定应用的工作流程,能被流水线化以加速开发速度。特别的编排起初是可接受的,这往往需要拥有健壮的大规模自动化机制。这也是为什么 Kubernetes 也被设计为一个构建组件和工具的生态系统的平台,使其更容易地部署、缩放、管理应用程序。

标签(label)可以让用户按照自己的喜好组织资源。 注释(annotation)让用户在资源里添加客户信息,以优化工作流程,为管理工具提供一个标示调试状态的简单方法。

此外,Kubernetes 控制面板是由开发者和用户均可使用的同样的 API 构建的。用户可以编写自己的控制器,比如 调度器(scheduler),使用可以被通用的命令行工具识别的他们自己的 API。

这种设计让大量的其它系统也能构建于 Kubernetes 之上。

Kubernetes 不是什么?

Kubernetes 不是传统的、全包容的平台即服务(Paas)系统。它尊重用户的选择,这很重要。

Kubernetes:

  • 并不限制支持的程序类型。它并不检测程序的框架 (例如,Wildfly),也不限制运行时支持的语言集合 (比如, Java、Python、Ruby),也不仅仅迎合 12 因子应用程序,也不区分 应用 与 服务 。Kubernetes 旨在支持尽可能多种类的工作负载,包括无状态的、有状态的和处理数据的工作负载。如果某程序在容器内运行良好,它在 Kubernetes 上只可能运行地更好。

  • 不提供中间件(例如消息总线)、数据处理框架(例如 Spark)、数据库(例如 mysql),也不把集群存储系统(例如 Ceph)作为内置服务。但是以上程序都可以在 Kubernetes 上运行。

  • 没有“点击即部署”这类的服务市场存在。

  • 不部署源代码,也不编译程序。持续集成 (CI) 工作流程是不同的用户和项目拥有其各自不同的需求和表现的地方。所以,Kubernetes 支持分层 CI 工作流程,却并不监听每层的工作状态。

  • 允许用户自行选择日志、监控、预警系统。( Kubernetes 提供一些集成工具以保证这一概念得到执行)

  • 不提供也不管理一套完整的应用程序配置语言/系统(例如 jsonnet)。

  • 不提供也不配合任何完整的机器配置、维护、管理、自我修复系统。

另一方面,大量的 PaaS 系统运行在 Kubernetes 上,诸如 Openshift、Deis,以及 Eldarion。你也可以开发你的自定义PaaS,整合上你自选的CI系统,或者只在 Kubernetes 上部署容器镜像。

因为 Kubernetes 运营在应用程序层面而不是在硬件层面,它提供了一些 PaaS 所通常提供的常见的适用功能,比如部署、伸缩、负载平衡、日志和监控。然而,Kubernetes 并非铁板一块,这些默认的解决方案是可供选择,可自行增加或删除的。

而且, Kubernetes 不只是一个编排系统 。事实上,它满足了编排的需求。 编排 的技术定义是,一个定义好的工作流程的执行:先做 A,再做 B,最后做 C。相反地, Kubernetes 囊括了一系列独立、可组合的控制流程,它们持续驱动当前状态向需求的状态发展。从 A 到 C 的具体过程并不唯一。集中化控制也并不是必须的;这种方式更像是编舞。这将使系统更易用、更高效、更健壮、复用性、扩展性更强。

Kubernetes 这个单词的含义?k8s?

Kubernetes 这个单词来自于希腊语,含义是 舵手 或 领航员 。其词根是 governor 和 cybernetic。 K8s 是它的缩写,用 8 字替代了“ubernete”。

『中级篇』k8s基础网络Cluster Network

通过国人大神的一键安装k8s集群安装了3个master节点和3个node节点的虚机,现在咱们的演示直接在开发机上运行就可以了dev机器。这次主要说说k8s相关的网络和网络相关的概念,service!源码:https://github.com/limingios/docker/tree/master/No.10

准备工作 创建2个pod

进入dev虚机上

cd ~
cd deployk8s-master/
cd pod-basic/
kubectl create -f pod_nginx.yml
kubectl create -f pod_tomcat.yml

未分类

未分类

未分类

进入node节点

  • 21,22,23 分别ping下pod的节点看能否ping通

ping nginx 都是可以ping通的

未分类

ping tomcat 都是可以ping通的

未分类

说明pod节点直接都是互相通信的

进入这3个node节点发现

他们都使用了Flannel的网络

未分类

未分类

未分类

详细看看官网怎么说

https://kubernetes.io/docs/concepts/cluster-administration/networking/

  • all containers can communicate with all other containers without NAT所有的容器和其他所有的容器之间可以直接通信,不需要经过NAT的转化

  • all nodes can communicate with all containers (and vice-versa) without NAT
    所有的节点可以直接访问其他节点的容器。the

  • IP that a container sees itself as is the same IP that others see it as
    容器自己的ip是什么别人直接访问他的时候就可以用这个ip地址。

k8s开源社区的插件太多了,支持插件的的,很早以前docker是不支持网络插件的,k8s的网络插件可以更方便的打通容器和节点。

flannel主要提供了跨主机间的容器通信;

  1. 在kubernetes的Pod、Service模型里,kube-proxy又借助iptables实现了Pod和Service间通信。
  2. 基于这种网络访问功能,我们平台提供了以下功能:
  3. 基于gorouter提供的平台域名的访问 – watch k8s endpoints event管理router信息;
  4. 基于skydns并定制化kube2sky组件和kubelet,提供同一命名空间下应用(Pod)之间基于业务域名的访问 – kube2sky基于k8s Service annotation解析并注册域名信息、kubelet设置容器启动时的domain search及外部dns;
  5. 实现容器tty访问控制台 – 每台k8s node部署平台组件 tty agent(根据Pod所属node信息, 建立对应k8s结点的tty连接);

PS:基础网络方便的通信使用k8s提供的满足它要求的插件。下节我们一起坐下如何让pod可以外部来访问。

超适合小项目的 K8S 部署策略

Kubernetes 的稳健性、可靠性使它成为现阶段最流行的云原生技术之一,但也有不少用户反映, Kubernetes 技术学习起来十分复杂,只适用于大集群且成本较高。这篇文章将打破你的观念,教你在小型项目中部署 Kubernetes 集群。

选择 K8S 部署小型集群的三大理由

理由一:花费时间少

在部署小型集群之前,你需要思考以下这些问题:

  • 应该如何部署应用程序?(仅仅 rsync 到服务器?)

  • 依赖关系是怎么样的?(如果利用 python 或 ruby,你必须在服务器上安装它们!)

  • 手动运行命令?(如果以 nohup 的方式在后台运行二进制文件这可能不是最好的选择,但去配置路由服务,是否还需要学习 systemd?)

  • 如何通过不同域名或 HTTP 路径运行多个应用程序?(你可能需要设置 haproxy 或 Nginx!)

  • 当更新应用程序后应该如何推出新变化?(停止服务、部署代码、重启服务?如何避免停机?)

  • 如果搞砸了部署怎么办?有什么方法可以回滚?

  • 应用程序是否需要使用其他服务?又该如何配置这些服务?(如:redis)

以上这些问题很有可能在你部署小型集群时出现,但 Kubernetes 为上述所有问题都提供了解决方案。或许还有其他方法可以解决上述问题,但是利用 Kubernetes 往往事半功倍,因为我们需要更多的时间专注于应用程序。

理由二:Kubernetes 记录整个部署过程

让我们看看利用 Kubernetes 部署集群的第二个理由。

你在工作时是否也是这样的状态:我上次运行了什么命令?当时服务器在运行什么服务?这让我想到了著名的 bash.org:

<erno> hm. I've lost a machine.. literally _lost_. it responds to ping, it works completely, I just can't figure out where in my apartment it is.

                                                                                                                            http://bash.org/?5273

这种情况曾经出现在我的工作中,让原本 10 分钟的工作量变成了一个周末。

但是如果你选择 Kubernetes 部署集群,就不会有这种困扰。因为 Kubernetes 使用描述性格式,如此用户就可以很轻松地知道接下来应该运行哪些内容,如何部署构建块。此外,控制层也会正常处理节点故障并自动重新调度 Pod。(对于像 Web 应用程序这样的无状态服务,就不再需要担心失败。)

理由三:Kubernetes 简单易学

Kubernetes 拥有自己的词汇表、工具,以及与传统 Unix 完全不同的配置服务器。Kubernetes 的知识足以建立和维护基础设施。使用 Kubernetes,你可以完全可以在 Kubernetes 中配置服务,无需 SSH 到服务器。你不必学习 systemd 也不必知道什么是运行级别; 你不必格式化磁盘,或学习如何使用 ps,vim。

我通过一个例子,来证明我的观点!

这是利用 Unix :

[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network.target remote-fs.target nss-lookup.target[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target

真的比利用 Kubernetes 难:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 1
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80

当你远程执行基础架构管理时,是不需要手动维护服务器的。你可以使用:ansible、salt、chef、puppet 等来完成这件事。当然,你需要学习使用很多 Kubernetes 相关工具,这要比学习替代品轻松的多。

小 结

Kubernetes 并不是将一件事做到极致的工具而是一个全方位的解决方案,它取代了开发人员习惯使用的许多技术和工具。

接下来我们用实际操作,为大家部署一个小型 Kubernetes 集群。

建立小型 Kubernetes 集群

下面就开始我们的教程。对于这个例子,我们将使用谷歌的 Kubernetes引擎(GKE),但如果谷歌不是你的菜,你也可以选择亚马逊(EKS)或微软(AKS)。

要构建我们的 Kubernetes 集群,我们将需要:

  • 域名(10 美元 /年,具体取决于域名);

  • DNS 主机由 cloudflare 提供(免费);

  • GKE 中的 3 个 node kubernetes 集群(5 美元 / 月);

  • 将 Webapp 作为 Docker 容器发送到 Google Container Registry(GCR)(免费);

  • 一些 yaml 文件配置 Kubernetes。

此外,为了节省成本,我们不会使用谷歌的 ingress controller 。相反,我们将在每个节点上运行 Nginx 作为 Daemon,并构建一个自定义运算符,将工作节点外部 IP 地址与 Cloudflare 同步。

谷歌设置

首先访问 console.cloud.google.com 并创建一个项目(如果你还没有项目)。你还需要设置结算帐户。然后前往 hamburger 菜单中的 Kubernetes 页面并创建一个新的集群。

你需要执行以下操作:

  • 选择 Zonal 区域类型(我使用了 us-central1-a 作为我的区域);

  • 选择你的 Kubernetes 版本;

  • 使用最便宜的实例类型(f1-micro)创建 3 个 node 池;

  • 对于该节点池,在高级屏幕中,将引导磁盘大小设置为 10GB,启用可抢占的 node(它们更便宜),启用自动升级和自动修复;

  • 在节点池下面还有一些其他选项。我们想要禁用 HTTP 负载均衡(GCP 中的负载均衡很昂贵且不稳定)并且还禁用所有 StackDriver 的服务以及禁用 Kubernetes dashboard 。

通过设置所有这些选项,你可以继续创建集群。

以下是减少的成本:

  • Kubernetes 控制层:免费,因为谷歌不收取专家的费用;

  • Kubernetes 工作节点:5.04 美元/月,3 个微节点通常为 11.65 美元/月,通过使用它们的可抢占性,我们将其降至 7.67 美元/月(“永久免费”等级则达到 5.04 美元);

  • 存储成本:免费,存储成本可以在 GCP 中累计。我们将免费获得 30GB 的永久磁盘,这就是我们选择 10GB 大小的原因;

  • 负载均衡器成本:免费,我们禁用 HTTP 负载均衡,因为仅此一项费用将达到 18 美元/月。相反,我们将在每个节点上运行我们自己的 HTTP 代理,并将 DNS 指向公共 IP;

  • 网络费用:免费,只要你每月低于 1GB,出口就是免费的(之后每GB 8 美分)。

因此,我们可以拥有一个 3 个节点的 Kubernetes 集群,价格与单个数字机器相同。

除了设置 GKE 之外,我们还需要添加一些防火墙规则,以允许外网点击我们节点上的 HTTP 端口。操作是:从 hamburger 菜单转到 VPC 网络,防火墙规则添加为 TCP 端口 80 和 443 的规则,IP 范围为 0.0.0.0/0。

未分类

本地设置

随着集群的启动和运行,我们就可以对其进行配置。通过 cloud.google.com/sdk/docs 的说明安装 gcloud 工具。

安装完成后,你可以通过运行以下命令进行设置:

gcloud auth login

你还需安装 Docker,将其连接到 GCR 上,方便你进行容器推送:

gcloud auth configure-docker

你也可以按照此处的说明安装和设置 kubectl (此工具适用于 Windows,OSX 或 Linux)。

gcloud components install kubectl
gcloud config set project PROJECT_ID
gcloud config set compute/zone COMPUTE_ZONE
gcloud container clusters get-credentials CLUSTER_NAME

构建 Web 应用程序

你可以使用任何编程语言构建 Web 应用。我们只需构建一个 port 端口的 HTTP 应用程序。就个人而言,我更喜欢在 Go 中构建这些应用程序,但对于某些类型,让我们尝试使用 Crystal。

创建一个 main.cr 文件:

# crystal-www-example/main.crrequire "http/server"Signal::INT.trap do
  exit
end

server = HTTP::Server.new do |context|
  context.response.content_type = "text/plain"
  context.response.print "Hello world from crystal-www-example! The time is #{Time.now}"end

server.bind_tcp("0.0.0.0", 8080)
puts "Listening on http://0.0.0.0:8080"server.listen

我们还需要一个 Dockerfile:

# crystal-www-example/Dockerfile
FROM crystallang/crystal:0.26.1 as builder

COPY main.cr main.cr

RUN crystal build -o /bin/crystal-www-example main.cr --release

ENTRYPOINT [ "/bin/crystal-www-example" ]

我们可以通过以下命令来构建和测试我们的 Web 应用程序:

docker build -t gcr.io/PROJECT_ID/crystal-www-example:latest .
docker run -p 8080:8080 gcr.io/PROJECT_ID/crystal-www-example:latest

然后输入 localhost:8080 在浏览器中访问。接着我们可以通过以下方式将我们的应用程序推到 GCR 中运行:

docker push gcr.io/PROJECT_ID/crystal-www-example:latest

配置 Kubernetes

对于此示例,我们将创建几个 yaml 文件来表示各种服务,然后通过运行 kubectl apply 在集群中配置它们。Kubernetes 的配置具有描述性,这些 yaml 文件将会告诉 Kubernetes 我们希望看到的状态。

我们需要做的事情:

  • 为我们的 crystal-www-example Web 应用程序创建部署和服务;

  • 为 Nginx 创建一个 Daemon Set 和 Config Map;

  • 运行自定义应用程序使用 Cloudflare 到 DNS 同步 node IP。

Web App 配置

首先让我们配置 webapp:(先将 PROJECT_ID 替换为你的项目 ID)

# kubernetes-config/crystal-www-example.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: crystal-www-example
  labels:
    app: crystal-www-example
spec:
  replicas: 1
  selector:
    matchLabels:
      app: crystal-www-example
  template:
    metadata:
      labels:
        app: crystal-www-example
    spec:
      containers:
      - name: crystal-www-example
        image: gcr.io/PROJECT_ID/crystal-www-example:latest
        ports:
        - containerPort: 8080---

kind: Service
apiVersion: v1
metadata:
  name: crystal-www-example
spec:
  selector:
    app: crystal-www-example
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080

现在创建一个 Deployment,它会通知 Kubernetes 创建一个 Pod,其中包含一个运行 Docker 容器的容器,以及一个用于集群内的 service discovery 。要应用此配置运行(在 kubernetes-config 文件夹中):

kubectl apply -f .

我们可以使用以下方法测试它是否在运行:

kubectl get pod
# you should see something like:
# crystal-www-example-698bbb44c5-l9hj9          1/1       Running   0          5m

我们还可以创建代理 API,以便我们访问它:

kubectl proxy

然后访问:

http://localhost:8001/api/v1/namespaces/default/services/crystal-www-example/proxy/

Nginx 配置

通常,在 Kubernetes 中处理 HTTP 服务时,你会使用 ingress controller。不幸的是,Google 的 HTTP 负载均衡器非常昂贵,因此我们将运行自己的 HTTP 代理并手动配置它。

我们将使用 Daemon Set 和 Config Map。Daemon Set 是在每个节点上运行的应用程序。Config Map 基本上是一个小文件,我们可以在容器中安装它,我们将存储 Nginx 配置。

yaml 看起来像这样:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
      - image: nginx:1.15.3-alpine
        name: nginx
        ports:
        - name: http
          containerPort: 80
          hostPort: 80
        volumeMounts:
        - name: "config"
          mountPath: "/etc/nginx"
      volumes:
      - name: config
        configMap:
          name: nginx-conf

---

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
data:
  nginx.conf: |
    worker_processes 1;
    error_log /dev/stdout info;

    events {
      worker_connections 10;
    }

    http {
      access_log /dev/stdout;

      server {
        listen 80;
        location / {
          proxy_pass http://crystal-www-example.default.svc.cluster.local:8080;
        }
      }
    }

你可以看到我们如何在 Nginx 容器内挂载 config map 的 nginx.conf。我们还在规范上设置了两个附加字段:hostNetwork: true、dnsPolicy: ClusterFirstWithHostNet 。

  • hostNetwork: true 以便我们可以绑定主机端口并从外网到达 Nginx;

  • dnsPolicy: ClusterFirstWithHostNet 以便我们可以访问集群内的服务。

应用更改:通过点击节点的公共 IP 来到达 Nginx。

你可以通过运行找到:

kubectl get node -o yaml
# look for:
# - address: ...
#   type: ExternalIP

我们的网络应用程序现在可通过互联网访问了。现在想想给它起一个好听的名字!

连接 DNS

我们需要 A 为集群的节点设置 3 条 DNS 记录:

未分类

然后添加一个 CNAME 条目以指向那些 A 记录。(即 www.example.com CNAME kubernetes.example.com)我们可以手动执行此操作,但最好自动执行此操作,以便在扩展或替换节点时 DNS 记录自动更新。

我认为这也是一个很好的说明示例,说明如何让 Kubernetes 为你工作而不是反对它。Kubernetes 完全可编写脚本,并且具有强大的 API。因此你可以使用不太难编写的自定义组件填补空白。我为此构建了一个小型 Go 应用程序,可在此处找到:kubernetes-cloudflare-sync。

建立一个 informer:

factory := informers.NewSharedInformerFactory(client, time.Minute)
lister := factory.Core().V1().Nodes().Lister()
informer := factory.Core().V1().Nodes().Informer()
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
  AddFunc: func(obj interface{}) {
    resync()
  },
  UpdateFunc: func(oldObj, newObj interface{}) {
    resync()
  },
  DeleteFunc: func(obj interface{}) {
    resync()
  },
})
informer.Run(stop)

informer 将在节点更改时随时调用我的 resync 函数。然后使用 Cloudflare API 库(github.com/cloudflare/cloudflare-go)同步 IP ,类似于:

var ips []stringfor _, node := range nodes {  for _, addr := range node.Status.Addresses {    if addr.Type == core_v1.NodeExternalIP {
      ips = append(ips, addr.Address)
    }
  }
}
sort.Strings(ips)for _, ip := range ips {
  api.CreateDNSRecord(zoneID, cloudflare.DNSRecord{
    Type:    "A",
    Name:    options.DNSName,
    Content: ip,
    TTL:     120,
    Proxied: false,
  })
}

就像我们的网络应用程序一样,我们将此应用程序作为 Kubernetes 中的部署:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubernetes-cloudflare-sync
  labels:
    app: kubernetes-cloudflare-sync
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kubernetes-cloudflare-sync
  template:
    metadata:
      labels:
        app: kubernetes-cloudflare-sync
    spec:
      serviceAccountName: kubernetes-cloudflare-sync
      containers:
      - name: kubernetes-cloudflare-sync
        image: gcr.io/PROJECT_ID/kubernetes-cloudflare-sync
        args:
        - --dns-name=kubernetes.example.com
        env:
        - name: CF_API_KEY
          valueFrom:
            secretKeyRef:
              name: cloudflare
              key: api-key
        - name: CF_API_EMAIL
          valueFrom:
            secretKeyRef:
              name: cloudflare
              key: email

你需要使用 cloudflare api 密钥和电子邮件地址创建 Kubernetes 密码:

kubectl create secret generic cloudflare --from-literal=email='EMAIL' --from-literal=api-key='API_KEY'

你还需要创建服务帐户(它允许我们的部署访问 Kubernetes API 来检索节点)。首次运行(特别是对于 GKE):

kubectl create clusterrolebinding cluster-admin-binding --clusterrole cluster-admin --user YOUR_EMAIL_ADDRESS_HERE

然后申请:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: kubernetes-cloudflare-sync
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kubernetes-cloudflare-sync
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-cloudflare-sync-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubernetes-cloudflare-sync
subjects:
- kind: ServiceAccount
  name: kubernetes-cloudflare-sync
  namespace: default

配置就绪后,运行 Cloudflare 的应用程序将在任何节点更改时被更新。以上就是基于 Kubernetes 部署小型集群的详细教程。

总 结

Kubernetes 就是这样一个收放自如的技术。就像你可能永远用不到 SQL 数据库中的所有功能,但你不得不承认 SQL 数据库极大地提高了你快速交付解决方案的能力。

Kubernetes 与 SQL 十分相似。在 Kubernetes 庞大的技术体系下,我们也并不能用到所有功能,却能在每个项目中恰到好处的使用部分功能实现完美部署。在每次利用 Kubernetes 部署小型集群时,我都会从中获得新的认知。

所以我的观点是,Kubernetes 对于小型部署也很有意义,而且既易于使用又便宜。如果你从来没有尝试过,现在就开动起来吧!

END