Skip to main content

11 篇博文 含有标签「kubernetes

View All Tags

· 6 分钟阅读

前言

将 Java 应用容器化虽然更好地解决了可移植性问题,但也存在着一些不友好的情况,比如低版本的JDK(低于Java 8u131)并不能识别 CGroup 资源限制。这将导致JVM读取的是宿主机的全部CPU和内存,一但容器使用资源超过限制则会被 docker 杀死。

在 kubernetes 中,我们会显示在 yaml 文件中配置CPU、内存请求和限制,我们希望容器中的JVM进程能够自动识别到 CGroup 资源限制,获取到正确的内存和CPU信息从而自行动态调整。

JVM 参数配置

以下操作皆在一台 4C 16G 服务器上进行。

版本低于 8u131

JDK 版本低于 8u131 版本的 JVM 不会自动识别到 CGroup 资源限制,需要手动设置初始堆大小以及最大堆大小,否则会按照宿主机的全部内存设置默认值:

  • 配置最大堆大小 -Xmx,默认值:内存的1/4
  • 配置初始堆大小 -Xms,默认值:内存的1/64

未配置JVM参数

可以看到 Max. Heap Size (Estimated): 3.48G,未能正确识别 CGroup 资源限制

$ docker run --rm -m 2GB openjdk:8u121-alpine java -XshowSettings:vm -version

VM settings:
Max. Heap Size (Estimated): 3.48G
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_121"
OpenJDK Runtime Environment (IcedTea 3.3.0) (Alpine 8.121.13-r0)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)

配置JVM参数

配置 -Xmx-Xms 后即可达到我们想要的结果

$ docker run --rm -m 2GB openjdk:8u121-alpine java -XshowSettings:vm -Xmx2000m -Xms2000m -version

VM settings:
Min. Heap Size: 1.95G
Max. Heap Size: 1.95G
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_121"
OpenJDK Runtime Environment (IcedTea 3.3.0) (Alpine 8.121.13-r0)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)

8u131 及以上版本

从 8u131 版本开始支持 UseCGroupMemoryLimitForHeapMaxRAMFraction 这两个选项,用 CGroupMemory 的大小作为 JVM heap size,MAXRAMFraction 是用来控制实际可用的内存数量的,比如设置为 1 的话就是 CGroupMemoryLimit 的全部,设置为 2 的话一半,3 的话就是 1/3,以此类推

| MaxRAMFraction取值 | 堆占比 | 容器内存=1G | 容器内存=2G | 容器内存=4G | 容器内存=8G | 容器内存=16G | | :--------------------: | :--------- | :-------------: | :-------------- | :-------------: | :-------------- | :--------------: || | | | | | | | 1 | ≈90% | 910.50M | 1.78G | 3.56G | 7.11G | 14.22G | | 2 | ≈50% | 455.50M | 910.50M | 1.78G | 3.56G | 7.11G | | 3 | ≈33% | 304.00M | 608.00M | 1.19G | 2.37G | 4.74G | | 4 | ≈25% | 228.00M | 455.50M | 910.50M | 1.78G | 3.56G |

未配置JVM参数

可以看到 Max. Heap Size (Estimated): 3.48G,未能正确识别 CGroup 资源限制

$ docker run --rm -m 2GB openjdk:8u131-alpine java -XshowSettings:vm  -version

VM settings:
Max. Heap Size (Estimated): 3.48G
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_131"
OpenJDK Runtime Environment (IcedTea 3.4.0) (Alpine 8.131.11-r2)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)

配置JVM参数

配置 -XX:+UnlockExperimentalVMOptions-XX:+UseCGroupMemoryLimitForHeap-XX:MaxRAMFraction=1 后即可达到我们想要的结果

$ docker run --rm -m 2GB openjdk:8u131-alpine java -XshowSettings:vm -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -version

VM settings:
Max. Heap Size (Estimated): 1.78G
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_131"
OpenJDK Runtime Environment (IcedTea 3.4.0) (Alpine 8.131.11-r2)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)

8u191 及以上版本

从 8u191 开始引入了 java10+ 上的 UseContainerSupport 选项,而且是默认启用的,不用设置。同时 UseCGroupMemoryLimitForHeap 这个就弃用了,不建议继续使用,同时还可以通过 -XX:InitialRAMPercentage-XX:MaxRAMPercentage-XX:MinRAMPercentage 这些参数更加细腻的控制 JVM 使用的内存比率。比如一些 Java 程序在运行时会调用外部进程、申请 Native Memory 等,所以即使是在容器中运行 Java 程序,也得预留一些内存给系统的。所以 -XX:MaxRAMPercentage 不能配置得太大。

未配置JVM参数

可以看到未添加任何 JVM 参数即可正确识别到 CGroup 资源限制

$ docker run --rm -m 2GB openjdk:8u191-alpine java -XshowSettings:vm -version

VM settings:
Max. Heap Size (Estimated): 455.50M
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_191"
OpenJDK Runtime Environment (IcedTea 3.10.0) (Alpine 8.191.12-r0)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)

配置JVM参数

  • 使用 -XX:MaxRAMFraction 参数调整 Max. Heap Size 大小
  $ docker run --rm -m 2GB openjdk:8u191-alpine java -XX:MaxRAMFraction=1 -XshowSettings:vm -version

VM settings:
Max. Heap Size (Estimated): 1.78G
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_191"
OpenJDK Runtime Environment (IcedTea 3.10.0) (Alpine 8.191.12-r0)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)
  • 使用 -XX:InitialRAMPercentage-XX:MaxRAMPercentage-XX:MinRAMPercentage 参数更加细腻的控制 JVM 使用的内存比率
  $ docker run --rm -m 2GB openjdk:8u191-alpine java -XX:InitialRAMPercentage=40.0 -XX:MaxRAMPercentage=90.0 -XX:MinRAMPercentage=50.0 -XshowSettings:vm -version

VM settings:
Max. Heap Size (Estimated): 1.60G
Ergonomics Machine Class: server
Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_191"
OpenJDK Runtime Environment (IcedTea 3.10.0) (Alpine 8.191.12-r0)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)

参考资料

· 10 分钟阅读

Prometheus 是 Kubernetes 中默认的监控方案,它专注于告警和收集存储最近的监控指标。但在一定的集群规模下,Prometheus 也暴露出一些问题。例如:如何以经济可靠的方式存储 PB 级别的历史数据,并且不牺牲查询时间?如何通过单一的查询接口访问到不同 Prometheus 服务器上的所有指标数据?能否以某种方式合并采集到的重复数据?针对以上的这些问题, Thanos 提供了高可用的的解决方案,并且它有着不受限制的数据存储能力。

Thanos

Thanos 基于 Prometheus。当我们以不同方式使用 Thanos 时,或多或少都会用到 Prometheus 功能,但是 Prometheus 始终是指标收集和使用本地数据进行预警功能的基础。

Thanos 使用 Prometheus 存储格式,把历史数据以相对高性价比的方式保存在对象存储里,同时兼有较快的查询速度。此外,它还能对你所有的 Prometheus 提供全局查询视图。

依据 KISS 原则和 Unix 哲学,Thanos 划分如下特定功能的组件。

  • 边车组件(Sidecar):连接Prometheus,并把Prometheus暴露给查询网关(Querier/Query),以供实时查询,并且可以上传Prometheus数据给云存储,以供长期保存;
  • 查询网关(Querier/Query):实现了Prometheus API,与汇集底层组件(如边车组件Sidecar,或是存储网关Store Gateway)的数据;
  • 存储网关(Store Gateway):将云存储中的数据内容暴露出来;
  • 压缩器(Compactor):将云存储中的数据进行压缩和下采样;
  • 接收器(Receiver):从Prometheus’ remote-write WAL(Prometheus远程预写式日志)获取数据,暴露出去或者上传到云存储;
  • 规则组件(Ruler):针对数据进行评估和报警;

组件之间的关系如图:

部署

在 K8S 集群中部署 Prometheus 最简单的方法是用 helm 安装 prometheus-operator。更多关于 Prometheus-operator 的内容请阅读《Prometheus-operator 介绍和配置解析 》。Prometheus-Operator 提供了高可用的支持,Thanos 边车组件(Sidecar)的注入,以及监控服务器、监控 Kubernetes 基础组件,可以监控应用所需的预制报警。

Choerodon 提供的 Prometheus-opertor 在社区版的基础上添加多集群监控的仪表盘。

本篇文章部署架构图如下所示:

对象存储

目前 thanos 支持大部分云厂商的对象存储服务,具体使用请参考thanos 对象存储。本文使用 minio 代替 S3 对象存储。为了方便将 minio 安装在 Observability 集群。

编写 minio 参数配置文件minio.yaml:

mode: distributed
accessKey: "AKIAIOSFODNN7EXAMPLE"
secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
persistence:
enabled: true
storageClass: nfs-provisioner
ingress:
enabled: true
path: /
hosts:
- minio.example.choerodon.io

执行安装命令:

helm install c7n/minio \
-f minio.yaml \
--version 5.0.4 \
--name minio \
--namespace monitoring

登录 minio 创建一个 thanos 桶。

在每个集群中都创建一个存储secret。

  • 配置文件 thanos-storage-minio.yaml

    type: s3
    config:
    bucket: thanos
    endpoint: minio.example.choerodon.io
    access_key: AKIAIOSFODNN7EXAMPLE
    secret_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    insecure: true
    signature_version2: false
  • 创建存储的 secret:

    kubectl -n monitoring create secret generic thanos-objectstorage --from-file=thanos.yaml=thanos-storage-minio.yaml

Observability 集群安装 Promethues-operator

在 Observability 集群中安装的 Promethues-operator 需要安装 grafana 并且修改默认的 datasource 为 Thanos 的 Query 组件。Observability-prometheus-operator.yaml 配置文件如下:

grafana:
persistence:
enabled: true
storageClassName: nfs-provisioner
ingress:
enabled: true
hosts:
- grafana.example.choerodon.io
additionalDataSources:
- name: Prometheus
type: prometheus
url: http://thanos-querier:9090/
access: proxy
isDefault: true
sidecar:
datasources:
defaultDatasourceEnabled: false
prometheus:
retention: 12h
prometheusSpec:
externalLabels:
cluster: observe # 添加 cluster 标签区分集群
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: nfs-provisioner
resources:
requests:
storage: 10Gi
thanos:
baseImage: quay.io/thanos/thanos
version: v0.10.1
objectStorageConfig:
key: thanos.yaml
name: thanos-objectstorage

安装 prometheus-operator 集群

helm install c7n/prometheus-operator \
-f Observability-prometheus-operator.yaml \
--name prometheus-operator \
--version 8.5.8 \
--namespace monitoring

A\B 集群安装 Promethues-operator

A\B 集群中就只需要安装 prometheus 相关的组件,grafana、alertmanager 等组件不再需要安装,配置文件proemtheus-operator.yaml 如下:

alertmanager:
enabled: false

grafana:
enabled: false

prometheus:
retention: 12h
prometheusSpec:
externalLabels:
cluster: a-cluster # 添加 cluster 标签区分集群
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: nfs-provisioner
resources:
requests:
storage: 10Gi
thanos:
baseImage: quay.io/thanos/thanos
version: v0.10.1
objectStorageConfig:
key: thanos.yaml
name: thanos-objectstorage

安装 prometheus-operator 集群

helm install c7n/prometheus-operator \
-f prometheus-operator.yaml \
--name prometheus-operator \
--version 8.5.8 \
--namespace monitoring

为 Thanos SideCar 创建子域名分别指向集群A/B

thanos-a.example.choerodon.io
thanos-b.example.choerodon.io

以 A 集群为例创建 ingress 规则

apiVersion: v1
kind: Service
metadata:
labels:
app: prometheus
name: thanos-sidecar-a
spec:
ports:
- port: 10901
protocol: TCP
targetPort: grpc
name: grpc
nodePort: 30901
selector:
statefulset.kubernetes.io/pod-name: prometheus-prometheus-operator-prometheus-0
type: NodePort
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
labels:
app: prometheus
name: thanos-sidecar-0
spec:
rules:
- host: thanos-a.example.choerodon.io
http:
paths:
- backend:
serviceName: thanos-sidecar-a
servicePort: grpc

Observability 集群安装 thanos

使用 kube-thanos 安装 Thanos。

安装需要的软件工具:

$ yum install -y golang
$ go get github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb
$ go get github.com/brancz/gojsontoyaml
$ go get github.com/google/go-jsonnet/cmd/jsonnet

使用 jsonnet-bundler 安装 kube-thanos

$ mkdir my-kube-thanos; cd my-kube-thanos
$ jb init # Creates the initial/empty `jsonnetfile.json`
# Install the kube-thanos dependency
$ jb install github.com/thanos-io/kube-thanos/jsonnet/kube-thanos@master # Creates `vendor/` & `jsonnetfile.lock.json`, and fills in `jsonnetfile.json`

更新 kube-thanos 依赖

$ jb update

创建 example.jsonnet

local k = import 'ksonnet/ksonnet.beta.4/k.libsonnet';
local sts = k.apps.v1.statefulSet;
local deployment = k.apps.v1.deployment;
local t = (import 'kube-thanos/thanos.libsonnet');

local commonConfig = {
config+:: {
local cfg = self,
namespace: 'monitoring',
version: 'v0.10.1',
image: 'quay.io/thanos/thanos:' + cfg.version,
objectStorageConfig: {
name: 'thanos-objectstorage',
key: 'thanos.yaml',
},
volumeClaimTemplate: {
spec: {
accessModes: ['ReadWriteOnce'],
storageClassName: ''
resources: {
requests: {
storage: '10Gi',
},
},
},
},
},
};

local s = t.store + t.store.withVolumeClaimTemplate + t.store.withServiceMonitor + commonConfig + {
config+:: {
name: 'thanos-store',
replicas: 1,
},
};

local q = t.query + t.query.withServiceMonitor + commonConfig + {
config+:: {
name: 'thanos-query',
replicas: 1,
stores: [
'dnssrv+_grpc._tcp.%s.%s.svc.cluster.local' % [service.metadata.name, service.metadata.namespace]
for service in [s.service]
],
replicaLabels: ['prometheus_replica', 'rule_replica'],
},
};


{ ['thanos-store-' + name]: s[name] for name in std.objectFields(s) } +
{ ['thanos-query-' + name]: q[name] for name in std.objectFields(q) }

创建 build.sh

#!/usr/bin/env bash

# This script uses arg $1 (name of *.jsonnet file to use) to generate the manifests/*.yaml files.

set -e
set -x
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail

# Make sure to start with a clean 'manifests' dir
rm -rf manifests
mkdir manifests

# optional, but we would like to generate yaml, not json
jsonnet -J vendor -m manifests "${1-example.jsonnet}" | xargs -I{} sh -c 'cat {} | gojsontoyaml > {}.yaml; rm -f {}' -- {}

# The following script generates all components, mostly used for testing

rm -rf examples/all/manifests
mkdir -p examples/all/manifests

jsonnet -J vendor -m examples/all/manifests "${1-all.jsonnet}" | xargs -I{} sh -c 'cat {} | gojsontoyaml > {}.yaml; rm -f {}' -- {}

执行以下命令创建 K8S 资源文件

$ ./build.sh example.jsonnet

对于生成的资源文件有两处需要修改

$ vim manifests/thanos-store-statefulSet.yaml
------------------------------------------------------
spec:
containers:
- args:
- store
- --data-dir=/var/thanos/store
- --grpc-address=0.0.0.0:10901
- --http-address=0.0.0.0:10902
- --objstore.config=$(OBJSTORE_CONFIG)
# - --experimental.enable-index-header # 注释掉这多余的一行
env:

$ vim manifests/thanos-query-deployment.yaml
------------------------------------------------------
containers:
- args:
- query
- --grpc-address=0.0.0.0:10901
- --http-address=0.0.0.0:9090
- --query.replica-label=prometheus_replica
- --query.replica-label=rule_replica
- --store=dnssrv+_grpc._tcp.thanos-store.monitoring.svc.cluster.local
# 添加本集群和 A/B 集群的 Store API
- --store=dnssrv+_grpc._tcp.prometheus-operated.monitoring.svc.cluster.local
- --store=dns+thanos-a.example.choerodon.io:30901
- --store=dns+thanos-b.example.choerodon.io:30901

创建 Thanos

$ kubectl create -f manifests/

通过端口转发查看 Thanos Query 是否正常

$ kubectl port-forward svc/thanos-query 9090:9090 -n monitoring

现在访问 http://grafana.example.choerodon.io 就可以查看多集群的监控信息。

总结

通过以上步骤就完成了使用 Thanos 的 Prometheus 多集群监控的安装,该方案具有长期存储的功能,并且可以查看跨集群的监控信息。


关于猪齿鱼

Choerodon 猪齿鱼作为全场景效能平台,是基于Kubernetes,Istio,knative,Gitlab,Spring Cloud来实现本地和云端环境的集成,实现企业多云/混合云应用环境的一致性。平台通过提供精益敏捷、持续交付、容器环境、微服务、DevOps等能力来帮助组织团队来完成软件的生命周期管理,从而更快、更频繁地交付更稳定的软件。

Choerodon 猪齿鱼v0.21已经发布,欢迎大家前来安装/升级。

更加详细的内容,请参阅Release Notes官网

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

欢迎加入Choerodon猪齿鱼社区,共同为企业数字化服务打造一个开放的生态平台。

· 11 分钟阅读

随着 Kubernetes 的广泛使用,如何保证集群稳定运行,成为了开发和运维团队关注的焦点。在集群中部署应用时,像忘记配置资源请求或忘记配置限制这样简单的事情可能就会破坏自动伸缩,甚至导致工作负载耗尽资源。这样种种的配置问题常常导致生产中断,为了避免它们我们用 Polaris 来预防。Polaris 是 Fairwinds 开发的一款开源的 Kubernetes 集群健康检查组件。通过分析集群中的部署配置,从而发现并避免影响集群稳定性、可靠性、可伸缩性和安全性的配置问题。

Choerodon 作为全场景效能平台,同样也是使用Kubernetes来部署和升级应用,如何保证集群稳定运行是十分关键的。为了满足 Choerodon 特性,Choerodon 团队借鉴了 Polaris 健康检查的实现原理,结合Choerodon的实际业务需求,在 Agent 组件中实现了一套自己的健康检查规则,用于更细粒度的检查集群,监控集群健康状态,从而保证集群稳定运行。通过对 Polaris 的实践,总结了一些对Polaris的认识和安装使用,希望对大家有所帮助。

关于Agent组件的功能,请参考Choerodon猪齿鱼 Agent——基于GitOps的云原生持续交付模型

Polaris的功能

Polaris是一款通过分析部署配置,从而发现集群中存在的问题的健康检查组件。当然,Polaris的目标可不仅仅只是发现问题,同时也提供避免问题的解决方案,确保集群处于健康状态。下面将会介绍Polaris的主要功能: Polaris 包含3个组件,分别实现了不同的功能:

  • Dashboard - 以图表的形式查看当前Kubernetes workloads的工作状态和优化点。
  • Webhook - 阻止在集群中安装不符合标准的应用
  • CLI - 检查本地的yaml文件,可结合CI/CD使用

Dashboard

Dashboard是polaris提供的可视化工具,可以查看Kubernetes workloads状态的概览以及优化点。也可以按类别、名称空间和工作负载查看。

概览集群状态

  • 查看集群健康评分
  • 查看集群检查结果
  • 查看集群版本、节点、pod、名称空间数量

按类别查看检查结果

  • Health Checks
  • Images
  • Networking
  • Resources
  • Security

按名称空间查看检查结果

Webhook

Polaris可以作为一个admission controller运行,作为一个validating webhook。它接受与仪表板相同的配置,并可以运行相同的验证。这个webhook将拒绝任何触发验证错误的workloads 。这表明了Polaris更大的目标,不仅仅是通过仪表板的可见性来鼓励更好的配置,而是通过这个webhook来实际执行它。Polaris不会修复workloads,只会阻止他们。

  • 使用和dashboard相同的配置
  • 阻止所有部署配置不通过的应用安装到集群
  • 不仅仅能够查看集群当前存在的缺陷,还能预防缺陷

CLI

在命令行上也可以使用Polaris来审计本地文件或正在运行的集群。这对于在CI/CD管道的基础设施代码上运行Polaris特别有帮助。如果Polaris给出的审计分数低于某个阈值,或者出现任何错误,可使用命令行标志来导致CI/CD失败。

  • 检查本地文件或正在运行的集群
  • 可以结合CI/CD,部署配置校验不通过时直接让CI/CD失败

安装与使用

如何安装polaris

polaris支持kubectl, helm and local binary三种安装方式,本文选择最简单的安装方式,分别介绍三个组件的安装,详细的安装教程参考polaris安装

1.Dashboard安装

Helm

添加helm charts仓库

helm repo add reactiveops-stable https://charts.reactiveops.com/stable

更新charts仓库并安装Dashboard组件

helm upgrade --install polaris reactiveops-stable/polaris --namespace polaris

如果需要在本地查看Dashboard仪表盘,可以使用以下命令,进行本地端口转发

kubectl port-forward --namespace polaris svc/polaris-dashboard 8080:80

2.Webhook安装

在集群中安装Webhook组件后,将会阻止不符合标准的应用部署在集群中。

Helm

添加helm charts仓库

helm repo add reactiveops-stable https://charts.reactiveops.com/stable

更新charts仓库并安装Webhook组件

helm upgrade --install polaris reactiveops-stable/polaris --namespace polaris \
--set webhook.enable=true --set dashboard.enable=false

3.CLI安装

如果需要在本地测试polaris,可以下载二进制文件安装 releases page,也可以使用 Homebrew安装:

brew tap reactiveops/tap
brew install reactiveops/tap/polaris
polaris --version

使用CLI检查本地配置文件

polaris --audit --audit-path ./deploy/

可以将扫描结果保存到yaml文件中

polaris --audit --output-format yaml > report.yaml

如何使用Polaris

上面简单的介绍了,polaris的安装与基本使用。但是,如果要根据我们项目的实际情况来结合polaris,使用默认配置就不能满足需求了。所以我们还需要知道如何定义polaris检查规则的配置文件,实现自定义配置。 在自定义配置polaris之前,我们需要先了解一下polaris检查的等级以及支持的检查类型。 polaris检查的严重等级分为errorwarningignore ,polaris不会检查ignore等级的配置项。 polaris支持的检查类型有:Health ChecksImagesNetworkingResourcesSecurity,下面我们将一一介绍:

健康检查(Health Checks)

Polaris 支持校验pods中是否存在readiness和liveiness探针。

keydefaultdescription
healthChecks.readinessProbeMissingwarning没有为pod配置readiness探针时失败。
healthChecks.livenessProbeMissingwarning没有为pod配置liveness探针时失败。

镜像(Images) | key | default | description | | ---------------------------- | -------- | ----------------------------------------- | | images.tagNotSpecified | error | 没有为镜像指定tag或者指定 latest时失败. | | images.pullPolicyNotAlways | ignore | 当镜像拉取策略不是 always时失败. | 网络(Networking)

keydefaultdescription
networking.hostNetworkSetwarning配置了 hostNetwork 时失败.
networking.hostPortSetwarning配置了 hostPort 时失败.

资源(Resources)

polaris支持校验内存、cpu使用限制是否配置

确保相关配置存在:
keydefaultdescription
resources.cpuRequestsMissingerror没有配置 resources.requests.cpu 时失败.
resources.memoryRequestsMissingerror没有配置 resources.requests.memory 时失败.
resources.cpuLimitsMissingerror没有配置 resources.limits.cpu 时失败.
resources.memoryLimitsMissingerror没有配置 resources.limits.memory 时失败.

对于内存、cpu等资源配置,还可以配置范围检查。只有当配置在指定区间内才可以通过检查。

limits:
type: object
required:
- memory
- cpu
properties:
memory:
type: string
resourceMinimum: 100M
resourceMaximum: 6G
cpu:
type: string
resourceMinimum: 100m
resourceMaximum: "2"

安全(Security)

keydefaultdescription
security.hostIPCSeterror配置了 hostIPC 属性时失败.
security.hostPIDSeterror配置了 hostPID 属性时失败.
security.notReadOnlyRootFileSystemwarningsecurityContext.readOnlyRootFilesystem 不是 true时失败.
security.privilegeEscalationAllowederrorsecurityContext.allowPrivilegeEscalation 属性是 true是失败.
security.runAsRootAllowederrorsecurityContext.runAsNonRoot 属性不是 true时失败.
security.runAsPrivilegederrorsecurityContext.privileged 属性是true时失败.

根据上文的介绍,我们已经可以根据项目的实际情况,定义自己的扫描配置。如果觉得polaris提供的检查规则不满足需求的话,我们还可以自定义检查规则。 比如:我们可以自定义规则检查镜像来源,当镜像来自quay.io抛出警告

checks:
imageRegistry: warning
customChecks:
imageRegistry:
successMessage: Image comes from allowed registries
failureMessage: Image should not be from disallowed registry
category: Images
target: Container # target can be "Container" or "Pod"
schema:
'$schema': http://json-schema.org/draft-07/schema
type: object
properties:
image:
type: string
not:
pattern: ^quay.io

还可以配置集群中的检查白名单,比如跳过检查dns-controller是否设置hostNetwork

exemptions:
- controllerNames:
- dns-controller
rules:
- hostNetworkSet

关于猪齿鱼

Choerodon 猪齿鱼作为全场景效能平台,是基于Kubernetes,Istio,knative,Gitlab,Spring Cloud来实现本地和云端环境的集成,实现企业多云/混合云应用环境的一致性。平台通过提供精益敏捷、持续交付、容器环境、微服务、DevOps等能力来帮助组织团队来完成软件的生命周期管理,从而更快、更频繁地交付更稳定的软件。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 21 分钟阅读

Kubernetes 的安全是一个相当广泛的主题,涉及很多高度相关的内容。和探讨大部分安全性相关的问题一样,首先需要考虑威胁模型——谁可能攻击你的系统,以及他们如何做到这一点。这可以帮你确定安全工作的优先级。对于大多数 Kubernetes 应用有三类主要的攻击者:

  1. 外部攻击者:当你在内部或云上部署应用时,你可能面临来自集群外的攻击。这类攻击者没有系统权限,所以会专注于公开的服务,会尝试获取访问权限并提升权限。
  2. 泄露的容器:Kubernetes 集群通常运行着各种工作负载。攻击者也可能利用集群中运行的容器的漏洞进行攻击,在这种时候,要最大程度降低漏洞攻击影响到整个集群的风险。攻击者可以访问单个容器的资源,因此限制容器权限至关重要。
  3. 恶意用户:Kubernetes 是一个多用户系统。攻击者可能拥有某用户的账户,并企图获得更多权限,这种情况比较复杂,要具体分析,需要限制不同用户的访问权限。

围绕云原生基础概念构建的模型可以帮你建立对 Kubernetes 安全的总体认识。下图将安全划分为四个层级,被称为云原生安全的4C模型。管理员将在不同的层次上应对三类攻击者。

1.png

4C 指的是云(Cloud)、集群(Cluster),容器(Containers)和代码(Code)

正如你在图中所看见的,4C 模型中每部分的安全性都是相互包含的。只依靠增强代码层次安全来预防云、集群和容器中安全漏洞是几乎不可能的。适当提高其他层的安全能力,就已经为你的代码提供强大的基础安全保障。下面将详细介绍这四部分内容。

Cloud

大多数情况下,云为 Kubernetes 集群提供可信的计算资源。如果云的基础设置是不可靠的(或以易受到攻击的方式配置),那就没有办法保证在这个基础上构建的 Kubernetes 集群的安全性。每一个云服务提供商都向他们的客户提供大量如何在其环境安全运行负载的建议。下面提供常用云服务厂商的安全文档链接,并且提供了构建安全 Kubernetes 集群的建议。

云服务提供商安全文档列表

云服务厂商链接
阿里云https://www.alibabacloud.com/trust-center
AWShttps://aws.amazon.com/security/
Google Cloud Platformhttps://cloud.google.com/security/
Microsoft Azurehttps://docs.microsoft.com/en-us/azure/security/azure-security

如果你运行在自己的硬件上或者其他云服务提供商,请查阅文档获取最佳安全实践。

通用的安全建议

  • 理想情况下,不开放 Kubernetes Master 节点公网访问,并且限制能够访问集群 Master 节点的 IP 地址。

  • 通过网络安全组等配置工作节点只接受 Master 节点上指定端口的连接,并且接受 Kubernetes 中服务类型为 NodePort 和 LoadBalancer 的连接。如果可能,这些节点也不应该暴露在公网中。

  • Kubernetes 访问云服务提供商的 API,每一个云服务提供商都赋予 Kubernetes 的 Master 和 Node 不同的权限。该访问权限遵循其管理资源所需资源的最小权限原则。

  • 访问 etcd, 只有在 master 节点中通过 TLS 可以访问 etcd。

  • 在所有可能情况下,对停用状态的的驱动设备加密。ectd 拥有整个集群的状态(包括密钥信息),因此对其磁盘要在其停用的时候加密。

Cluster

Kubernetes 是一个复杂的系统,下图展示了一个集群不同的组成部分,为了保证集群整体的安全,需要仔细保护每一个组件。

2.png

将需要注意保护的集群组件划分成两个部分:

  1. 保护组成集群的可配置组件
  2. 保护运行在集群中的应用

集群的组件

  1. 控制对 Kubernetes API 的访问

    使用 TLS 进行安全通讯。Kubernetes 期望默认情况下使用 TLS 对集群中所有 API 通信进行加密,大多数安装方式都会创建必要的证书并分发给集群组件。

    API 认证。在安装集群时,为API服务器选择与通用访问模式匹配的身份验证机制。例如,小型单用户集群可能希望使用简单的证书或静态Bearer令牌方法。较大的群集可能希望集成现有的OIDC或LDAP服务器,以将用户细分为多个组。更多有信息请参考认证

    API 授权。一旦通过身份验证,每个API调用也都有望通过授权检查。Kubernetes 附带了一个集成的基于角色的访问控制(RBAC)组件,该组件将传入的用户或组与捆绑到角色中的一组权限进行匹配。这些权限将动词(get,create,delete)与资源(pods,service,nodes)结合在一起,并且可以是命名空间或集群作用域。提供了一组现成的角色,这些角色根据客户端可能要执行的操作提供合理的默认责任分离。更多有关信息请参考授权

  2. 控制对 Kubelet 的访问。

    Kubelet 在端口10250和10255上提供小型 REST API。端口10250是读/写端口,而10255是具有API端点子集的只读端口。这些端点授予对节点和容器的强大控制权。默认情况下,Kubelets允许未经身份验证对此API进行访问。生产集群应启用 Kubelet 身份验证和授权,可以通过设置 --read-only-port=0 来禁用只读端口,但是10250 端口用于系统指标收集和其他重要功能,所以开发者必须仔细控制对此端口的访问。更多有关信息请参考Kubelet身份验证/授权

  3. 保护集群组件不受损坏

    • 限制对 etcd 的访问。对API的etcd后端的写入访问权等同于在整个集群上获得root权限,而读取访问权也可以获取集群信息。管理员应始终使用从API服务器到etcd服务器的强凭据,例如通过TLS客户端证书的相互身份验证,通常建议 etcd服务器隔离在仅API服务器可以访问的防火墙后面。
    • 限制对Alpha或Beta功能的访问。Alpha和Beta Kubernetes功能正在积极开发中,并且可能会存在限制或错误,从而导致安全漏洞。始终评估Alpha或Beta功能可能提供的价值,以防对您的安全状况造成潜在风险。如有疑问,请禁用不使用的功能。
    • 启用审核日志记录。审核记录器是一项Beta版功能,可记录API的行为,在出现问题后提供分析依据。
    • 经常轮换基础设施凭证。密钥和凭证生存周期越短,就越难被攻击者利用。
    • 在启用第三方集成之前,请先进行审查。
    • 停用时加密 ectd。etcd数据库将包含可通过Kubernetes API访问的所有信息,可能使攻击者深入了解群集的状态。
    • 接收有关安全更新和报告漏洞的警报。

集群中的应用

根据应用程序受到的攻击,关注安全的特定方面。例如,运行中的服务 A 对于其他应用非常重要,而服务 B 容易受到资源耗尽攻击,如果不设置服务 B 的资源限制,就会因为服务 B 耗尽资源导致服务 A 不可用。所以需要注意下面几个常见安全措施:

  • 定义资源限制

    默认情况下,Kubernetes 集群中对所有资源没有对 CPU 、内存和磁盘的使用限制。可以通过创建资源配额策略,并附加到 namespace 中来限制资源的使用。

    下面的例子将限制命名空间中Pod 的数量为4个,CPU使用在1和2之间,内存使用在1GB 和 2GB 之间。

    # compute-resources.yaml
    apiVersion: v1
    kind: ResourceQuota
    metadata:
    name: compute-resources
    spec:
    hard:
    pods: "4"
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi
    requests.nvidia.com/gpu: 4

    分配资源配额到命名空间:

    kubectl create -f ./compute-resources.yaml --namespace=myspace

    查看 namespace 资源使用情况:

    [root@localhost ~]# kubectl describe quota compute-resources --namespace=myspace
    Name: compute-resources
    Namespace: myspace
    Resource Used Hard
    -------- ---- ----
    limits.cpu 0 2
    limits.memory 0 2Gi
    pods 0 4
    requests.cpu 0 1
    requests.memory 0 1Gi
    requests.nvidia.com/gpu 0 4

    也可以为 Pod 添加资源限制,设置内存请求为 256MiB,内存限制为512MiB

    apiVersion: v1
    kind: Pod
    metadata:
    name: default-mem-demo
    spec:
    containers:
    - name: default-mem-demo-ctr
    image: nginx
    resources:
    limits:
    memory: 512Mi
    requests:
    memory: 256Mi
  • Pod 安全策略

    作为 Pod 的组成部分,容器通常配置有非常实用的安全默认值,这些默认值适用于大多数情况。但是,有时 Pod 可能需要其他权限才能执行其预期任务,在这种情况下,我们需要增强 Pod 的默认权限。安全上下文定义了 Pod 或容器的特权和访问控制,安全上下文设置有:

    1. 委托访问:基于用户ID 和用户组ID 权限去访问资源对象(如文件)。

    2. SELinux:为对象分配安全标签

    3. 以特权或非特权用户运行

    4. Linux 功能:为进程提供一些特权,而不去赋予它root用户的所有特权

    5. AppArmor:使用程序配置文件来限制单个程序的功能。

    6. Seccomp:过滤进程的系统调用

    7. 允许提升权限(AllowPrivilegeEscalation):子进程能否获得父进程更多的权限。这个布尔值直接控制是否在容器进程上设置了 no_new_privs 标志。在以下情况下 AllowPrivilegeEscalation 始终为 true——一是,以特权OR运行;二是,具有 CAP_SYS_ADMIN

      securityContext 字段下配置安全策略,在 Pod 中指定的安全策略将被应用于所有容器,容器中指定的安全策略应用于单个容器,当它们重复时,容器级会覆盖 Pod 级的的安全策略。

      apiVersion: v1
      kind: Pod
      metadata:
      name: security-context-demo
      spec:
      securityContext:
      runAsUser: 1000
      runAsGroup: 3000
      fsGroup: 2000
      volumes:
    - name: sec-ctx-vol
    emptyDir: {}
    containers:
    - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
    mountPath: /data/demo
    securityContext:
    runAsUser: 2000
    allowPrivilegeEscalation: false

    更多有关信息请参考[Pod 安全策略](https://kubernetes.io/docs/concepts/policy/pod-security-policy/)

  • 网络规则

    Kubernetes允许来自集群中任何容器的所有网络流量发送到集群中的任何其他容器并由其接收。当尝试隔离工作负载时,这种开放式方法没有帮助,因此需要应用网络策略来帮助开发者实现所需的隔离。Kubernetes NetworkPolicy API使开发者能够将入口和出口规则应用于选定的Pod,用于第3层和第4层流量,并依赖于实现容器网络接口(CNI)的兼容网络插件的部署。

    网络策略是 namespace 作用域,并根据匹配标签(例如,tier:backend)的选择应用于Pod。当NetworkPolicy对象的Pod选择器与Pod匹配时,根据策略中定义的入口和出口规则来管理进出Pod的流量。 所有来自或发往该Pod的流量都会被拒绝,除非有允许它的规则。

    要在Kubernetes集群中正确隔离网络和传输层的应用程序,网络策略应以“拒绝所有”的默认前提开始。然后,应将每个应用程序组件及其所需源和目标的规则逐个列入白名单,并进行测试以确保流量模式按预期工作。

  • 密钥管理

    Kubernetes 使用 Secrets 保护应用程序需要访问敏感信息——密码、X.509证书、SSH密钥或OAuth令牌等。它通过卷装入,对它的访问严格限于那些需要使用它的实体(用户和Pod),且当存储在磁盘上时(静止状态)它是不可访问或只读的。

需要考虑的全部安全问题如下:

需要关注的安全领域建议
RBAC 授权https://kubernetes.io/docs/reference/access-authn-authz/rbac/
认证方式https://kubernetes.io/docs/reference/access-authn-authz/controlling-access/
密钥管理https://kubernetes.io/docs/concepts/configuration/secret/ https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/
Pod 安全规则https://kubernetes.io/docs/concepts/policy/pod-security-policy/
服务质量https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/
网络规则https://kubernetes.io/docs/concepts/services-networking/network-policies/
ingress 的 TLShttps://kubernetes.io/docs/concepts/services-networking/ingress/#tls

Contanier

为了在 Kunernetes 中运行软件,必须将它打包成容器。需要考虑下面的注意事项,来保证容器符合 Kubernetes 安全配置。

  • 最小化镜像

    理想情况下,使用应用程序二进制文件和二进制文件所依赖的的任何相关项来创建镜像。事实上,没有什么阻止开发者将 scratch 作为基础镜像,并将静态链接的二进制文件复制到镜像中。它没有其他依赖,镜像中包含单个文件,以及一些描述容器如何运行的元数据。最小化镜像加快镜像的分发速度,并且显著减少容器内部的受攻击面。

  • 基础镜像

    但是这并不总是可行的,因此需要慎重选择基础镜像。最佳的方法是使用操作系统供应商支持的镜像,或者是 docker hub 上官方支持的镜像。不要盲目使用之前未经过审查的不受信任来源的镜像,尤其是在生产环境中。

  • 镜像扫描

    镜像扫描对于保障容器应用的安全至关重要,确保您的镜像定期通过信誉良好的工具进行扫描,可以使用 CoreOS 的 Clair 之类的工具扫描容器中的已知漏洞,它是容器镜像的静态漏洞分析器。

  • 镜像的签名和执行

    使用 CNCF 项目的的 TUF 和 Notary 对容器进行签名,在执行前验证签名,确保镜像的正确没有被篡改。

  • 禁止特权用户

    在构建镜像时,创建最低操作系统权限的用户完成容器的运行。

Code

最后是代码级别,这是程序员能掌控的被攻击面,但不在 Kubernetes 的安全保护范围,提供如下建议:

  • 仅通过 TLS 访问

    如果您的代码需要通过TCP进行通信,则理想情况下,它将提前与客户端执行TLS握手。除少数情况外,默认行为应是对传输中的所有内容进行加密。

  • 限制通信范围

    无论什么情况下,程序只应该公开必要的端口。

  • 第三方依赖安全性

    确保第三方依赖是安全的。

  • 静态代码分析

    大多数语言都提供了静态代码分析,可以分析代码中是否存在潜在的不安全编码实践。

  • 动态探测攻击

    自动化工具可以针对您的服务尝试会破坏服务的攻击。这些包括SQL注入,CSRF和XSS。最受欢迎的动态分析工具之一是OWASP Zed Attack代理。

更多关于Kubernetes系列的文章欢迎阅读:

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 15 分钟阅读

本文是从 0 到 1 使用 Kubernetes 系列第七篇,上一篇《从 0 到 1 使用 Kubernetes 系列(六):数据持久化实战》 介绍了 Kubernetes 中的几种常用储存类型,本文将介绍 K8S 网络相关的内容。

不同宿主机上运行的容器并不能通过 IP 相互访问,那么 Kubernetes 是如何实现不同节点上 Pod 的互通?Pod 有生命周期,它的 IP 会随着动态的创建和销毁而动态变化,Kubernetes 又是怎样对外提供稳定的服务?今天就为大家一一解答这些疑问。

Docker 网络

先来看一下 Docker 中的网络。在启动 Docker 服务后,默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。

Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了 MTU(接口允许接收的最大传输单元),通常是 1500 Bytes,或宿主主机网络路由上支持的默认值,这些值都可以在服务启动的时候进行配置。

root@ubuntu:/root# ifconfig
...
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 0.0.0.0
ether 02:42:d2:00:10:6c txqueuelen 0 (Ethernet)
...
root@ubuntu:/root# docker inspect busybox
···
"IPAddress": "172.17.0.2",
···

为了实现上述功能,Docker 主要用到了 linux 的 BridgeNetwork NamespaceVETH

  • Bridge 相当于是一个虚拟网桥,工作在第二层网络。也可以为它配置 IP,工作在三层网络。docker0 网关就是通过 Bridge 实现的。
  • Network Namespace 是网络命名空间,通过 Network Namespace 可以建立一些完全隔离的网络栈。比如通过 docker network create xxx 就是在建立一个 Network Namespace
  • VETH 是虚拟网卡的接口对,可以把两端分别接在两个不同的 Network Namespace 中,实现两个原本隔离的 Network Namespace 的通信。

所以总结起来就是:Network Namespace 做了容器和宿主机的网络隔离,Bridge 分别在容器和宿主机建立一个网关,然后再用 VETH 将容器和宿主机两个网络空间连接起来。但这都是在同一个主机上的网络实现,如果想要在多台主机上进行网络就得看看下面介绍的 Kubernetes 网络。

Kubernetes 网络

Kubernetes 为了解决容器的“跨主通信”问题,提出了很多解决方案。常见思路有两种:

  • 直接在宿主机上建立不同宿主机上子网的路由规则;
  • 通过特殊的网络设备封装二层数据帧,根据目标 IP 地址匹配到对应的子网找到对应的宿主机 IP 地址,最后将转发 IP 包,目的宿主机上同样的特殊网络设备完成解封并根据本机路由表转发。

Flannel

大家所熟知的 Flannel 项目是 CoreOS 公司推出的容器网络解决方案。它本身只是一个框架,为开发者提供容器网络功能的是 Flannel 的后端实现。目前有如下三种具体实现:

  • UDP
  • VXLAN
  • host-gw

下面的三层网络指的是七层网络模型中的底部的三层:网络层、数据链路层和物理层。

UDP 模式是最早支持,性能最差,但最容易理解和实现的容器跨主网络方案。Flannel UDP 模式提供的是一个三层的覆盖网络:首先对发出端的 IP 包进行 UDP 封装,然后在接受端进行解封拿到原始的 IP 包,进而把这个包转发给目标容器。它相当于在两个容器之间打通一条“隧道”,使得两个容器可以直接使用 IP 通信,而不关心容器和宿主机的分布情况。

Flannel UDP 模式原理图

因为 Flannel 进行 UDP 封装和解封都是在用户态完成,而在 Linux 系统中上下文切换和用户态操作的代价非常大,这就是它性能不好的主要原因。

VXLAN 即 Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络虚拟化技术。VXLAN 在内核态就完成了上面的封装和解封工作,通过与 UDP 模式类似的“隧道”机制,构建出覆盖网络(Overlay Network),使得连接在这个 VXLAN 二层网络的“主机”可以像在局域网自由通信。

Flannel VXLAN 模式原理图

host-gw 模式的工作原理是将每一个 Flannel 子网的下一跳设置为该子网对应的宿主机 IP 地址。

也就是说,这台“主机”(host)会充当这条容器通信路径里的“网关”(Getway)。Flannel host-gw 模式必须要求集群宿主机之间是二层连通的

Flannel host-gw 示意图

Calico

Calico 项目提供的网络解决方案与 Flannel Host-gw 模式同理。但是不同于 Flannel 通过 Etcd 和宿主机的 flanneld 来维护路由信息得做法,Calio 项目使用 BGP(边界网关协议) 来自动的在整个集群中分发路由消息。它由三部分组成:

Calico 的 CNI 插件:这是 Calico 与 Kubernetes 对接的部分。 Felix:它是一个 DaemonSet,负责在宿主机插入路由规则,以及维护 Calico 所需的网络设备等。 BIRD:它是 BGP 的客户端,负责在集群里分发路由规则信息。

除了对路由信息的维护方式之外,Calico 项目和 Flannel 的 host-gw 另一个不同是它不会在宿主机上创建任何网桥设备。

Calico 工作原理图

CNI(容器网络接口)

CNI)是 CNCF 旗下的一个项目,由一组用于配置 Linux 容器的网络接口的规范和库组成,同时还包含了一些插件。CNI 仅关心容器创建时的网络分配,和当容器被删除时释放网络资源。其基本思想为: Kubernetes 在启动 Infra 容器之后,就可以直接调用 CNI 网络插件,为这个 Infra 容器的 Network Namespace 配置符合预期的网络栈。

Kubernetes 使用 CNI 接口,维护一个单独的网桥来代替 docker0。这个网桥就叫做 CNI 网桥,它在宿主机上的默认名称是:cni0。以 Flannel 的 VXLAN 模式为例,在 Kubernetes 环境里,它的工作方式没有变化,只是 docker0 网桥替换成了 CNI 网桥。CNI 网桥只是接管所有 CNI 插件负责的,即 Kuberntes 创建的容器(Pod)。

Flannel vxlan cni 版

Service

Kubernetes 中 Pod 有生命周期,它的 IP 会随着动态的创建和销毁而动态变化,不能稳定的提供服务。Kubernetes Service 定义这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略。开发者可以通过一个 Service 的入口地址访问其背后的一组 Pod。一旦 Service 被创建,Kubernetes 就会自动为它分配一个可用的 Cluster IP,在 Service 的整个生命周期中它的 Cluster IP 都不会发生改变。这样就解决了分布式集群的服务发现。

一个典型的 Service 定义如下:

apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- nmae: dafault
protocol: TCP
port: 8000
targetPort: 80

在这个 Service 例子中,笔者使用 selector 字段声明这个 Service 只代理 app=nginx 标签的 pod。这个 Service 的 8000 端口代理 Pod 的 80 端口。

然后定义应用 Delpoyment 如下:

apiVersion: v1
kind: Delpoyment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
meatdata:
lalels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containers: 80
protocol: TCP

被 selector 选中的 Pod,就被称为 Serivce 的 Endpoints,你可以使用 kubectl get ep 查看它们,如下所示:

$ kubectl get endpoints nginx
NAME ENDPOINTS AGE
nginx 172.20.1.16:80,172.20.2.22:80,172.20.2.23:80 1m

通过该 Service 的 VIP 10.68.57.93 地址,就可以访问到它所代理的 Pod:

$ kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.68.57.93 <none> 80/TCP 1m

$ curl 10.68.57.93
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
......
<h1>Welcome to nginx!</h1>
......
</html>

这个 VIP 地址是 Kubernetes 自动为 Service 分配的。访问 Service 的 VIP 地址和代理的 80 端口,它就为我们返回了默认的 nginx 页面,这种方式称为:Cluster IP 模式的 Service。

集群外访问 Service

Servcie 的访问信息在 kubernates 集群外无效,因为所谓的 Service 的访问接口,实际上是每台宿主机上由 kube-proxy 生成的 iptables 规则,以及 kube-dns 生成的 DNS 记录。

解决外部访问 Kubernetes 集群里创建的 servcie 有以下几种方法:

  • NodePort
  • LoadBalancer

NodePort 方法

下面是 NodePort 的例子:

apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- name: http
nodePort: 30080
port: 8080
targetPort: 80
protocol: TCP

在这个 Service 定义中,声明它的类型为 type=NodePort。此时在 ports 字段中声明了 Service 的 8080 端口代理 Pod 的 80 端口。

如果你不显示声明 nodePort 字段,Kubernetes 会为你随机分配可用端口来设置代理,这个端口的范围默认为:30000-32767。这里设置为 30080。

这里就可以如此访问这个 service:

<任何一台宿主机 IP 地址>:30080

LoadBalancer

这种方法适用于公有云上的 Kubernetes 服务,通过指定一个 LoadBalancer 类型的 Service 实现。

apiVersion: v1
kind: Service
metadata:
name: example-service
spec:
ports:
- port: 8765
targetPort: 9379
selector:
app: example
type: LoadBalancer

创建 Service 时,你可以选择自动创建云网络负载均衡器。这提供了一个外部可访问的 IP 地址,只要您的群集在受支持的云环境中运行,就可以将流量发送到群集节点上的正确端口。

Ingress

为代理不同后端 Service 而设置的路由规则集合就是 Kubernetes 里的 Ingress。

举一个例子,这里有一个订阅系统,它的域名是:https://wwww.example.com 。其中 http://www.example.com/book 是订书系统,https://www.example.com/food 是订餐系统。这两个系统分别由 book 和 food 两个 Deployment 来提供服务。

apiVersion: v1
kind: Ingress
metadata:
name: example-ingress
spec:
tls:
- hosts:
- www.example.com
secretName: example-secret
rules:
- host: www.example.com
http:
paths:
- path: book
backend:
serviceName: book-svc
servicePort: 80
- path: /food
backend:
serviceName: food-svc
servicePort: 80

这个 yaml 文件值得关注的 rules 字段,它叫作:IngressRules。

IngressRule 的 Key 就是 host,它必须是一个标准域名格式的字符串,不能是 IP 地址。

host 字段定义的值就是 Ingress 的入口,也就是说当用户访问 www.example.com 的时候,实际上访问到的是这个 Ingress 对象。Kubernetes 就能根据 IngressRule 进行下一步转发,这里定义两个 path,它们分别对应 book 和 food 这个两个 Deployment 的 Service。

由此不难看出,Ingress 对象其实就是 Kubernetes 项目对“反向代理”的一种抽象。

总结

今天这篇文主要介绍了 Kubernetes 集群实现容器跨主机通信的几种方式的原理,并且介绍了如何使用 Service 对外界提供服务。

更多 Kubernetes 相关文章,点击蓝字可阅读:

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 15 分钟阅读

本文是从 0 到 1 使用 Kubernetes 系列第六篇,上一篇《从 0 到 1 使用 Kubernetes 系列(五):Kubernetes Scheduling》介绍了 Kubernetes 调度器如何进行资源调度,本文将为大家介绍几种常用储存类型。

默认情况下 Pod 挂载在磁盘上的文件生命周期与 Pod 生命周期是一致的,若 Pod 出现崩溃的情况,kubelet 将会重启它,这将会造成 Pod 中的文件将丢失,因为 Pod 会以镜像最初的状态重新启动。在实际应用当中,开发者有很多时候需要将容器中的数据保留下来,比如在 Kubernetes 中部署了 MySql,不能因为 MySql 容器挂掉重启而上面的数据全部丢失;其次,在 Pod 中同时运行多个容器时,这些容器之间可能需要共享文件。也有的时候,开发者需要预置配置文件,使其在容器中生效,例如自定义了 mysql.cnf 文件在 MySql 启动时就需要加载此配置文件。这些都将是今天将要实战解决的问题。

今天这篇文将讲解下面几种常用存储类型:

  • secret
  • configMap
  • emptyDir
  • hostPath
  • nfs
  • persistentVolumeClaim

SECRET

Secret 对象允许您存储和管理敏感信息,例如密码,OAuth 令牌和 ssh 密钥。将此类信息放入一个 secret 中可以更好地控制它的用途,并降低意外暴露的风险。

▌ 使用场景

鉴权配置文件挂载

▌ 使用示例

在 CI 中 push 构建好的镜像就可以将 docker 鉴权的 config.json 文件存入 secret 对象中,再挂载到 CI 的 Pod 中,从而进行权限认证。

  • 首先创建 secret
$ kubectl create secret docker-registry docker-config  --docker-server=https://hub.docker.com --docker-username=username --docker-password=password
secret/docker-config created
  • 新建 docker-pod.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: Pod
metadata:
name: docker
spec:
containers:
- name: docker
image: docker
command:
- sleep
- "3600"
volumeMounts:
- name: config
mountPath: /root/.docker/
volumes:
- name: config
secret:
secretName: docker-config
items:
- key: .dockerconfigjson
path: config.json
mode: 0644
  • Docker Pod 挂载 secret
$ kubectl apply -f docker-pod.yaml
pod/docker created
  • 查看挂载效果
$ kubectl exec docker -- cat /root/.docker/config.json
{"auths":{"https://hub.docker.com":{"username":"username","password":"password","auth":"dXNlcm5hbWU6cGFzc3dvcmQ="}}}
  • 清理环境
$ kubectl delete pod docker
$ kubectl delete secret docker-config

ConfigMap

许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。这些配置信息需要与 docker image 解耦 ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件。

▌ 使用场景

配置信息文件挂载

▌ 使用示例

使用 ConfigMap 中的数据来配置 Redis 缓存

  • 创建 example-redis-config.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: ConfigMap
metadata:
name: example-redis-config
data:
redis-config: |
maxmemory 2b
maxmemory-policy allkeys-lru
  • 创建 ConfigMap
$ kubectl apply -f example-redis-config.yaml
configmap/example-redis-config created
  • 创建 example-redis.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: kubernetes/redis:v1
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: data
- mountPath: /redis-master
name: config
volumes:
- name: data
emptyDir: {}
- name: config
configMap:
name: example-redis-config
items:
- key: redis-config
path: redis.conf
  • Redis Pod 挂载 ConfigMap 测试
$ kubectl apply -f example-redis.yaml
pod/redis created
  • 查看挂载效果
$ kubectl exec -it redis redis-cli
$ 127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"
2) "2097152"
$ 127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"
  • 清理环境
$ kubectl delete pod redis
$ kubectl delete configmap example-redis-config

EmptyDir

当使用 emptyDir 卷的 Pod 在节点创建时,会在该节点创建一个新的空目录,只要该 Pod 运行在该节点,该目录会一直存在,Pod 内的所有容器可以将改目录挂载到不同的挂载点,但都可以读写 emptyDir 内的文件。当 Pod 不论什么原因被删除,emptyDir 的数据都会永远被删除(一个 Container Crash 并不会在该节点删除 Pod,因此在 Container crash 时,数据不会丢失)。默认情况下,emptyDir 支持任何类型的后端存储:disk、ssd、网络存储。也可以通过设置 emptyDir.medium 为 Memory,kubernetes 会默认 mount 一个 tmpfs(RAM-backed filesystem),因为是 RAM Backed,因此 tmpfs 通常很快。但是会在容器重启或者 crash 时,数据丢失。

▌ 使用场景

同一 Pod 内各容器共享存储

▌ 使用示例

在容器 a 中生成 hello 文件,通过容器 b 输出文件内容

  • 创建 test-emptydir.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: Pod
metadata:
name: test-emptydir
spec:
containers:
- image: alpine
name: container-a
command:
- /bin/sh
args:
- -c
- echo 'I am container-a' >> /cache-a/hello && sleep 3600
volumeMounts:
- mountPath: /cache-a
name: cache-volume
- image: alpine
name: container-b
command:
- sleep
- "3600"
volumeMounts:
- mountPath: /cache-b
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
  • 创建 Pod
kubectl apply -f test-emptydir.yaml
pod/test-emptydir created
  • 测试
$ kubectl exec test-emptydir -c container-b -- cat /cache-b/hello
I am container-a
  • 清理环境
$ kubectl delete pod test-emptydir

HostPath

将宿主机对应目录直接挂载到运行在该节点的容器中。使用该类型的卷,需要注意以下几个方面:

  1. 使用同一个模板创建的 Pod,由于不同的节点有不同的目录信息,可能会导致不同的结果
  2. 如果 kubernetes 增加了已知资源的调度,该调度不会考虑 hostPath 使用的资源
  3. 如果宿主机目录上已经存在的目录,只可以被 root 可以写,所以容器需要 root 权限访问该目录,或者修改目录权限

▌ 使用场景

运行的容器需要访问宿主机的信息,比如 Docker 内部信息/var/lib/docker 目录,容器内运行 cadvisor,需要访问/dev/cgroups

▌ 使用示例

使用 Docker socket binding 模式在列出宿主机镜像列表。

  • 创建 test-hostpath.yaml 文件,粘贴以下信息:
apiVersion: v1
kind: Pod
metadata:
name: test-hostpath
spec:
containers:
- image: docker
name: test-hostpath
command:
- sleep
- "3600"
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-sock
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: Socket
  • 创建 test-hostpath Pod
$ kubectl apply -f test-hostpath.yaml
pod/test-hostpath created
  • 测试是否成功
$ kubectl exec test-hostpath docker images
REPOSITORY IMAGE ID CREATED SIZE
docker 639de9917ae1 13 days ago 171MB
...

NFS 存储卷

NFS 卷允许将现有的 NFS(网络文件系统)共享挂载到您的容器中。不像 emptyDir,当删除 Pod 时,nfs 卷的内容被保留,卷仅仅是被卸载。这意味着 nfs 卷可以预填充数据,并且可以在 pod 之间共享数据。 NFS 可以被多个写入者同时挂载。

  • 重要提示:您必须先拥有自己的 NFS 服务器然后才能使用它。

▌ 使用场景

不同节点 Pod 使用统一 nfs 共享目录

▌ 使用示例

  • 创建 test-nfs.yaml 文件,粘贴以下信息:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-nfs
spec:
selector:
matchLabels:
app: store
replicas: 2
template:
metadata:
labels:
app: store
spec:
volumes:
- name: data
nfs:
server: nfs.server.com
path: /
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: alpine
image: alpine
command:
- sleep
- "3600"
volumeMounts:
- mountPath: /data
name: data
  • 创建测试 deployment
$ kubectl apply -f test-nfs.yaml
deployment/test-nfs created
  • 查看 pod 运行情况
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
test-nfs-859ccfdf55-kkgxj 1/1 Running 0 1m 10.233.68.245 uat05 <none>
test-nfs-859ccfdf55-aewf8 1/1 Running 0 1m 10.233.67.209 uat06 <none>
  • 进入 Pod 中进行测试
# 进入uat05节点的pod中
$ kubectl exec -it test-nfs-859ccfdf55-kkgxj sh
# 创建文件
$ echo "uat05" > /data/uat05
# 退出uat05节点的pod
$ edit
# 进入uat06节点的pod中
$ kubectl exec -it test-nfs-859ccfdf55-aewf8 sh
# 查看文件内容
$ cat /data/uat05
uat05
  • 清理环境
$ kubectl delete deployment test-nfs

PersistentVolumeClaim

上面所有例子中我们都是直接将存储挂载到的 pod 中,那么在 kubernetes 中如何管理这些存储资源呢?这就是 Persistent Volume 和 Persistent Volume Claims 所提供的功能。

● PersistentVolume 子系统为用户和管理员提供了一个 API,该 API 将如何提供存储的细节抽象了出来。为此,我们引入两个新的 API 资源:PersistentVolume 和 PersistentVolumeClaim。

  • PersistentVolume(PV)是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。 PV 是 Volume 之类的卷插件,但具有独立于使用 PV 的 Pod 的生命周期。此 API 对象包含 Volume 的实现,即 NFS、iSCSI 或特定于云供应商的存储系统。
  • PersistentVolumeClaim(PVC)是用户存储的请求。它与 Pod 相似。Pod 消耗节点资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存)。声明可以请求特定的大小和访问模式(例如,可以以读/写一次或 只读多次模式挂载)。虽然 PersistentVolumeClaims 允许用户使用抽象存储资源,但用户需要具有不同性质(例如性能)的 PersistentVolume 来解决不同的问题。集群管理员需要能够提供各种各样的 PersistentVolume,这些 PersistentVolume 的大小和访问模式可以各有不同,但不需要向用户公开实现这些卷的细节。对于这些需求,StorageClass 资源可以实现。

● 在实际使用场景里,PV 的创建和使用通常不是同一个人。这里有一个典型的应用场景:管理员创建一个 PV 池,开发人员创建 Pod 和 PVC,PVC 里定义了 Pod 所需存储的大小和访问模式,然后 PVC 会到 PV 池里自动匹配最合适的 PV 给 Pod 使用。

▌ 使用示例

  • 创建 PersistentVolume
apiVersion: v1
kind: PersistentVolume
metadata:
name: mypv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.0
nfs:
path: /tmp
server: 172.17.0.2
  • 创建 PersistentVolumeClaim
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 5Gi
volumeName: mypv
  • 创建 Pod 绑定 PVC
kind: Pod
apiVersion: v1
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
  • 查看 pod 运行情况验证绑定结果
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
mypod 1/1 Running 0 1m 10.233.68.249 uat05 <none>
$ kubectl exec -it mypod sh
$ ls /var/www/html
  • 清理环境
$ kubectl delete pv mypv
$ kubectl delete pvc myclaim
$ kubectl delete po mypod

总结

本次实战中使用了 secret 存储 docker 认证凭据,更好地控制它的用途,并降低意外暴露的风险。

使用 configMap 对 redis 进行缓存配置,这样即使 redis 容器挂掉重启 configMap 中的配置依然会生效。接着又使用 emptyDir 来使得同一 Pod 中多个容器的目录共享,在实际应用中开发者通常使用 initContainers 来进行预处理文件,然后通过 emptyDir 传递给 Containers。然后再使用 hostPath 来访问宿主机的资源,当网路 io 达不到文件读写要求时,可考虑固定应用只运行在一个节点上然后使用 hostPath 来解决文件读写速度的要求。

NFS 和 PersistentVolumeClaim 的例子实质上都是试容器挂载的 nfs 服务器共享目录,但这些资源一般都只掌握在了管理员手中,开发人员想要获取这部分资源那么就不是这么友好了,动态存储类(StorageClass)就能很好的解决此类问题。

更多关于 Kubernetes 系列的文章:

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 18 分钟阅读

Kubernetes作为一个容器编排调度引擎,资源调度是它的最基本也是最重要的功能。当开发者部署一个应用时它运行在哪个节点?这个节点满不满足开发的运行要求?Kubernetes又是如何进行资源调度的呢?

▌通过本文可了解到以下信息:

  • 资源请求及限制对pod调度的影响
  • 查看调度事件events
  • 了解label选择器对pod调度的影响
  • 了解节点亲和性和Pod亲和性对调度的影响
  • 不使用调度器,手动调度一个pod
  • 了解Daemonset的角色
  • 了解如何配置Kubernetes scheduler

在Kubernetes中有一个kube-scheduler组件,该组件运行在master节点上,它主要负责pod的调度。Kube-scheduler监听kube-apiserver中是否有还未调度到node上的pod(即Spec.NodeName为空的Pod),再通过特定的算法为pod指定分派node运行。如果分配失败,则将该pod放置调度队列尾部以重新调度。调度主要分为几个部分:首先是预选过程,过滤不满足Pod要求的节点。然后是优选过程,对通过要求的节点进行优先级排序,最后选择优先级最高的节点分配,其中涉及到的两个关键点是过滤和优先级评定的算法。调度器使用一组规则过滤不符合要求的节点,其中包括设置了资源的request和指定了Nodename或者其他亲和性设置等等。优先级评定将过滤得到的节点列表进行打分,调度器考虑一些整体的优化策略,比如将Deployment控制的多个副本集分配到不同节点上等。

资源请求及限制对pod调度的影响

在部署应用时,开发者会考虑到使这个应用运行起来需要多少的内存和CPU资源的使用量,这样才能判断应将他运行在哪个节点上。在部署文件resource属性中添加requests字段用于说明运行该容器所需的最少资源,当调度器开始调度该Pod时,调度程序确保对于每种资源类型,计划容器的资源请求总和必须小于节点的容量才能分配该节点运行Pod,resource属性中添加limits字段用于限制容器运行时所获得的最大资源。如果该容器超出其内存限制,则可能被终止。 如果该容器可以重新启动,kubelet会将它重新启动。如果调度器找不到合适的节点运行Pod时,就会产生调度失败事件,调度器会将Pod放置调度队列以循环调度,直到调度完成。

在下面例子中,运行一个nginx Pod,资源请求了256Mi的内存和100m的CPU,调度器将判断哪个节点还剩余这么多的资源,寻找到了之后就会将这个Pod调度上去。同时也设置了512Mi的内存和300m的CPU的使用限制,如果该Pod运行之后超出了这一限制就将被重启甚至被驱逐。

apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "300m"

参考文档:

  • Assign CPU Resources to Containers and Pods

  • Assign Memory Resources to Containers and Pods

查看调度事件events

在部署应用后,可以使用 kubectl describe 命令进行查看Pod的调度事件,下面是一个coredns被成功调度到node3运行的事件记录。

$ kubectl describe po coredns-5679d9cd77-d6jp6 -n kube-system
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 29s default-scheduler Successfully assigned kube-system/coredns-5679d9cd77-d6jp6 to node3
Normal Pulled 28s kubelet, node3 Container image "grc.io/kubernetes/coredns:1.2.2" already present on machine
Normal Created 28s kubelet, node3 Created container
Normal Started 28s kubelet, node3 Started container

下面是一个coredns被调度失败的事件记录,根据记录显示不可调度的原因是没有节点满足该Pod的内存请求。

$ kubectl describe po coredns-8447874846-5hpmz -n kube-system
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 22s (x3 over 24s) default-scheduler 0/3 nodes are available: 3 Insufficient memory.

label选择器对pod调度的影响

例如开发者需要部署一个ES集群,由于ES对磁盘有较高的要求,而集群中只有一部分节点有SSD磁盘,那么就需要将标记一下带有SSD磁盘的节点即给这些节点打上Lable,让ES的pod只能运行在带这些标记的节点上。

Lable是附着在K8S对象(如Pod、Service等)上的键值对。它可以在创建对象的时候指定,也可以在对象创建后随时指定。Kubernetes最终将对labels最终索引和反向索引用来优化查询和watch,在UI和命令行中会对它们排序。通俗的说,就是为K8S对象打上各种标签,方便选择和调度。

  1. 查看节点信息。

    $ kubectl get nodes
    NAME STATUS ROLES AGE VERSION
    node1 Ready etcd,master 128m v1.12.4
    node2 Ready etcd,lb,master 126m v1.12.4
    node3 Ready etcd,lb,worker 126m v1.12.4
  2. 选择出有SSD磁盘的节点,并给这个节点打上标记(label)。

    $ kubectl label nodes <your-node-name> disktype=ssd
    node/<your-node-name> labeled
  3. 验证节点上是否有成功打上对应label。

    $ kubectl get nodes --show-labels
    NAME STATUS ROLES AGE VERSION LABELS
    node1 Ready etcd,master 139m v1.12.4 ...disktype=ssd,kubernetes.io/hostname=node1...
    node2 Ready etcd,lb,master 137m v1.12.4 ...kubernetes.io/hostname=node2...
    node3 Ready etcd,lb,worker 137m v1.12.4 ...kubernetes.io/hostname=node3...
  4. 创建一个ES的pod, 调度到有SSD磁盘标记的节点上。在pod的配置里, 要指定nodeSelector属性值为disktype:ssd。这意味着pod启动后会调度到打上了disktype=ssd标签的node上。

        apiVersion: v1
    kind: Pod
    metadata:
    name: es
    spec:
    containers:
    - name: es
    image: es
    nodeSelector:
    disktype: ssd
  5. 验证pod启动后是否调度到指定节点上。

    $ kubectl get pods -o wide
    NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
    default es-5679d9cd77-sbmcx 1/1 Running 0 134m 10.244.2.3 node1 <none>

参考文档:

  • Assign Pods to Nodes

节点亲和性和Pod亲和性对调度的影响

上小节讲述的nodeSelector提供了一种非常简单的方法,可以将pod限制为具有特定标签的节点。而更为强大的表达约束类型则可以由Affinity和Anti-affinity来配置。即亲和性与反亲和性的设置。亲和性和反亲和性包括两种类型:节点(反)亲和性与Pod(反)亲和性。

Node affinity与NodeSelector很相似,它允许你根据节点上的标签限制你的pod可以在哪些节点上进行调度。目前有两种类型的节点关联,称为required During Scheduling Ignored During Execution和 preferred During Scheduling Ignored During Execution。可以将它们分别视为“硬规则”和“软规则”,前者指定了要将 pod调度到节点上必须满足的规则,而后者指定调度程序将尝试强制但不保证的首选项。名称中的“Ignored During Execution”部分意味着,类似于nodeSelector工作方式,如果节点上的标签在运行时更改,不再满足pod上的关联性规则,pod仍将继续在该节点上运行。Pod affinity强调的是同一个节点中Pod之间的亲和力。可以根据已在节点上运行的pod上的标签来约束pod可以调度哪些节点上。比如希望运行该Pod到某个已经运行了Pod标签为app=webserver的节点上,就可以使用Pod affinity来表达这一需求。

目前有两种类型Pod亲和力和反亲和力,称为required During Scheduling Ignored During Execution以及 preferred During Scheduling Ignored During Execution,其中表示“硬规则”与“软规则”的要求。类似于Node affinity,IgnoredDuringExecution部分表示如果在Pod运行期间改变了Pod标签导致亲和性不满足以上规则,则pod仍将继续在该节点上运行。无论是Selector还是Affinity,都是基于Pod或者Node的标签来表达约束类型。从而让调度器按照约束规则来调度Pod运行在合理的节点上。

节点亲和性如下所示,其中亲和性定义为:该pod只能放置在一个含有键为kubernetes.io/hostname并且值为node1或者node2标签的节点上。此外,在满足该标准的节点中,具有其键为app且值为webserver的标签的节点应该是优选的。

apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node1
- node2
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: app
operator: In
values:
- webserver
containers:
- name: with-node-affinity
image: k8s.gcr.io/pause:2.0

Pod反亲和性如下所示,其中反亲和性定义为:在此拓扑域(相当于以topologyKey的值进行的节点分组)中,命名空间为default下有标签键为app,标签值为redis的Pod时不在此Node上运行。

apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
namespaces:
- default
topologyKey: kubernetes.io/hostname
containers:
- name: with-node-affinity
image: k8s.gcr.io/pause:2.0

不使用调度器, 手动调度一个pod

Scheduling过程的本质其实就是给Pod赋予nodeName属性合适的值。那么在开发者进行Pod部署时就直接指定这个值是否可行呢?答案是肯定的。如下配置,将nginx直接分配到node1上运行。

apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
nodeName: node1

还有一种指定节点的部署方式——static pod,就像它名称一样,他是一个“静态”的Pod,它不通过apiserver,直接由kubelet进行托管。在kubelet的启动参数中--pod-manifest-path=DIR,这里的DIR就是放置static pod的编排文件的目录。把static pod的编排文件放到此目录下,kubelet就可以监听到变化,并根据编排文件创建pod。还有一个启动参数--manifest-url=URL,kubelet会从这个URL下载编排文件,并创建pod。static pod有一个特性是我们使用docker或kubectl删除static pod后, static pod还能被kubelet进程拉起。通过这种方式保证了应用的可用性。有点相当于systemd的功能, 但比systemd好的一点是, static pod的镜像信息会在apiserver中注册。 这样的话, 我们就可以统一对部署信息进行可视化管理。 此外static pod是容器, 无需拷贝二进制文件到主机上, 应用封装在镜像里也保证了环境的一致性, 无论是应用的编排文件还是应用的镜像都方便进行版本管理和分发。

在使用kubeadm部署kubernetes集群时,static pod得到了大量的应用,比如 etcd、kube-scheduler、kube-controller-manager、kube-apiserver 等都是使用 static pod的方式运行的。

使用static pod部署出来的pod名称与其他pod有很大的不同点,名称中没有“乱码”,只是简单的将pod的name属性值与它运行在的node的name属性值相连接而成。如下所示,coredns是通过Deployment部署出来的名称中就有部分“乱码”,而etcd,kube-apiserver这种Pod就是static pod。

$ kubectl get po --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-5679d9cd77-d6jp6 1/1 Running 0 6m59s
kube-system etcd-node1 1/1 Running 0 6m58s
kube-system etcd-node2 1/1 Running 0 6m58s
kube-system etcd-node3 1/1 Running 0 6m54s
kube-system kube-proxy-nxj5d 1/1 Running 0 6m52s
kube-system kube-proxy-tz264 1/1 Running 0 6m56s
kube-system kube-proxy-zxgxc 1/1 Running 0 6m57s

了解Daemonset角色

DaemonSet是一种控制器,它确保在一些或全部Node上都运行一个指定的Pod。这些Pod就相当于守护进程一样不期望被终止。当有Node加入集群时,也会为他们新增一个Pod。当有Node从集群移除时,对应的Pod也会被回收。当删除DaemonSet时将会删除它创建的所有Pod。一般情况下,Pod运行在哪个节点上是由Kubernates调度器选择的。但是在Kubernates 1.11版本之前由DaemonSet Controller创建的Pod在创建时已经确定了在哪个节点上运行(pod在创建的时候.spec.nodeName字段就指定了, 因此会被scheduler忽略),所以即使调度器没有启动DaemonSet Controller创建的Pod仍然也可以被分配node。直到Kubernates 1.11版本,DaemonSet的pod由scheduler调度才作为alpha特性引入。在上小节中kube-proxy就是以DaemonSet的方式进行运行的。

配置Kubernetes scheduler

如果需要配置一些高级的调度策略以满足我们的需要,可以修改默认调度程序的配置文件。kube-scheduler在启动的时候可以通过--policy-config-file参数来指定调度策略文件,开发者可以根据自己的需要来组装Predicates和Priority函数。选择不同的过滤函数和优先级函数。调整控制优先级函数的权重和过滤函数的顺序都会影响调度结果。

官方的Policy文件如下:

kind: Policy
apiVersion: v1
predicates:
- {name: PodFitsHostPorts}
- {name: PodFitsResources}
- {name: NoDiskConflict}
- {name: NoVolumeZoneConflict}
- {name: MatchNodeSelector}
- {name: HostName}
priorities:
- {name: LeastRequestedPriority, weight: 1}
- {name: BalancedResourceAllocation, weight: 1}
- {name: ServiceSpreadingPriority, weight: 1}
- {name: EqualPriority, weight: 1}

其中predicates区域是调度的预选阶段所需要的过滤算法。priorities区域是优选阶段的评分算法。

总结

再来回顾一下调度的主要构成部分:首先是预选过程,过滤掉不满足Pod要求的节点,然后是优选过程,对通过要求的节点进行优先级排序,最后选择优先级最高的节点进行分配。当调度器不工作时或有临时需求可以手动指定nodeName属性的值,让其不通过调度器进行调度直接运行在指定的节点上。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 11 分钟阅读

创建Namespace

在一个Kubernetes集群中可以创建多个Namespace进行“环境隔离”,当项目和人员众多的时候,可以考虑根据项目的实际情况(例如生产、测试、开发)划分不同的Namespace。

创建一个名称为“nginx”的Namespace:

[root@localhost~]# kubectl create ns nginx

namespace "nginx" created

查看集群中已创建出来的Namespace:

[root@localhost~]# kubectl get ns

NAME STATUS AGE
default Active 35d
kube-public Active 35d
kube-system Active 35d
nginx Active 19s

创建Deployment

Deployment为Pod 和Replica Set(下一代Replication Controller)提供声明式更新。只需要在 Deployment 中描述想要的目标状态是什么,Deployment Controller 就会帮开发者将 Pod 和 ReplicaSet 的实际状态改变成目标状态。开发者可以定义一个全新的 Deployment 来创建 ReplicaSet 或者删除已有的 Deployment 并创建一个新的来替换。使用Deployment能够更加方便地管理Pod,包括扩容、缩容、暂停、滚动更新、回滚等。在Choerodon中用实例的方式来展现Deployment,同时支持在线升级,停止,删除等多元化功能。

典型的应用场景包括:

  • 定义Deployment来创建Pod和ReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续Deployment

编写名为dp.yaml文件,内容如下:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
namespace: nginx
labels:
app: nginx
spec:
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.13.5-alpine
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80

保存后使用kubectl命令部署:

[root@localhost~]# kubectl apply -f dp.yaml

deployment.apps"nginx-deployment"created

可执行下面命令查看部署出来的Deployment:

[root@localhost~]# kubectl get deployment -n nginx

NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
inx-deployment 1 1 1 1 51s

可执行下面命令查看Deployment创建出来的Pod:

[root@localhost~]# kubectl get pod -n nginx -o wide

NAME READY STATUS RESTARTS AGE IP NODE
nginx-deployment-866d7c64c7-8rnd5 1/1 Running 0 3m 10.233.68.248 clusternode11

Pod状态为Running,说明已经正常工作了就可以在集群中通过Pod IP进行访问了:

[root@localhost~]# curl 10.233.68.248

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body{
width:35em;
margin:0 auto;
font-family:Tahoma,Verdana,Arial,sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!<h1>
<p>If you see this page,the nginx web server is successfully installed and working. Further configuration is required.</P>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for uesing nginx.</em></p>
</body>
</html>

更多关于Deployment的介绍请参考这里: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/

创建Service

Kubernetes Pod 是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束。 通过 Deployment 能够动态地创建和销毁 Pod。 每个 Pod 都会获取它自己的 IP 地址,然而这些IP地址并不是稳定固定的,在销毁Pod时这些IP也会进行回收。 这会导致一个问题:在 Kubernetes 集群中,如果一组 Pod(称为 Backend)为其它Pod (称为 Frontend)提供服务,那么那些 Frontend 该如何发现,并连接到这组 Pod 中的哪些 Backend 呢?

这里要隆重的请出Service来解决这个问题。

编写名为svc.yaml文件,内容如下:

apiVersion: v1
kind: Service
metadata:
namespace: nginx
name: nginx-service
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80

使用kubectl命令部署:

[root@localhost~]# kubectl apply -f svc.yaml

service "nginx-service"created

可执行下面命令查看部署出来的Service:

[root@localhost~]# kubectl get svc -n nginx

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service ClusterIP 10.233.47.128 <none> 80/TCP 56s

可以看到Pod对应的Service已经建好了,而这个“对应”就是依靠Service里面的Selector与Pod的Labels建立映射的(即Selector内容与Pod的Labels内容需一致)。现在集群内部可以通过该Service访问到Nginx,Choerodon中提供了Service的可视化创建操作,可以更加方便便捷的创建网络:

[root@localhost~]# curl 10.233.47.128

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body{
width:35em;
margin:0 auto;
font-family:Tahoma,Verdana,Arial,sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!<h1>
<p>If you see this page,the nginx web server is successfully installed and working. Further configuration is required.</P>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for uesing nginx.</em></p>
</body>
</html>

更多关于Service的介绍请参考这里: https://kubernetes.io/docs/concepts/services-networking/service/

创建Ingress

此时,只有集群内部和所在主机能访问Nginx,要让节点外的其他主机能够访问,还得需要创建Ingress。Ingress 可以给 Service 提供集群外部访问的 URL、负载均衡、SSL 终止、HTTP 路由等功能。Ingress对应了Choerodon中的域名,Choerodon中除了对域名的管理外还添加了域名证书的管理,支持在线申请和导入。

编写名为ing.yaml文件,内容如下:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ingress
namespace: nginx
spec:
rules:
- host: nginx.example.local #此域名需解析到k8s集群负载主机IP
http:
paths:
- backend:
serviceName: nginx-service
servicePort: 80
path: /

使用kubectl命令部署:

[root@localhost~]# kubectl apply -f ing.yaml

ingress.extensions "nginx-ingress" created

可执行下面命令查看部署出来的Ingress:

[root@localhost~]# kubectl get ingress -n nginx

NAME HOSTS ADDRESS PORTS AGE
nginx-ingress nginx.example.local 80 1m

此时,就可以在浏览器中使用定义的URL访问Nginx了:

更多关于Ingress的介绍请参考这里: https://kubernetes.io/docs/concepts/services-networking/ingress/

看完传统Kubernetes的应用搭建后,来看看Choerodon中如何进行应用搭建吧。

基于Choerodon的应用搭建

一键部署

Choerodon中的应用部署简单明了,其构建了一套环境,实例,服务,域名的对象来进行Kubernetes的基础对象映射,为Kubernetes基础对象的创建和修改提供了可视化的操作界面。

在Choerodon猪齿鱼平台部署一个应用只需要在“应用管理”页面点击“创建应用”,并在“开发流水线”创建分支,提交代码后发布应用,在“部署流水线”页面选择要部署的应用,版本,目标环境,部署模式,设置好网络、域名即完成应用的部署。

那Choerodon在应用的搭建的背后又做了哪些事情呢?这就不得不提到GitOps了。

GitOps

Choerodon采用Kubernetes作为基础平台,通过Helm Chart打包应用,将Helm Release抽象成Kubernets自定义对象,这样就能通过Kubernetes资源对象文件描述整个环境部署应用系统的状态。在Choerodon中将应用搭建最终生成的yaml文件以配置库的方式存储在GitLab中,通过对比配置yaml文件的改动来判断应用的搭建状态,实现业务代码与配置代码的分离。

同时用户在平台中创建环境时会同步创建一个与环境对应的Git仓库用来存放部署配置文件,之后所有在环境中部署的相关操作,都会转化为Git库中部署配置文件的操作,同时触发Choerodon部署服务进行状态记录,应用状态记录后触发Choerodon在Kubernetes中的Agent进行应用部署。最终可以根据配置库,Choerodon部署服务状态记录,Choerodon Agent来诠释整个应用部署所经历的流程。

总结

以上步骤可以看出,从构建程序到外部访问,K8S提供了Namespace, Deployment, Service, Ingress等基础对象。以此为基础,Choerodon猪齿鱼平台构建了一套环境,实例,服务,域名的对象来进行映射,包括滚动升级、容错提高、服务测试等,并以友好的UI界面管理进行管理,使用户可以简单的通过页面进行Kubernetes的对象操作,从而创建自己的应用。

Choerodon猪齿鱼平台不仅实现一键部署,平台还提供了完备的测试管理,用于对新发布应用的测试。知识管理模块提供了企业内部的信息分享平台,报表模块则能够提供更为详细的开发、迭代信息。Choerodon猪齿鱼平台提升了K8S持续集成、持续部署的能力,使各个基础平台的耦合性更高,更加适用于企业实践DevOps。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 7 分钟阅读

首先,通过下面命令克隆并进入项目:

git clone https://github.com/choerodon/kubeadm-ansible.git && cd kubeadm-ansible

通过 Virtualbox + Vagrant启动三台CentOS系统虚拟机,在项目根目录中有编写好的Vagrantfile文件,直接使用就可以了。

Vagrantfile文件如下:

Vagrant.configure(2) do |config|

(1..3).each do |i|
config.vm.define "node#{i}" do |s|
s.vm.box = "bento/centos-7.3"
s.vm.box_url = "http://file.choerodon.com.cn/vagrant/box/bento_centos-7.3.box"
s.vm.hostname = "node#{i}"
n = 10 + i
s.vm.network "private_network", ip: "192.168.56.#{n}"
s.vm.provider "virtualbox" do |v|
v.cpus = 2
v.memory = 4096
end
end
end
end

其中box_url指定box镜像下载地址,hostname指虚拟机主机名,private_network指内网ip地址,cpus和memory指虚拟机的硬件资源要求。

vagrant-cachier插件用于不同虚拟机中共享公共包缓存,减少虚拟机的包下载时间。 根据上述Vagrantfile文件启动的虚拟机相关信息如下:

HostnameCPUMemoryIPSystem
node124G192.168.56.11CentOS 7.3
node224G192.168.56.12CentOS 7.3
node324G192.168.56.13CentOS 7.3

在项目根目录中执行下面命令启动虚拟机:

启动前请确认主机已开启CPU虚拟化支持。

vagrant up

登录虚拟机node1

vagrant ssh node1

部署Kubernetes

在node1中部署Ansible所需的环境

sudo yum install -y epel-release && \
sudo yum install -y \
ansible \
git \
httpd-tools \
pyOpenSSL \
python-cryptography \
python-lxml \
python-netaddr \
python-passlib \
python-pip

在node1中再次克隆项目代码(防止换行符改变导致后期部署出错)

git clone https://github.com/choerodon/kubeadm-ansible.git && cd kubeadm-ansible

在node1中编辑项目下的kubeadm-ansible/inventory/hosts文件,修改各机器的访问地址、用户名、密码,并维护好各节点与角色的关系,前面的名称为机器的hostname。该用户必须是具有root权限的用户,但并非要求一定是root用户,其他具有root权限的用户也可以。比如,想要部署单master节点集群,只需要这样配置(参考):

*在all分区中每一行为一个节点的信息,node1为该节点的hostname,ansible_host指节点内网IP,ip指Kubernetes目标绑定网卡IP,ansible_user为该节点具有管理员权限的一个用户,ansible_ssh_pass为该用户的密码,ansible_become代表执行命令时使用管理员权限。

其中Kube-Master节点为Kubernetes主节点、Kube-Node节点为Kubernetes普通节点、Etcd节点为将部署Etcd的节点,按本教程安装Kube-Master节点与Etcd节点必须一致,Etcd官方建议Etcd集群节点个数为奇数个(比如1、3、5)以防止脑裂。

[all]
node1 ansible_host=192.168.56.11 ip=192.168.56.11 ansible_user=root ansible_ssh_pass=vagrant ansible_become=true
node2 ansible_host=192.168.56.12 ip=192.168.56.12 ansible_user=root ansible_ssh_pass=vagrant ansible_become=true
node3 ansible_host=192.168.56.13 ip=192.168.56.13 ansible_user=root ansible_ssh_pass=vagrant ansible_become=true
[kube-master]
node1
[etcd]
node1
[kube-node]
node1
node2
node3

*在项目下的kubeadm-ansible/inventory/hosts文件默认配置的是3个master节点集群。

在node1中执行下面命令部署集群:

ansible-playbook -i inventory/hosts -e @inventory/vars cluster.yml

在cluster.yml中我们将集群的安装划分为6个阶段,分别为:

  • 安装预备
    • 安装前检查:检查系统,确认Yum库,下载cfssl。
    • Docker相关检查:检查Docker Engine,Configuration,Proxy。
  • Etcd安装
    • 生成Etcd证书
    • 安装Docker
    • 配置系统环境
  • kube-master:kube-node必须组件安装
    • kubelet
  • kube-master安装
    • 检查kubeadm
    • 生成证书
    • 修改配置
  • kube-node安装
    • 生成配置文件
    • kubeadm join
  • 其他组件安装
    • 配置flannel网络
    • 安装ingress-nginx
    • 安装dashboard
    • 安装heapster
    • 安装kube-lego

至此,集群部署到此结束,可以执行下面命令查看pod状态,都为Running状态则部署成功:

kubectl get po -n kube-system

如果部署失败,想要重置集群(所有数据),执行:

ansible-playbook -i inventory/hosts reset.yml

添加节点

如果想再添加一个节点,进入已有集群当中可以按下面步骤进行: 编辑kubeadm-ansible/inventory/hosts,将新节点信息添加进去。比如新节点hostname为node4,ip为192.168.56.14,其余信息与其他节点相同,那么进行如下添加信息:

[all]
node1 ansible_host=192.168.56.11 ip=192.168.56.11 ansible_user=root ansible_ssh_pass=vagrant ansible_become=true
node2 ansible_host=192.168.56.12 ip=192.168.56.12 ansible_user=root ansible_ssh_pass=vagrant ansible_become=true
node3 ansible_host=192.168.56.13 ip=192.168.56.13 ansible_user=root ansible_ssh_pass=vagrant ansible_become=true
node4 ansible_host=192.168.56.14 ip=192.168.56.14 ansible_user=root ansible_ssh_pass=vagrant ansible_become=true
[kube-master]
node1
[etcd]
node1
[kube-node]
node1
node2
node3
node4

节点信息添加完成后,就可以进行节点添加操作了:

ansible-playbook -i inventory/hosts -e @inventory/vars scale.yml

添加完成后查看节点信息:

kubectl get node

集群部署的介绍就到此结束了,下一篇我们将为大家介绍如何搭建第一个应用程序。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 12 分钟阅读

Kubeadm是什么

Kubeadm是一个提供Kubeadm init 和 Kubeadm join命令,用于创建Kubernetes集群的最佳实践“快速路径”工具。

Kubeadm可以在多种设备上运行,可以是Linux笔记本电脑,虚拟机,物理/云服务器或Raspberry Pi。这使得Kubeadm非常适合与不同种类的配置系统(例如Terraform,Ansible等)集成。

开发者可以在支持安装deb或rpm软件包的操作系统上非常轻松地安装Kubeadm。SIG集群生命周期SIG Cluster Lifecycle Kubeadm的SIG相关维护者提供了预编译的这些软件包,也可以在其他操作系统上使用。

Kubeadm的目标

Kubeadm的目标是在不安装其他功能插件的基础上,建立一个通过Kubernetes一致性测试Kubernetes Conformance tests的最小可行集群。它在设计上并不会安装网络解决方案,而是需要用户自行安装第三方符合CNI的网络解决方案(如:flannel,calico,weave network等)。

Kubeadm的子任务

  • kubeadm init 初始化Kubernetes主节点
  • kubeadm join 初始化Kubernetes工作节点并将其加入群集
  • kubeadm upgrade 将Kubernetes集群升级到更新版本
  • kubeadm token 用于管理Kubeadm join所使用的令牌
  • kubeadm reset 恢复由Kubeadm init或 Kubeadm join对此主机所做的任何更改
  • kubeadm version 版本打印Kubeadm版本

Kubeadm的安装

安装Kubeadm需要手动安装Kubelet和Kubectl,因为Kubeadm是不会安装和管理这两个组件的。

  • Kubelet:在群集中的所有计算机上运行的组件,并执行诸如启动pod和容器之类的操作。
  • Kubectl:操作群集的命令行工具。

▌Ubuntu

apt-get update && apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

▌CentOS

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

CFSSL

CFSSL是CloudFlare开源的一款PKI/TLS工具。它既是命令行工具,也是用于签名、验证和捆绑TLS证书的HTTP API 服务器。

CFSSL包括:

  • 一组用于生成自定义 TLS PKI 的工具
  • cfssl,即CFSSL的命令行工具
  • multirootca 是可以使用多个签名密钥的证书颁发服务器
  • kbundle 用于构建证书池
  • cfssljson 从cfssl和multirootca中获取JSON输出,并将证书,密钥,CSR和bundle写入磁盘

PKI借助数字证书和公钥加密技术提供可信任的网络身份,通常,证书就是一个包含如下身份信息的文件:

  • 证书所有组织的信息
  • 公钥
  • 证书颁发组织的信息
  • 证书颁发组织授予的权限,如:证书有效期、适用的主机名、用途等
  • 使用证书颁发组织私钥创建的数字签名

CFSSL的安装

由于运行环境不同,故使用Go命令进行编译安装,在安装之前请确保已安装1.8版本以上的Go命令行以及配置了环境变量GOPATH。

go get -u github.com/cloudflare/cfssl/cmd/...

Ansible

Ansible是什么

Ansible是个什么东西呢?官方的title是“Ansible is Simple IT Automation”——简单的自动化IT工具。这个工具的目标有这么几项:自动化部署APP;自动化管理配置项;自动化的持续交付;自动化的云服务管理。

所有的这几个目标本质上来说都是在一台或者几台服务器上,执行一系列的命令而已。就像Fabric,以及基于Fabric开发的自动化应用部署的工具: Essay 。都是做了这么个事——批量地在远程服务器上执行命令 。

那么Fabric和Ansible有什么差别呢?简单来说Fabric像是一个工具箱,提供了很多好用的工具,用来在Remote执行命令,而Ansible则是提供了一套简单的流程,你只要按照它的流程来做,就能轻松完成任务。这就像是库和框架的关系一样。

当然,它们之间也有共同点——都是基于 Paramiko 开发的。这个Paramiko是什么呢?它是一个纯Python实现的ssh协议库。因此Fabric和Ansible还有一个共同点就是不需要在远程主机上安装client/agents,因为它们是基于ssh来和远程主机通讯的。

Ansible基于Python开发,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。Ansible是基于模块工作的,本身没有批量部署的能力。真正具有批量部署的是Ansible所运行的模块,Ansible只是提供一种框架,主要包括:

  1. 连接插件connection plugins:负责和被监控端实现通信;
  2. host inventory:指定操作的主机,是一个配置文件里面定义监控的主机;
  3. 各种模块:核心模块、command模块、自定义模块;
  4. 借助于插件完成记录日志邮件等功能;
  5. Playbook:剧本执行多个任务时,非必需可以让节点一次性运行多个任务。

Ansible的基本架构

  1. 核心引擎:即图中所看到的Ansible。

  2. 核心模块(core module):

    模块库(module library)分为两部分,一个是核心模块,另外一个就是自定义模块(custom modules)。核心模块中都是Ansible自带的模块,Ansible模块资源分发到远程节点使其执行特定任务或匹配一个特定的状态。这些核心模块都遵循 Batteries Included 哲学。其实这里还是很有意思的,Batterires Included:Python has a large standard library, commonly cited as one of Python’s greatest strengths,providing tools suited to many tasks. 这就意味着Python有巨大的库支持你完成你想完成的任务工作。

  3. 自定义模块(custom modules):

    如果Ansible无法满足你所需求的模块,那么你能使用 Ansible 添加自定义化的模块。

  1. 插件(plugin):

    这里的理解就是完成较小型的任务,辅助模块来完成某个功能。

  1. 剧本(playbook):

    定义需要给远程主机执行的一系列任务。比如安装一个nginx服务,可以把这拆分为几个任务放到一个playbook中。例如:第一步,需要下载nginx的安装包。第二步,将事先写好的nginx.conf的配置文件下发的目标服务器上。第三步,把服务启动起来。第四步,检查端口是否正常开启。这些步骤可以通过playbook来进行整合,然后通过inventory来下发到想要执行剧本的主机上。并且playbook也支持交互式执行playbook里面的步骤,如果有哪一个步骤执行返回了一个错误报告,可以仅仅只单独执行这个步骤。你可以把playbook理解成为一个组策略,控制管理这个OU下所有的主机行为。

  1. 连接插件(connectior plugins):

    Ansible默认是基于SSH连接到目标机器上执行操作的。但是同样的Ansible支持不同的连接方法,要是这样的话就需要连接插件来完成连接了。

  1. 主机清单(host inventory):

    为Ansible定义了管理主机的策略。一般小型环境下只需要在host文件中写入主机的IP地址即可,但是到了中大型环境有可能需要使用静态inventory或者动态主机清单来生成所需要执行的目标主机。

Ansible的功能特性

  1. 应用代码自动化部署

  2. 系统管理配置自动化

  3. 支持持续交付自动化

  4. 支持云计算,大数据平台环境

  5. 轻量级,无序在客户端安装agent,更新时只需在控制机上进行更行即可

  6. 批量任务执行可以写成脚本,不用分发到远程就可以执行

  7. 支持非root用户管理操作,支持sudo

  8. 使用python编写,维护更简单

Ansible的安装

▌Ubuntu

sudo apt-get install software-properties-common
sudo apt-add-repository ppa:ansible/ansible
sudo apt-get update
sudo apt-get install ansible

▌CentOS

sudo yum install epel-release
sudo yum install ansible

总结

通过前面的介绍,可以大致了解Kubeadm、CFSSL、Ansible这三个工具的作用,由于安装Kubernetes集群时执行Kubeadm命令较为固定和繁琐,并且有些命令需要所有节点都执行,故将这些命令编写为Ansible playbooks,使用Ansible进行执行,从而提高部署效率和降低出错的概率。

该系列第一篇为:《从0到1使用Kubernetes系列——Kubernetes入门》,下一篇将介绍如何使用Ansible快速安装Kubernetes集群,欢迎各位持续关注。

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献:

· 24 分钟阅读

基本概念

Docker 是什么

Docker 起初是 dotCloud 公司创始人 Solomon Hykes 在法国的时候发起的一项公司内部项目,Docker 是基于 dotCloud 公司多年云服务技术的一次革新,在 2013 年 3 月以 Apache 2.0 授权协议进行开源,其项目主要代码在 GitHub 上进行维护,自从 Docker 开源之后,就一直受到了广泛讨论和关注。

Docker 进行开发实现使用的是 Google 公司推出的 Go 语言,对进程进行封装隔离是基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,这属于操作系统层面的虚拟化技术。因为隔离的进程独立于宿主与其它隔离的进程,所以也称其为容器(后文会对“容器”的概念进行详细介绍)。Docker 在容器的基础上,进行了进一步的封装,从网络互联、文件系统到进程隔离等,大大地简化了容器的创建和维护,让 Docker 技术比虚拟机技术更加轻便、快捷。

以下两张图片对比了 Docker 与传统虚拟化方式的不同之处。Docker 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,没有进行硬件虚拟;而传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程。因此容器要比传统虚拟机更为轻便。

图为 传统虚拟化

图为 Docker

为什么要使用 Docker?

Docker 是一种新兴的虚拟化方式,跟传统的虚拟化方式相比具有众多优势。

  • 系统资源利用更高效

    因为容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,所以 Docker 对系统资源的利用率更高。

  • 启动时间更快速

    Docker 容器应用由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。极大地节省了开发、测试,部署的时间。

  • 运行环境一致性

    开发过程中比较常见的问题就是环境一致性问题。因为开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码在我机器上没问题啊!”这类问题。

  • 持续交付与部署

    对开发和运维人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 Dockerfile 来进行镜像构建,并结合持续集成(Continuous Integration)系统进行集成测试,而运维人员则可以直接在各种环境中快速部署该镜像,甚至结合持续部署(Continuous Delivery/Deployment) 系统进行自动部署。

    而且使用 Dockerfile 使镜像构建透明化,不仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好地在生产环境中部署该镜像。

  • 迁移更轻松

    由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻松地将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境变化导致应用无法正常运行的情况。

  • 维护和扩展更轻松

    Docker 使用的分层存储以及镜像技术,使得应用重复部分的复用更为容易,也使得应用的维护更新和基于基础镜像进一步扩展镜像变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大降低了应用服务的镜像制作成本。

Docker 的镜像和容器

Docker 的口号是“Build, Ship and Run Any App, Anywhere.”,大意是编译好一个应用后,可以在任何地方运行,不会像传统的程序一样,一旦换了运行环境,往往就会出现缺这个库,少那个包的问题。那么 Docker 是怎么做到这点的呢?

简单说就是它在编译应用的时候把这个应用依赖的所有东西都构建到镜像里面(有点像程序的静态编译——只是像而已)。我们把这个编译构建好的东西叫 Docker 镜像(Image),然后当 Docker deamon(Docker 的守护进程/服务进程)运行这个镜像的时候,我们称其为 Docker 容器(Container)。可以简单理解 Docker 镜像和 Docker 容器的关系就像是程序和进程的关系一样(当然实质是不一样的)。

Images 和 Layers

每个 Docker 镜像(Image)都引用了一些只读的(read-only)层(layer),不同的文件系统 layer 也不同。这些 layer 堆叠在一起构成了容器(Container)的根文件系统(root filesystem)。下图是 Ubuntu 15.04 的镜像,共由 4 个镜像层(image layer)组成:

Container 和 Layers

容器和镜像的主要区别就是顶部的那个可写层(即之前说的那个“container layer”)。容器运行时做的所有操作都会写到这个可写层里面,当容器删除的时候,这个可写层也会被删掉,但底层的镜像依旧保持不变。所以,不同的容器都有自己的可写层,但可以共享同一个底层镜像。下图展示了多个容器共享同一个 Ubuntu 15.04 镜像。

Docker 的 storage driver 负责管理只读的镜像层和可写的容器层,当然不同的 driver 实现的方式也不同,但其后都有两项关键技术:可堆叠的镜像层(stackable image layer)和写时拷贝技术(copy-on-write, CoW)。

Docker 数据持久化

刚开始的时候,Docker 一般只适用于无状态的计算场景使用。但随着发展,Docker 通过 data volume 技术也可以做到数据持久化了。Data volume 就是我们将主机的某个目录挂载到容器里面,这个 data volume 不受 storage driver 的控制,所有对这个 data volume 的操作会绕过 storage driver 直接其操作,其性能也只受本地主机的限制。而且我们可以挂载任意多个 data volume 到容器中,不同容器也可以共享同一个 data volume。

下图展示了一个 Docker 主机上面运行着两个容器.每一个容器在主机上面都有着自己的地址空间(/var/lib/docker/...),除此以外,它们还共享着主机上面的同一个/data 目录。

Kubernetes 简介

Kubernetes 是谷歌开源的容器集群管理系统,是 Google 多年大规模容器管理技术 Borg 的开源版本,主要功能包括:

  • 于容器的应用部署、维护和滚动升级
  • 负载均衡和服务发现
  • 跨机器和跨地区的集群调度
  • 自动伸缩
  • 无状态服务和有状态服务
  • 广泛的 Volume 支持
  • 插件机制保证扩展性

Kubernetes 发展非常迅速,已经成为容器编排领域的领导者。

Kubernetes 是什么

Kubernetes 提供了很多的功能,它可以简化应用程序的工作流,加快开发速度。通常,一个成功的应用编排系统需要有较强的自动化能力,这也是为什么 Kubernetes 被设计作为构建组件和工具的生态系统平台,以便更轻松地部署、扩展和管理应用程序。

用户可以使用 Label 以自己的方式组织管理资源,还可以使用 Annotation 来自定义资源的描述信息,比如为管理工具提供状态检查等。

此外,Kubernetes 控制器也是构建在跟开发人员和用户使用的相同的 API 之上。用户可以编写自己的控制器和调度器,也可以通过各种插件机制扩展系统的功能。这种设计使得用户可以方便地在 Kubernetes 之上构建各种应用系统。

Kubernetes 不是什么

Kubernetes 不是一个传统意义上,包罗万象的 PaaS (平台即服务) 系统。它给用户预留了选择的自由。

  • 不限制支持的应用程序类型,它不插手应用程序框架, 也不限制支持的语言 (如 Java, Python, Ruby 等),Kubernetes 旨在支持极其多样化的工作负载,包括无状态、有状态和数据处理工作负载。只要应用可以在容器中运行,那么它就可以很好地在 Kubernetes 上运行。
  • 不提供内置的中间件 (如消息中间件)、数据处理框架 (如 Spark)、数据库 (如 mysql) 或集群存储系统 (如 Ceph) 等。这些应用直接运行在 Kubernetes 之上。
  • 不提供点击即部署的服务市场。
  • 不直接部署代码,也不会构建用户的应用程序,但用户可以在 Kubernetes 之上构建需要的持续集成 (CI) 工作流。
  • 允许用户选择自己的日志、监控和告警系统。
  • 不提供应用程序配置语言或系统 (如 jsonnet)。
  • 不提供机器配置、维护、管理或自愈系统。

另外,已经有很多 PaaS 系统运行在 Kubernetes 之上,如 Openshift, Deis 和 Eldarion 等。 你也可以构建自己的 PaaS 系统,或者只使用 Kubernetes 管理你的容器应用。

当然了,Kubernetes 不仅仅是一个 “编排系统”,它消除了编排的需要。Kubernetes 通过声明式的 API 和一系列独立、可组合的控制器保证了应用总是在期望的状态,而用户并不需要关心中间状态是如何转换的。这使得整个系统更容易使用,而且更强大、更可靠、更具弹性和可扩展性。

基本组件

核心组件

Kubernetes 主要由以下几个核心组件组成:

  • etcd:保存了整个集群的状态;
  • apiserver:提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制;
  • controller manager:负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
  • scheduler:负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上;
  • kubelet:负责维护容器的生命周期,同时也负责 Volume(CVI)和网络(CNI)的管理;
  • Container runtime:负责镜像管理以及 Pod 和容器的真正运行(CRI);
  • kube-proxy:负责为 Service 提供 cluster 内部的服务发现和负载均衡

除了核心组件,还有一些推荐的 Add-ons:

  • kube-dns:负责为整个集群提供 DNS 服务
  • Ingress Controller:为服务提供外网入口
  • Heapster:提供资源监控
  • Dashboard:提供 GUI
  • Federation:提供跨可用区的集群
  • Fluentd-elasticsearch:提供集群日志采集、存储与查询

组件详细介绍

Etcd

Etcd 是 CoreOS 基于 Raft 开发的分布式 key-value 存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)。

Etcd 主要功能:

  • 基本的 key-value 存储
  • 监听机制
  • key 的过期及续约机制,用于监控和服务发现
  • 原子 CAS 和 CAD,用于分布式锁和 leader 选举

kube-apiserver

kube-apiserver 是 Kubernetes 最重要的核心组件之一,主要提供以下的功能:

  • 提供集群管理的 REST API 接口,包括认证授权、数据校验以及集群状态变更等
  • 提供其他模块之间的数据交互和通信的枢纽(其他模块通过 API Server 查询或修改数据,只有 API Server 才直接操作 etcd)

kube-controller-manager

Controller Manager 由 kube-controller-manager 和 cloud-controller-manager 组成,是 Kubernetes 的大脑,它通过 apiserver 监控整个集群的状态,并确保集群处于预期的工作状态。

kube-controller-manager 由一系列的控制器组成

  • Replication Controller
  • Node Controller
  • CronJob Controller
  • Daemon Controller
  • Deployment Controller
  • Endpoint Controller
  • Garbage Collector
  • Namespace Controller
  • Job Controller
  • Pod AutoScaler
  • RelicaSet
  • Service Controller
  • ServiceAccount Controller
  • StatefulSet Controller
  • Volume Controller
  • Resource quota Controller

cloud-controller-manager

在 Kubernetes 启用 Cloud Provider 的时候才需要,用来配合云服务提供商的控制,也包括一系列的控制器,如:

  • Node Controller
  • Route Controller
  • Service Controller

kube-scheduler

kube-scheduler 负责分配调度 Pod 到集群内的节点上,它监听 kube-apiserver,查询还未分配 Node 的 Pod,然后根据调度策略为这些 Pod 分配节点(更新 Pod 的 NodeName 字段)。

调度器需要充分考虑诸多的因素:

  • 公平调度
  • 资源高效利用
  • QoS
  • affinity 和 anti-affinity
  • 数据本地化(data locality)
  • 内部负载干扰(inter-workload interference)
  • deadlines

Kubelet

每个节点上都运行一个 kubelet 服务进程,默认监听 10250 端口,接收并执行 master 发来的指令,管理 Pod 及 Pod 中的容器。每个 kubelet 进程会在 API Server 上注册节点自身信息,定期向 master 节点汇报节点的资源使用情况,并通过 cAdvisor 监控节点和容器的资源。

Container runtime

容器运行时(Container Runtime)是 Kubernetes 最重要的组件之一,负责真正管理镜像和容器的生命周期。Kubelet 通过 Container Runtime Interface (CRI) 与容器运行时交互,以管理镜像和容器。

kube-proxy

每台机器上都运行一个 kube-proxy 服务,它监听 API server 中 service 和 endpoint 的变化情况,并通过 iptables 等来为服务配置负载均衡(仅支持 TCP 和 UDP)。

kube-proxy 可以直接运行在物理机上,也可以以 static pod 或者 daemonset 的方式运行。

kube-proxy 当前支持一下几种实现:

  • userspace:最早的负载均衡方案,它在用户空间监听一个端口,所有服务通过 iptables 转发到这个端口,然后在其内部负载均衡到实际的 Pod。该方式最主要的问题是效率低,有明显的性能瓶颈。
  • iptables:目前推荐的方案,完全以 iptables 规则的方式来实现 service 负载均衡。该方式最主要的问题是在服务多的时候产生太多的 iptables 规则,非增量式更新会引入一定的时延,大规模情况下有明显的性能问题
  • ipvs:为解决 iptables 模式的性能问题,v1.8 新增了 ipvs 模式,采用增量式更新,并可以保证 service 更新期间连接保持不断开
  • winuserspace:同 userspace,但仅工作在 windows 上。

Kubernetes 架构

K8s 设置由几个部分组成,其中一些是可选的,一些是整个系统运行所必需的。下面是 k8s 的全局架构图:

Kubernetes 有两个不同的部分构成,一个是 Master,一个是 Node。Master 负责调度资源和为客户端提供 API,客户端可以是 UI 界面或者 CLI 工具,在 Kubernetes 中 CLI 工具通常为 kubectl。 Kubernetes Master 接受使用 YAML 定义的配置文件,根据配置文件中相关信息将容器分配到其中一个 Node 上。另外,镜像库在 Kubernetes 中也起到一个很重要的角色,Kubernetes 需要从镜像库中拉取镜像基于这个镜像的容器才能成功启动。常用的镜像库有 dockerhub、阿里云镜像库等。下面图片为 Master 的架构图:

Master 有三个组件:API Server、Scheduler、Controller。API Server 提供了友好易用的 API 供外部调用,同时有很多强大的工具使得 API 调用更加简单,如 kubectl 封装了大量 API 调用,使得部署、配置更加简单。Kubernetes-dashboard 可以让用户在界面上操作 Kubernetes,而无需手动输入各个 API 的调用地址参数等信息。

当 API Server 收到部署请求后,Scheduler 会根据所需的资源,判断各节点的资源占用情况分配合适的 Node 给新的容器。判断依据包括内存、CPU、磁盘等。

Controller 负责整个集群的整体协调和健康,保证每个组件以正确的方式运行。

在图的最下边是 ETCD 数据库。如前文所述 ETCD 是分布式存储数据库,其作为 Kubernetes 的中央数据库,存储了集群的状态,组件可以通过查询 ETCD 了解集群的状态。

Kubernetes Master 分配容器到 Node 执行,Node 将会承受压力,通常情况下新容器不会运行在 Master 上。或者说 Master 是不可调度的,但是你也可以选择把 Master 同时也作为 Node,但是这并不是地道的用法。下面的为 Node 的架构图:

Kube-proxy 在 Node 中管理网络,其左右至关重要。Kube-proxy 通过管理 iptables 等方式使得 pod 到 pod 之间,和 pod 到 node 之间网络能够互通。实质上在跨主机的 pod 之间网络也能够互通。

Kubelet 负责向 api server 报告信息,并把健康状态、指标和节点状态信息存入 ETCD 中。

Docker 上文已详细介绍这里就不多做阐述。

Supervisord 保证 Docker 和 kubelet 一直在运行中,supervisord 并不是必须组件,可以使用其他类似组件替换。

Pod 是可以在 Kubernetes 中创建和管理的最小可部署计算单元。一个 POD 中可以包含多个容器,但 Kubernetes 仅管理 pod。如果多个容器运行在一个 POD 中,就相当于这些容器运行在同一台主机中,需要注意端口占用问题。

参考资料

https://yeasy.gitbooks.io/docker_practice/content/introduction/what.html

https://yeasy.gitbooks.io/docker_practice/content/introduction/why.html

https://docs.docker.com/storage/storagedriver/

https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/

http://k8s.docker8.com/

https://www.youtube.com/watch?v=zeS6OyDoy78

关于猪齿鱼

Choerodon 猪齿鱼是一个全场景效能平台,基于 Kubernetes 的容器编排和管理能力,整合 DevOps 工具链、微服务和移动应用框架,来帮助企业实现敏捷化的应用交付和自动化的运营管理的平台,同时提供 IoT、支付、数据、智能洞察、企业应用市场等业务组件,致力帮助企业聚焦于业务,加速数字化转型。

大家也可以通过以下社区途径了解猪齿鱼的最新动态、产品特性,以及参与社区贡献: