Jim

Talk is cheap. Show me the code.


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

  • 搜索

Kubernetes CronJob 的一个应用案例

发表于 2019-08-30 | 分类于 Kubernetes

最近 Kubernetes 集群中出现过几次 Redis 故障,具体表现是每次集群重启(云资源按需启动), Redis Pod 都要老半天才能启动起来,后来逐渐排查定位才发现原来是由于 Redis 开启了 aof 持久化机制。

我们知道在 AOF 持久化机制下,Resdis 的每一条写命令都会被同步、并且追加的方式持久化的磁盘文件,当 Redis 由于意外故障时,下次重启就会原封不动地执行 AOF 文件中的命令来恢复 Redis。那么显然这种方式会带来一个问题是随着时间的推移 aof 文件体积会越来越大,每次 Redis 重启都要执行一遍 aof 持久化的命令,速度也会越来越慢,从而导致 Redis 启动变慢。

显然解决方法是怎么把 AOF 文件变小,丢弃没用的记录。Redis 有一条 BGREWRITE 命令就是解决这问题的,这条命令的工作原理是将当前 Redis 中的数据都导出成 Redis 写语句,然后生成新的 aof 文件,替换掉旧的。显然新的 aof 文件体积会原因小于长时间运行的旧的 aof 文件,因为新的 aof 只是当前 Redis 的数据恢复语句,只记录当前的状态。

由于我们 Redis 是运行在 Kubernetes 集群中,所以借助于 Kubernetes 的 CronJob 机制定期执行 Redis BGREWRITE 命令来重写 aof 文件,从而缩小文件体积。

Kubernetes CronJob 简介

Kubernetes CronJob 资源用来定义一些需要定时执行的任务,类似于 Linux/Unix 的 Crontab。CronJob 资源创建后会按照写的定时任务规则启动 Pod 执行定义的任务。关于 CronJob 更详细的信息见这里:https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs

基于 k8s CronJob 定期 AOF 重写

定义 CronJob 资源文件,每两小时进行一次 Redis AOF 重写,资源文件如下:
redis-cron.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: batch/v1beta1 #for API server versions >= 1.8.0 use batch/v1beta1
kind: CronJob
metadata:
name: redis-bgrewriteaof-cron
labels:
app: redis-bgrewriteaof-cron
spec:
schedule: "0 */2 * * *"
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 3
concurrencyPolicy: Forbid
startingDeadlineSeconds: 120
jobTemplate:
spec:
template:
spec:
containers:
- name: redis
image: docker.io/bitnami/redis:4.0.12
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-test
key: redis-password
args: [redis-cli, -h, redis-stage-master, -a, $(REDIS_PASSWORD), BGREWRITEAOF]
restartPolicy: OnFailure

关于上面资源里面的一些配置项含义在此不做具体介绍,很多都能看懂,具体每个配置项含义看官方文档,都有详细的解释。这里有一点需要「特别注意」的是 redis
容器的 args 字段引用环境变量的方法:
比如这里引用了 REDIS_PASSWORD 环境变量,需要写成:$(REDIS_PASSWORD) 这样的方式引用,而不能写成:$REDIS_PASSWORD 或者 ${REDIS_PASSWORD}。

执行 kubectl create 创建定义的 CronJob 资源

1
kubectl create -f redis-cron.yaml -n test

查看 CronJob 执行情况

1
2
3
$ kubectl get cronjob -n test
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
redis-bgrewriteaof-cron 0 */2 * * * False 0 1h 14h

可以看出最近一小时前已经调度过一次,如果要看调度是否成功可以看对应 Pod 的 log,也可以 kubectl get jobs -n test 查看启动的 Job 的状态。

相关文档

https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs | Running Automated Tasks with a CronJob
https://redis.io/commands/bgrewriteaof | Redis之AOF重写及其实现原理

数据库升级 DevOps 落地实践

发表于 2019-08-29 | 分类于 DevOps

在我们做持续集成/交付的过程中,应用的发布已经通过 DevOps 流水线基本能满足快速迭代的需求,但是很多企业在落地实践 DevOps 的过程中很容易忽略的一点是关于应用数据库版本、升级的管理,每次上线发布数据库的更新依然通过运维或者 DBA 手工更新,在微服务、容器盛行的背景下,服务多,服务发布速度快,显然靠人工该 DB 是跟不上迭代速度的,从而导致 DB 的更新成了整个软件交付周期的瓶颈。这一点我是深有体会,尤其是每次上线时,多个微服务同时上线,同时还需要进行 DB 升级,这个时候研发人员会给我们 SQL 执行,当然研发人员的数量是远远多于开发人员,所以每次上线运维人员经常陷入被”围堵“的尴尬境地。当然还存在其他种种痛点,大概总结下有如下痛点:

  • DB 的升级人工执行跟不上版本发布速度,成为软件交付的瓶颈;
  • 人工执行 DB 升级错误率比自动化执行更容易出错;
  • 各个环境 DB 没有统一的版本管理,经常会出现这个 SQL 有没有在某个环境执行的疑问;
  • 环境之间数据脚本同步经常出现遗漏的情况,由于开发或测试环境操作 DB 的人多,在应用从一个环境升级到另一个环境中经常忘记执行某条 SQL,然后导致各种问题故障。这种问题甚至在应用上线时也会频频出现,然后通知运维或 DBA 执行遗漏 SQL;

那么在 DevOps 落地实践中如何很好地处理好数据库升级这一环呢,从而解决上述存在的种种痛点,不要让数据库升级成为软件交付的瓶颈,使得数据库的升级流程融入自动化流水线。我调研了下业界关于应用 DB 升级的方案,不少文章或者圈内人士推崇专门的数据库管理工具版本化管理,自动化执行,比如 Flyway,Liquibase 等著名的工具,都是专业的数据库版本管理和自动化工具。

在本文中主要介绍如何将 Flyway 和其他 DevOps 工具链整合,实现 DB 升级的自动化和管理的版本化,从而解决之前存在的一系列痛点。本文用到的工具链有:Flyway + Jenkins 2.0(Pipeline 脚本)+ Gitlab + MySQL。需要说明的一点是本文并不是一步步讲解各种工具链如何使用和相关介绍,重点在于工具链的整合实践,以及如何恰当地应用。在文末附有完整的 Pipeline 实现脚本,仅供参考!

数据库升级 DevOps 实践带来了什么收益

其实在文章开言已经说清楚了,总结起来就两点:

  • 所有环境数据库版本统一管理;
  • 数据库升级变更自动化;

实践方案概要

数据库升级脚本统一按微服务模块以独立 git 仓库的形式管理起来,每次版本迭代,规划好 SQL 模型定义(DDL),将 db 脚本签入独立的 git 仓库,然后使用专门的数据库版本管理工具自动扫描仓库目录的 db 升级脚本,由于 db 升级脚本文件名称符合一定的命名规范,所以工具可以自动按版本号顺序执行脚本,并且已经执行过的脚步文件再次执行会忽略。关于 DB 升级工具的选择,我们选用 Flyway,功能单一、容易上手,以规约优于配置的思想规范 DB 的版本化管理,我们写的 SQL 脚本文件都必须符合 Flyway 的文件名命名规范,这样才能在升级过程中生效。

具体实践

借助的工具链:Flyway + Jenkins 2.0(Pipeline 脚本)+ Gitlab + MySQL(Google Cloud SQL)

  1. 以微服务应用 git 工程名称在 gitlab 一个单独的组创建 db 代码工程;
  2. 在 db 代码工程中创建以数据库命名的目录,存放对应数据库升级的脚本文件,脚本文件名称需要符合 FlywayDB 的命名规范:
  3. db 代码工程分支管理:dev 环境对应 dev 分支,test 环境对应 test 分支,stage 环境对应 stage 分支,生产环境对应 master 分支;
  4. Jenkins 脚本注册相应代码工程名称和对应 db 名称;
  5. 点击 Jenkins 执行数据库升级;

强制规约

  1. gitlab 代码工程名称和 db 工程名称一致,db 工程目录下文件夹以数据库名称命名;
  2. db 脚本名称符合 FlywayDB 命名规范;
  3. db 脚本文件版本名统一大于 1.0,比如: 可以是 V1.0.1,但不能是 V0.2.3;
  4. db 脚本内容为 DDL 语句,不能包含 DML 或者 DCL 语句,这个要严格审核,因为 DML 和 DCL 版本追踪没意义,而且各个环境可能还不兼容,FlywayDB 的本质是数据库 Sechma 版本管理,只关心表结构,表里面的数据不关心。关于数据库 DDL、DML、DCL 相关概念及区别见这里;
  5. 已经执行过的 db 脚本不能修改后重复执行,并且执行过的 db 脚本文件需要原封不动保留,不能丢失和修改,否则升级会失败,这个一定要注意。如果对已经执行的 db 脚本不满意,有改动需要变更,则新加 db 脚本文件,可以小版本号比原先增 1,相当于临时 fix,但是我们尽量减少这种情况的发生;

具体 Workflow

开发人员 Workflow

开发、测试、预发布环境开发人员点击 Jenkins job 执行数据库升级:

  1. 将 SQL 脚本按照 FlywayDB 规范提交到对应的 db 仓库,提交 MR 到对应分支;
  2. 小组 db 脚本审核人审核没问题后合并 db 代码;
  3. 小组成员点击 Jenkins Job,执行数据库升级
    3.1 选择环境+服务名称+要升级的数据库名称

    3.2 运行 Job

运维人员 Workflow

运维人员只负责线上 SQL 的升级:

  1. 开发人员告知运维人员本次上线 db 脚本已提交到代码仓库并 merge 到 master 分支;
  2. 运维人员点击 Jenkins Job 执行相应服务的数据库升级:
    2.1 选择服务名称+要升级的数据库名称

    2.2 运行 Job,Pipeline 会阻塞在确认节点,做最后的审查

    2.3 Job 执行完成

Workflow 举例

  1. 新建一个 gitlab 工程,专门存放 db 脚本:
    服务名称假设为 db-migration-demo,db 名称为 demo,仓库里面存放的 SQL 脚本如下:

  2. git 提交代码,然后点击 Jenkins Job,执行数据库升级

关于 Pipeline 设计的两个功能点

1. 数据库整库备份策略

数据库 DDL 变更前整库备份一下是有必要的,但是每次变更都整库备份也不合理,因为可能某天上线,数据库升级比较集中,一天内会触发很多次备份,造成了资源的浪费。解决方案是给备份一个时间窗口(比如 2 小时),每次执行前判断下最近两小时是否有备份,如果没有则触发整库备份,这样就能避免每次执行 Job 都会触发整库备份。
具体解决方法:
获取当前时间减去两小时的时间,然后和上次整库备份的时间戳比较,如果前者大,说明最近两小时内没备份,然后自动触发整库备份,时间戳比较用 Shell 脚本实现:

1
2
3
4
5
6
7
8
9
10
# !/bin/bash

t1=`date -d "$1" +%s`
t2=`date -d "$2" +%s`

if [ $t1 -ge $t2 ]; then
echo "true"
else
echo "false"
fi

2. 每次变更前备份库下的所有表结构,同时记录下 FlywayDB 更改前后状态

表结构备份和 FlywayDB 更改前后状态信息都以制品的方式归到 Jenkins,这样可以随时在 Jenkins 界面查看相关信息,比如查看 Flyway 前后执行状态如何,点开制品页即可看到:

附:Pipeline 脚本实现

为例减小文章的篇幅,这里只贴下运维人员 Workflow 的 Jenkins pipeline 脚本,研发人员的和这个类似,只是一些小的改动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
pipeline {
parameters {
//服务名称
choice(name:'serviceName', choices: [
'db-migration-demo'
]
, description: '服务名称')
//数据库
choice(name:'dbName', choices: [
'demo'
]
, description: '数据库名称')
}
agent {
kubernetes {
label "sql-${UUID.randomUUID().toString()}"
defaultContainer 'jnlp'
yaml """
apiVersion: v1
kind: Pod
metadata:
labels:
some-label: db-imgration
spec:
containers:
- name: flyway
image: boxfuse/flyway
command:
- cat
tty: true
- name: mysql-client
image: arey/mysql-client
command:
- cat
tty: true
- name: gcloud
image: google/cloud-sdk:alpine
command:
- cat
tty: true
"""
}
}
post {
failure {
echo "Database migration failed!"
}
success {
echo "Database migration success!"
}
}


options {
gitLabConnection('gitlab-connection')
//保持构建的最大个数
buildDiscarder(logRotator(numToKeepStr: '20'))
}

stages {
stage('初始化') {
steps {
script {
currentBuild.description = "production环境${params.serviceName}服务${params.dbName}库升级..."
}

container('gcloud') {
withCredentials([file(credentialsId: 'cloudInfrastructureAccess', variable: 'cloudSQLCredentials')]) {
sh "gcloud auth activate-service-account ${env.cloudInfrastructureAccessSA} --key-file=${cloudSQLCredentials} --project=${env.gcpProject}"
}
}

// 判断是否要进行数据库备份,如果两小时内没有备份则自动触发全量备份
script {
isBackup = 'false'
// 默认 jenkins 跑在 busybox 容器,获取时间和普通 Linux 发行版有点区别
date2HoursAgo = sh(returnStdout: true, script: "date -u +'%Y-%m-%d %H' -d@\"\$((`date +%s`-7200))\"").trim()
container('gcloud') {
latestDBBackupTime = sh(returnStdout: true, script: "gcloud sql backups list --instance=${env.prodMySqlInstance} --limit=1 | grep -v 'WINDOW_START_TIME' | awk '{print \$2}' | awk -F ':' '{print \$1}'|sed 's/T/ /g'").trim()
}
withCredentials([file(credentialsId: 'time-compare.sh', variable: 'timeCompare')]) {
isBackup = sh(returnStdout: true, script: "sh ${timeCompare} \'$date2HoursAgo\' \'$latestDBBackupTime\'").trim()
echo "$date2HoursAgo"
echo "$latestDBBackupTime"
echo "$isBackup"
}
}
}
}

// 如果两小时内没有备份则自动触发全量备份
stage('整库智能备份') {
when {
expression { isBackup == 'true' }
}
steps {
script {
container('gcloud') {
// 列出最近 10 个备份,便于观察
sh "gcloud sql backups list --instance=${env.prodMysqlInstance} --limit=10"
backupTimestamp = sh(returnStdout: true, script: "date -u +'%Y-%m-%d %H%M%S'").trim()
backupDescription="Flyway backuped at $backupTimestamp (UTC)"
// gcloud 创建 db 备份
sh "gcloud sql backups create --async --instance=${env.prodMysqlInstance} --description=\'$backupDescription\'"
sh "gcloud sql backups list --instance=${env.prodMysqlInstance} --limit=10"
}
}
}
}

stage('表结构备份') {
steps {
withCredentials([usernamePassword(credentialsId: "sql-secret-production", passwordVariable: 'sqlPass', usernameVariable: 'sqlUser')]) {
container('mysql-client') {
sh "mysqldump -h ${env.prodMySqlHost} -u$sqlUser -p$sqlPass -d ${params.dbName} --single-transaction > ${params.serviceName}-${params.dbName}-`TZ=UTC-8 date +%Y%m%d-%H%M%S`-dump.sql"
}
}
// 表结构备份同步到 gcs 存储桶
container('gcloud') {
sh "gsutil cp *-dump.sql ${env.gcsBackupBucket}/db/production/${params.serviceName}/"
}

// jenkins 归档数据库备份,可在 BlueOcean 页面制品页查看
archiveArtifacts "*-dump.sql"
}
}

stage('拉取 db 脚本') {
steps {
script {
checkout([$class: 'GitSCM', branches: [[name: "master"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'jenkins-deploy', url: "${env.dbMigrationGitRepoGroup}/${params.serviceName}"]]])
}
}
}

stage('flyway migrate') {
steps{
script {
host = "${env.prodMySqlHost}"
timestamp = sh(returnStdout: true, script: "TZ=UTC-8 date +%Y%m%d-%H%M%S").trim()
flywayStateFile = "flyway-state-production-${params.serviceName}-${params.dbName}_${timestamp}.txt"

container('flyway') {
withCredentials([usernamePassword(credentialsId: "sql-secret-production", passwordVariable: 'sqlPass', usernameVariable: 'sqlUser')]){
sh "flyway -url=jdbc:mysql://${host}/${params.dbName}?useSSL=false -user=${sqlUser} -password=${sqlPass} -locations=filesystem:${params.dbName} -baselineOnMigrate=true repair"
sh "echo \"[ flyway 升级前 db 状态 ]\" > $flywayStateFile"
sh "flyway -url=jdbc:mysql://${host}/${params.dbName}?useSSL=false -user=${sqlUser} -password=${sqlPass} -locations=filesystem:${params.dbName} -baselineOnMigrate=true info \
| tee -a $flywayStateFile"
try {
timeout(time: 8, unit: 'HOURS') {
env.isMigrateDB = input message: '确认升级 DB?',
parameters: [choice(name: "isMigrateDB", choices: 'Yes\nNo', description: "您当前选择要升级的是${params.serviceName}服务${params.dbName}库,确认升级?")]
}
} catch (err) {
sh "echo 'Exception!' && exit 1"
}
if (env.isMigrateDB == 'No') {
sh "echo '已取消升级!' && exit 1"
}
sh "flyway -url=jdbc:mysql://${host}/${params.dbName}?useSSL=false -user=${sqlUser} -password=${sqlPass} -locations=filesystem:${params.dbName} -baselineOnMigrate=true migrate"
sh "echo \"\n\n[ flyway 升级后 db 状态 ]\" >> $flywayStateFile"
sh "flyway -url=jdbc:mysql://${host}/${params.dbName}?useSSL=false -user=${sqlUser} -password=${sqlPass} -locations=filesystem:${params.dbName} -baselineOnMigrate=true info \
| tee -a $flywayStateFile"
archiveArtifacts "$flywayStateFile"
}
}
}
}
}
}
}

从 Docker 到 Kubernetes 日志管理机制详解

发表于 2019-08-26 | 分类于 Kubernetes

在容器化时代,容器应用的日志管理和传统应用存在很大的区别,为了顺应容器化应用,Docker 和 Kubernetes 提供了一套完美的日志解决方案。本文从 Docker 到 Kubernetes 逐步介绍在容器化时代日志的管理机制,以及在 Kubernetes 平台下有哪些最佳的日志收集方案。涉及到的话题有 Docker 日志管理机制、Kubernetes 日志管理机制、Kubernetes 集群日志收集方案。本文结构如下:

  • Docker 日志管理机制

    • Docker 的日志种类
    • 基于日志驱动(loging driver)的日志管理机制
    • Docker 日志驱动(loging driver)配置
    • Docker 默认的日志驱动 json-file
  • Kubernetes 日志管理机制

    • 应用 Pod 日志
    • Kuberntes 集群组件日志
  • Kubernetes 集群日志收集方案

    • 节点级日志代理方案
    • sidecar 容器方案
    • 应用程序直接将日志传输到日志平台

Docker 日志管理机制

Docker 的日志种类

在 Docker 中日志分为两大类:

  • Docker 引擎日志;
  • 容器日志;
Docker 引擎日志

Docker 引擎日志就是 docker 服务的日志,即 dockerd 守护进程的日志,在支持 Systemd 的系统中可以通过 journal -u docker 查看日志。

容器日志

容器日志指的是每个容器打到 stdout 和 stderr 的日志,而不是容器内部的日志文件。docker 管理所有容器打到 stdout 和 stderr 的日志,其他来源的日志不归 docker 管理。我们通过 docker logs 命令查看容器日志都是读取容器打到 stdout 和 stderr 的日志。

基于日志驱动(loging driver)的日志管理机制

Docker 提供了一套通用、灵活的日志管理机制,Docker 将所有容器打到 stdout 和 stderr 的日志都统一通过日志驱动重定向到某个地方。

Docker 支持的日志驱动有很多,比如 local、json-file、syslog、journald 等等,类似插件一样,不同的日志驱动可以将日志重定向到不同的地方,这体现了 Docker 日志管理的灵活性,以热插拔的方式实现日志不同目的地的输出。

Dokcer 默认的日志日志驱动是 json-file,该驱动将将来自容器的 stdout 和 stderr 日志都统一以 json 的形式存储到本地磁盘。日志存储路径格式为:/var/lib/docker/containers/<容器 id>/<容器 id>-json.log。所以可以看出在 json-file 日志驱动下,Docker 将所有容器日志都统一重定向到了 /var/lib/docker/containers/ 目录下,这为日志收集提供了很大的便利。

注意:只有日志驱动为:local、json-file 或者 journald 时,docker logs 命令才能查看到容器打到 stdout/stderr 的日志。

下面为官方支持的日志驱动列表:

驱动 描述
none 运行的容器没有日志,docker logs 也不返回任何输出。
local 日志以自定义格式存储,旨在实现最小开销。
json-file 日志格式为JSON。Docker的默认日志记录驱动程序。
syslog 将日志消息写入syslog。该 syslog 守护程序必须在主机上运行。
journald 将日志消息写入journald。该journald守护程序必须在主机上运行。
gelf 将日志消息写入Graylog扩展日志格式(GELF)端点,例如Graylog或Logstash。
fluentd 将日志消息写入fluentd(转发输入)。该fluentd守护程序必须在主机上运行。
awslogs 将日志消息写入Amazon CloudWatch Logs。
splunk 使 用HTTP 事件收集器将日志消息写入 splunk。
etwlogs 将日志消息写为 Windows 事件跟踪(ETW)事件。仅适用于Windows平台。
gcplogs 将日志消息写入 Google Cloud Platform(GCP)Logging。
logentries 将日志消息写入 Rapid7 Logentries。

Docker 日志驱动(loging driver)配置

上面我们已经知道 Docker 支持多种日志驱动类型,我们可以修改默认的日志驱动配置。日志驱动可以全局配置,也可以给特定容器配置。

  • 查看 Docker 当前的日志驱动配置

    1
    docker  info |grep  "Logging Driver"
  • 查看单个容器的设置的日志驱动

    1
    docker inspect  -f '{{.HostConfig.LogConfig.Type}}'   容器id
  • Docker 日志驱动全局配置
    全局配置意味所有容器都生效,编辑 /etc/docker/daemon.json 文件(如果文件不存在新建一个),添加日志驱动配置。
    示例:配置 Docker 引擎日志驱动为 syslog

    1
    2
    3
    {
    "log-driver": "syslog"
    }
  • 给特定容器配置日志驱动
    在启动容器时指定日志驱动 --log-driver 参数。
    示例:启动 nginx 容器,日志驱动指定为 journald

    1
    docker  run --name nginx -d --log-driver journald nginx

Docker 默认的日志驱动 json-file

json-file 日志驱动记录所有容器的 STOUT/STDERR 的输出 ,用 JSON 的格式写到文件中,每一条 json 日志中默认包含 log, stream, time 三个字段,示例日志如下:
文件路径为:
/var/lib/docker/containers/40f1851f5eb9e684f0b0db216ea19542529e0a2a2e7d4d8e1d69f3591a573c39/40f1851f5eb9e684f0b0db216ea19542529e0a2a2e7d4d8e1d69f3591a573c39-json.log

1
{"log":"14:C 25 Jul 2019 12:27:04.072 * DB saved on disk\n","stream":"stdout","time":"2019-07-25T12:27:04.072712524Z"}

那么打到磁盘的 json 文件该如何配置轮转,防止撑满磁盘呢?每种 Docker 日志驱动都有相应的配置项日志轮转,比如根据单个文件大小和日志文件数量配置轮转。json-file 日志驱动支持的配置选项如下:

选项 描述 示例值
max-size 切割之前日志的最大大小。可取值单位为(k,m,g), 默认为-1(表示无限制)。 --log-opt max-size=10m
max-file 可以存在的最大日志文件数。如果切割日志会创建超过阈值的文件数,则会删除最旧的文件。仅在max-size设置时有效。正整数。默认为1。 --log-opt max-file=3
labels 适用于启动Docker守护程序时。此守护程序接受的以逗号分隔的与日志记录相关的标签列表。 --log-opt labels=production_status,geo
env 适用于启动Docker守护程序时。此守护程序接受的以逗号分隔的与日志记录相关的环境变量列表。 --log-opt env=os,customer
compress 切割的日志是否进行压缩。默认是disabled。 --log-opt compress=true

Kubernetes 日志管理机制

在 Kubernetes 中日志也主要有两大类:

  • 应用 Pod 日志;
  • Kuberntes 集群组件日志;
应用 Pod 日志

Kubernetes Pod 的日志管理是基于 Docker 引擎的,Kubernetes 并不管理日志的轮转策略,日志的存储都是基于 Docker 的日志管理策略。k8s 集群调度的基本单位就是 Pod,而 Pod 是一组容器,所以 k8s 日志管理基于 Docker 引擎这一说法也就不难理解了,最终日志还是要落到一个个容器上面。

假设 Docker 日志驱动为 json-file,那么在 k8s 每个节点上,kubelet 会为每个容器的日志创建一个软链接,软连接存储路径为:/var/log/containers/,软连接会链接到 /var/log/pods/ 目录下相应 pod 目录的容器日志,被链接的日志文件也是软链接,最终链接到 Docker 容器引擎的日志存储目录:/var/lib/docker/container 下相应容器的日志。另外这些软链接文件名称含有 k8s 相关信息,比如:Pod id,名字空间,容器 ID 等信息,这就为日志收集提供了很大的便利。

举例:我们跟踪一个容器日志文件,证明上述的说明,跟踪一个 kong Pod 日志,Pod 副本数为 1

/var/log/containers/kong-kong-d889cf995-2ntwz_kong_kong-432e47df36d0992a3a8d20ef6912112615ffeb30e6a95c484d15614302f8db03.log
------->
/var/log/pods/kong_kong-kong-d889cf995-2ntwz_a6377053-9ca3-48f9-9f73-49856908b94a/kong/0.log
------->
/var/lib/docker/containers/432e47df36d0992a3a8d20ef6912112615ffeb30e6a95c484d15614302f8db03/432e47df36d0992a3a8d20ef6912112615ffeb30e6a95c484d15614302f8db03-json.log

Kuberntes 集群组件日志

Kuberntes 集群组件日志分为两类:

  • 运行在容器中的 Kubernetes scheduler 和 kube-proxy。
  • 未运行在容器中的 kubelet 和容器 runtime,比如 Docker。

在使用 systemd 机制的服务器上,kubelet 和容器 runtime 写入日志到 journald。如果没有 systemd,他们写入日志到 /var/log 目录的 .log 文件。容器中的系统组件通常将日志写到 /var/log 目录,在 kubeadm 安装的集群中它们以静态 Pod 的形式运行在集群中,因此日志一般在 /var/log/pods 目录下。

Kubernetes 集群日志收集方案

Kubernetes 本身并未提供集群日志收集方案,k8s 官方文档给了三种日志收集的建议方案:

  • 使用运行在每个节点上的节点级的日志代理
  • 在应用程序的 pod 中包含专门记录日志 sidecar 容器
  • 应用程序直接将日志传输到日志平台

节点级日志代理方案

从前面的介绍我们已经了解到,k8s 每个节点都将容器日志统一存储到了 /var/log/containers/ 目录下,因此可以在每个节点安装一个日志代理,将该目录下的日志实时传输到日志存储平台。

由于需要每个节点运行一个日志代理,因此日志代理推荐以 DaemonSet 的方式运行在每个节点。官方推荐的日志代理是 fluentd,当然也可以使用其他日志代理,比如 filebeat,logstash 等。

sidecar 容器方案

有两种使用 sidecar 容器的方式:

  • sidecar 容器重定向日志流
  • sidecar 容器作为日志代理
sidecar 容器重定向日志流

这种方式基于节点级日志代理方案,sidecar 容器和应用容器在同一个 Pod 运行,这个容器的作用就是读取应用容器的日志文件,然后将读取的日志内容重定向到 stdout 和 stderr,然后通过节点级日志代理统一收集。这种方式不推荐使用,缺点就是日志重复存储了,导致磁盘使用会成倍增加。比如应用容器的日志本身打到文件存储了一份,sidecar 容器重定向又存储了一份(存储到了 /var/lib/docker/containers/ 目录下)。这种方式的应用场景是应用本身不支持将日志打到 stdout 和 stderr,所以才需要 sidecar 容器重定向下。

sidecar 容器作为日志代理

这种方式不需要节点级日志代理,和应用容器在一起的 sidecar 容器直接作为日志代理方式运行在 Pod 中,sidecar 容器读取应用容器的日志,然后直接实时传输到日志存储平台。很显然这种方式也存在一个缺点,就是每个应用 Pod 都需要有个 sidecar 容器作为日志代理,而日志代理对系统 CPU、和内存都有一定的消耗,在节点 Pod 数很多的时候这个资源消耗其实是不小的。另外还有个问题就是在这种方式下由于应用容器日志不直接打到 stdout 和 stderr,所以是无法使用 kubectl logs 命令查看 Pod 中容器日志。

应用程序直接将日志传输到日志平台

这种方式就是应用程序本身直接将日志打到统一的日志收集平台,比如 Java 应用可以配置日志的 appender,打到不同的地方,很显然这种方式对应用程序有一定的侵入性,而且还要保证日志系统的健壮性,从这个角度看应用和日志系统还有一定的耦合性,所以个人不是很推荐这种方式。

总结:综合对比上述三种日志收集方案优缺点,更推荐使用节点级日志代理方案,这种方式对应用没有侵入性,而且对系统资源没有额外的消耗,也不影响 kubelet 工具查看 Pod 容器日志。

相关文档

https://juejin.im/entry/5c03f8bb5188251ba905741d | Docker 日志驱动配置
https://www.cnblogs.com/operationhome/p/10907591.html | Docker容器日志管理最佳实践
https://www.cnblogs.com/cocowool/p/Docker_Kubernetes_Log_Location.html | 谈一下Docker与Kubernetes集群的日志和日志管理
https://kubernetes.io/docs/concepts/cluster-administration/logging/ | Kubernetes 日志架构

Kubernetes 集群安全机制详解

发表于 2019-08-22 | 分类于 Kubernetes

本文主要介绍 Kubernetes 的安全机制,如何使用一系列概念、技术点、机制确保集群的访问是安全的,涉及到的关键词有:api-server,认证,授权,准入控制,RBAC,Service Account,客户端证书认证,Kubernetes 用户,Token 认证等等。虽然涉及到的技术点比较琐碎,比较多,但是了解整个机制后就很容易将其串起来,从而能很好地理解 Kubernetes 集群安全机制。本文结构如下:

  • Kubernetes api-server 安全访问机制;
  • Kubernetes 认证方式之客户端证书(TLS);
  • Kubernetes 授权方式之 RBAC 介绍;
  • Kubernetes 中两种账号类型介绍;
  • 实践:基于客户端证书认证方式新建 Kubeconfig 访问集群;
  • 实践:Kubeconfig 或 token 方式登陆 Kubernetes dashboard;

Kubernetes api-server 安全访问机制

kube-apiserver 是 k8s 整个集群的入口,是一个 REST API 服务,提供的 API 实现了 Kubernetes 各类资源对象(如 Pod,RC,Service 等)的增、删、改、查,API Server 也是集群内各个功能模块之间交互和通信的枢纽,是整个集群的总线和数据中心。

由此可见 API Server 的重要性了,我们用 kubectl、各种语言提供的客户端库或者发送 REST 请求和集群交互,其实底层都是以 HTTP REST 请求的方式同 API Server 交互,那么访问的安全机制是如何保证的呢,总不能随便来一个请求都能接受并响应吧。API Server 为此提供了一套特有的、灵活的安全机制,每个请求到达 API Server 后都会经过:认证(Authentication)–>授权(Authorization)–>准入控制(Admission Control) 三道安全关卡,通过这三道安全关卡的请求才给予响应:

认证(Authentication)

认证阶段的工作是识别用户身份,支持的认证方式有很多,比如:HTTP Base,HTTP token,TLS,Service Account,OpenID Connect 等,API Server 启动时可以同时指定多种认证方式,会逐个使用这些方法对客户请求认证,只要通过任意一种认证方式,API Server 就会认为 Authentication 成功。高版本的 Kubernetes 默认认证方式是 TLS。在 TLS 认证方案中,每个用户都拥有自己的 X.509 客户端证书,API 服务器通过配置的证书颁发机构(CA)验证客户端证书。

授权(Authorization)

授权阶段判断请求是否有相应的权限,授权方式有多种:AlwaysDeny,AlwaysAllow,ABAC,RBAC,Node 等。API Server 启动时如果多种授权模式同时被启用,Kubernetes 将检查所有模块,如果其中一种通过授权,则请求授权通过。 如果所有的模块全部拒绝,则请求被拒绝(HTTP状态码403)。高版本 Kubernetes 默认开启的授权方式是 RBAC 和 Node。

准入控制(Admission Control)

准入控制判断操作是否符合集群要求,准入控制配备有一个“准入控制器”的列表,发送给 API Server 的每个请求都需要通过每个准入控制器的检查,检查不通过,则 API Server 拒绝调用请求,有点像 Web 编程的拦截器的意思。具体细节在这里不进行展开了,如想进一步了解见这里:Using Admission Controllers。

Kubernetes 认证方式之客户端证书(TLS)

通过上一节介绍我们知道 Kubernetes 认证方式有多种,这里我们简单介绍下客户端证书(TLS)认证方式,也叫 HTTPS 双向认证。一般我们访问一个 https 网站,认证是单向的,只有客户端会验证服务端的身份,服务端不会管客户端身份如何。我们来大概看下 HTTPS 握手过程(单向认证):

  1. 客户端发送 Client Hello 消息给服务端;
  2. 服务端回复 Server Hello 消息和自身证书给客户端;
    3.客户端检查服务端证书的合法性,证书检查通过后根据双方发送的消息生成 Premaster Key,然后用服务端的证书里面的公钥加密 Premaster Key 并发送给服务端 ;
  3. 服务端通过自己的私钥解密得到 Premaster Key,然后通过双方协商的算法和交换的消息生成 Session Key(后续双方数据加密用的对称密钥,客户端也能通过同样的方法生成同样的 Key),然后回复客户端一个消息表明握手结束,后续发送的消息会以协商的对称密钥加密。

关于 HTTPS 握手详细过程见之前文章:「Wireshark 抓包理解 HTTPS 协议」

HTTPS 双向认证的过程就是在上述第 3 步的时候同时回复自己的证书给服务端,然后第 4 步服务端验证收到客户端证书的合法性,从而达到了验证客户端的目的。在 Kubernetes 中就是用了这样的机制,只不过相关的 CA 证书是自签名的:

Kubernetes 授权方式之 RBAC 介绍

基于角色的访问控制(Role-Based Access Control, 即 RBAC),是 k8s 提供的一种授权策略,也是新版集群默认启用的方式。RBAC 将角色和角色绑定分开,角色指的是一组定义好的操作集群资源的权限,而角色绑定是将角色和用户、组或者服务账号实体绑定,从而赋予这些实体权限。可以看出 RBAC 这种授权方式很灵活,要赋予某个实体权限只需要绑定相应的角色即可。针对 RBAC 机制,k8s 提供了四种 API 资源:Role、ClusterRole、RoleBinding、ClusterRoleBinding。

  • Role: 只能用于授予对某一单一命名空间中资源的访问权限,因此在定义时必须指定 namespace;
    以下示例描述了 default 命名空间中的一个 Role 对象的定义,用于授予对 pod 的读访问权限:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    kind: Role
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
    namespace: default
    name: pod-reader
    rules:
    - apiGroups: [""] # 空字符串""表明使用core API group
    resources: ["pods"]
    verbs: ["get", "watch", "list"]
  • ClusterRole:针对集群范围的角色,能访问整个集群的资源;
    下面示例中的 ClusterRole 定义可用于授予用户对某一特定命名空间,或者所有命名空间中的 secret(取决于其绑定方式)的读访问权限:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
    # 鉴于ClusterRole是集群范围对象,所以这里不需要定义"namespace"字段
    name: secret-reader
    rules:
    - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "watch", "list"]
  • RoleBinding:将 Role 和用户实体绑定,从而赋予用户实体命名空间内的权限,同时也支持 ClusterRole 和用户实体的绑定;
    下面示例中定义的 RoleBinding 对象在 default 命名空间中将 pod-reader 角色授予用户 jane。 这一授权将允许用户 jane 从 default 命名空间中读取pod:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 以下角色绑定定义将允许用户"jane"从"default"命名空间中读取pod。
    kind: RoleBinding
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
    name: read-pods
    namespace: default
    subjects:
    - kind: User
    name: jane
    apiGroup: rbac.authorization.k8s.io
    roleRef:
    kind: Role
    name: pod-reader
    apiGroup: rbac.authorization.k8s.io
  • ClusterRoleBinding:将 ClusterRole 和用户实体绑定,从而赋予用户实体集群范围的权限;
    下面示例中所定义的 ClusterRoleBinding 允许在用户组 manager 中的任何用户都可以读取集群中任何命名空间中的 secret:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 以下`ClusterRoleBinding`对象允许在用户组"manager"中的任何用户都可以读取集群中任何命名空间中的secret。
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
    name: read-secrets-global
    subjects:
    - kind: Group
    name: manager
    apiGroup: rbac.authorization.k8s.io
    roleRef:
    kind: ClusterRole
    name: secret-reader
    apiGroup: rbac.authorization.k8s.io

关于 RBAC 更详细的讲解见这里:https://jimmysong.io/kubernetes-handbook/concepts/rbac.html

Kubernetes 中两种账号类型介绍

K8S中有两种用户(User):服务账号(ServiceAccount)和普通的用户(User)。 ServiceAccount 是由 k8s 管理的,而 User 账号是在外部管理,k8s 不存储用户列表,也就是说针对用户的增、删、该、查都是在集群外部进行,k8s 本身不提供普通用户的管理。

两种账号的区别:

  • ServiceAccount 是 k8s 内部资源,而普通用户是存在于 k8s 之外的;
  • ServiceAccount 是属于某个命名空间的,不是全局的,而普通用户是全局的,不归某个 namespace 特有;
  • ServiceAccount 一般用于集群内部 Pod 进程使用,和 api-server 交互,而普通用户一般用于 kubectl 或者 REST 请求使用;

ServiceAccount 的实际应用

ServiceAccount 可以用于 Pod 访问 api-server,其对应的 Token 可以用于 kubectl 访问集群,或者登陆 kubernetes dashboard。

普通用户的实际应用

  • X509 客户端证书
    客户端证书验证通过为API Server 指定 –client-ca-file=xxx 选项启用,API Server通过此 ca 文件来验证 API 请求携带的客户端证书的有效性,一旦验证成功,API Server 就会将客户端证书 Subject 里的 CN 属性作为此次请求的用户名。关于客户端证书方式的用户后面会有专门的实践介绍。
  • 静态token文件
    通过指定–token-auth-file=SOMEFILE 选项来启用 bearer token 验证方式,引用的文件是一个包含了 token,用户名,用户ID 的csv文件,请求时,带上 Authorization: Bearer xxx 头信息即可通过 bearer token 验证;
  • 静态密码文件
    通过指定 --basic-auth-file=SOMEFILE 选项启用密码验证,引用的文件是一个包含 密码,用户名,用户ID 的csv文件,请求时需要将 Authorization 头设置为 Basic BASE64ENCODED(USER:PASSWORD);

实践:基于客户端证书认证方式新建 Kubeconfig 访问集群

Kubeconfig 文件详解

我们知道在安装完 k8s 集群后会生成 $HOME/.kube/config 文件,这个文件就是 kubectl 命令行工具访问集群时使用的认证文件,也叫 Kubeconfig 文件。这个 Kubeconfig 文件中有很多重要的信息,文件大概结构是这样,这里说明下每个字段的含义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: ...
server: https://192.168.26.10:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: ...
client-key-data: ...

可以看出文件分为三大部分:clusters、contexts、users
clusters 部分
定义集群信息,包括 api-server 地址、certificate-authority-data: 用于服务端证书认证的自签名 CA 根证书(master 节点 /etc/kubernetes/pki/ca.crt 文件内容 )。

contexts 部分
集群信息和用户的绑定,kubectl 通过上下文提供的信息连接集群。

users 部分
多种用户类型,默认是客户端证书(x.509 标准的证书)和证书私钥,也可以是 ServiceAccount Token。这里重点说下前者:

  • client-certificate-data: base64 加密后的客户端证书;
  • client-key-data: base64 加密后的证书私钥;

一个请求在通过 api-server 的认证关卡后,api-server 会从收到客户端证书中取用户信息,然后用于后面的授权关卡,这里所说的用户并不是服务账号,而是客户端证书里面的 Subject 信息:O 代表用户组,CN 代表用户名。为了证明,可以使用 openssl 手动获取证书中的这个信息:
首先,将 Kubeconfig 证书的 user 部分 client-certificate-data 字段内容进行 base64 解密,保存文件为 client.crt,然后使用 openssl 解析证书信息即可看到 Subject 信息:

1
openssl x509 -in client.crt -text

解析集群默认的 Kubeconfig 客户端证书得到的 Subject 信息是:

1
Subject: O=system:masters, CN=kubernetes-admin

可以看出该证书绑定的用户组是 system:masters,用户名是 kubernetes-admin,而集群中默认有个 ClusterRoleBinding 叫 cluster-admin,它将名为 cluster-admin 的 ClusterRole 和用户组 system:masters 进行了绑定,而名为 cluster-admin 的 ClusterRole 有集群范围的 Superadmin 权限,这也就理解了为什么默认的 Kubeconfig 能拥有极高的权限来操作 k8s 集群了。

新建具有只读权限的 Kubeconfig 文件

上面我们已经解释了为什么默认的 Kubeconfig 文件具有 Superadmin 权限,这个权限比较高,有点类型 Linux 系统的 Root 权限。有时我们会将集群访问权限开放给其他人员,比如供研发人员查看 Pod 状态、日志等信息,这个时候直接用系统默认的 Kubeconfig 就不太合理了,权限太大,集群的安全性没有了保障。更合理的做法是给研发人员一个只读权限的账号,避免对集群进行一些误操作导致故障。

我们以客户端证书认证方式创建 Kubeconfig 文件,所以需要向集群自签名 CA 机构(master 节点)申请证书,然后通过 RBAC 授权方式给证书用户授予集群只读权限,具体方法如下:

假设我们设置证书的用户名为:developer – 证书申请时 -subj 选项 CN 参数。

生成客户端 TLS 证书
  1. 创建证书私钥

    1
    openssl genrsa -out developer.key 2048
  2. 用上面私钥创建一个 csr(证书签名请求)文件,其中我们需要在 subject 里带上用户信息(CN为用户名,O为用户组)

    1
    openssl req -new -key developer.key -out developer.csr -subj "/CN=developer"

    其中/O参数可以出现多次,即可以有多个用户组

  3. 找到 k8s 集群(API Server)的 CA 根证书文件,其位置取决于安装集群的方式,通常会在 master 节点的 /etc/kubernetes/pki/ 路径下,会有两个文件,一个是 CA 根证书(ca.crt),一个是 CA 私钥(ca.key) 。

  4. 通过集群的 CA 根证书和第 2 步创建的 csr 文件,来为用户颁发证书
    1
    openssl x509 -req -in developer.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out developer.crt -days 365

至此,客户端证书颁发完成,我们后面会用到文件是 developer.key 和 developer.crt

基于 RBAC 授权方式授予用户只读权限

在 k8s 集群中已经有个默认的名为 view 只读 ClusterRole 了,我们只需要将该 ClusterRole 绑定到 developer 用户即可:

1
kubectl create clusterrolebinding kubernetes-viewer --clusterrole=view --user=developer

基于客户端证书生成 Kubeconfig 文件

前面已经生成了客户端证书,并给证书里的用户赋予了集群只读权限,接下来基于客户端证书生成 Kubeconfig 文件:
拷贝一份 $HOME/.kube.config,假设名为 developer-config,在其基础上做修改:

  1. contexts 部分 user 字段改为 developer,name 字段改为 developer@kubernetes。(这些改动随意命名,只要前后统一即可);
  2. users 部分 name 字段改为 developer,client-certificate-data 字段改为 developer.crt base64 加密后的内容,client-key-data 改为 developer.key base64 加密后的内容;

    注意:证书 base64 加密时指定 –wrap=0 参数
    cat developer.crt | base64 –wrap=0
    cat developer.key | base64 –wrap=0

    接下来测试使用新建的 Kubeconfig 文件:

    [root@master ~]# kubectl –kubeconfig developer-config –context=developer@kubernetes get pod
    NAME READY STATUS RESTARTS AGE
    nginx-deployment-5754944d6c-dqsdj 1/1 Running 0 5d9h
    nginx-deployment-5754944d6c-q675s 1/1 Running 0 5d9h
    [root@master ~]# kubectl –kubeconfig developer-config –context=developer@kubernetes delete pod nginx-deployment-5754944d6c-dqsdj
    Error from server (Forbidden): pods “nginx-deployment-5754944d6c-dqsdj” is forbidden: User “developer” cannot delete resource “pods” in API group “” in the namespace “default”

可以看出新建的 Kubeconfig 文件可以使用,写权限是被 forbidden 的,说明前面配的 RBAC 权限机制是起作用的。

实践:Kubeconfig 或 token 方式登陆 Kubernetes dashboard

我们打开 kubernetes dashboard 访问地址首先看到的是登陆认证页面,有两种登陆认证方式可供选择:Kubeconfig 和 Token 方式

其实两种方式都需要服务账号的 Token,对于 Kubeconfig 方式直接使用集群默认的 Kubeconfig: $HOME/.kube/config 文件不能登陆,因为文件中缺少 Token 字段,所以直接选择本地的 Kubeconfig 文件登陆会报错。正确的使用方法是获取某个服务账号的 Token,然后将 Token 加入到 $HOME/.kube/config 文件。下面具体实践下两种登陆 dashboard 方式:

准备工作

首先,两种方式都需要服务账号,所以我们先创建一个服务账号,然后为了测试,给这个服务账号一个查看权限(RBAC 授权),到时候登陆 dashboard 后只能查看,不能对集群中的资源做修改。

  1. 创建一个服务账号(在 default 命名空间下);

    1
    kubectl create serviceaccount kube-dashboard-reader
  2. 将系统自带的 ClusterRole:view 角色绑定到上一步创建的服务账号,授予集群范围的资源只读权限;

    1
    kubectl create clusterrolebinding kube-dashboard-reader --clusterrole=view --serviceaccount=default:kube-dashboard-reader
  3. 获取服务账号的 token;

    1
    kubectl get secret `kubectl get secret -n default | grep kube-dashboard-reader | awk '{print $1}'` -o jsonpath={.data.token} -n default | base64 -d

Kubeconfig 方式登陆 dashboard

拷贝一份 $HOME/.kube/config,修改内容,将准备工作中获取的 Token 添加入到文件中:在 Kubeconfig 的 Users 下 User 部分添加,类型下面这样:

1
2
3
4
5
6
7
...
users:
- name: kubernetes-admin
user:
client-certificate-data: ...
client-key-data: ...
token: <这里为上面获取的 Token...>

然后登陆界面选择 Kubeconfig 单选框,选择该文件即可成功登陆 dashboard。

Token 方式登陆 dashboard

登陆界面选择 Token 单选框,将准备工作中获取的 Token 粘贴进去即可成功登陆。

相关文档

https://kubernetes.io/zh/docs/reference/access-authn-authz/controlling-access/ | Kubernetes API 访问控制
https://mp.weixin.qq.com/s/u4bGemn1cxbiZBoMX54sPA | 小白都能看懂的 Kubernetes安全之 API-server 安全
https://zhuanlan.zhihu.com/p/43237959 | 为 Kubernetes 集群添加用户
https://tonybai.com/2016/11/25/the-security-settings-for-kubernetes-cluster/ | Kubernetes 集群的安全配置
https://support.qacafe.com/knowledge-base/how-do-i-display-the-contents-of-a-ssl-certificate/ | x.509 证书内容查看

Kong 微服务网关在 Kubernetes 的实践

发表于 2019-08-17 | 分类于 微服务网关

本文主要介绍将 Kong 微服务网关作为 Kubernetes 集群统一入口的最佳实践,之前写过一篇文章使用 Nginx Ingress Controller 作为集群统一的流量入口:使用 Kubernetes Ingress 对外暴露服务,但是相比于 Kong Ingress Controller 来说,Kong 支持的功能更加强大,更适合微服务架构:

  • 拥有庞大的插件生态,能轻易扩展 Kong 支持的功能,比如 API 认证,流控,访问限制等;
  • Kong 服务本身和 Admin 管理 API 都集成在一个进程,通过端口区分两者,简化了部署的复杂度;
  • Kong 节点的配置统一持久化到数据库,所有节点通过数据库共享数据,在 Ingress 更新后能实时同步到各个节点,而 Nginx Ingress Controller 是通过重新加载机制响应 Ingress 更新,这种方式代价比较大,可能会导致服务的短暂中断;
  • Kong 有成熟的第三方管理 UI 和 Admin 管理 API 对接,从而能可视化管理 Kong 配置;

本文先介绍 Kong 微服务网关在 Kubernetes 的架构,然后进行架构实践,涉及到的话题有:

  • Kong 微服务网关在 Kubernetes 的架构;
  • helm 部署 Kong(包含 Kong Ingress Controller);
  • 部署 Konga;
  • 示例:通过 Konga 配置对外访问 Kubernetes Dashboard;

Kong 微服务网关在 Kubernetes 的架构

Kubernetes 简化了微服务架构,以 Service 为单位,代表一个个微服务,但是 k8s 集群整个网络对外是隔离的,在微服务架构下一般需要一个网关作为所有 API 的入口,在 k8s 中架构微服务同样也需要一个网关,作为集群统一的入口,作为服务消费方和提供方的交互中间件。Kong 可以充当这一网关角色,为集群提供统一的外部流量入口,集群内部 Service 之间通信通过 Service 名称调用:

那么 Kong 是如何在 k8s 集群上跑起来?具体机制是什么样的?
Kong 作为服务接入层,不仅提供了外部流量的接收转发,而且其本身还提供了 Admin 管理 API,通过 Admin 管理 API 实现 Kong 的路由转发等相关配置,这两项功能都是在一个进程中实现。

在 k8s 中 Kong 以 Pod 形式作为节点运行,Pod 通过 Deployment 或者 DaemenSet 管理。所有 Kong 节点共享一个数据库,因此通过 Admin API 配置,所有节点都能同步感知到变化。既然 Kong 以 Pod 的形式运行在 k8s 集群中,那么其本身需要对外暴露,这样外部流量才能进来,在本地可以 nodePort 或者 hostNetwork 对外提供服务,在云平台一般通过 LoadBalancer 实现。一般的部署最佳实践是将 Kong 的 Admin 管理功能独立出来一个 Pod,专门作为所有其他节点的统一配置管理,不对外提供流量转发服务,只提供配置功能,而其他 Kong 节点专门提供流量转发功能。

说一说 Kong Ingress Controller:其实没有 Kong Ingress Controller 也完全够用,其存在的意义是为了实现 k8s Ingress 资源对象。我们知道 Ingress 只不过是定义了一些流量路由规则,但是光有这个路由规则没用,需要 Ingress Controller 来将这些路由规则转化成相应代理的实际配置,比如 Kong Ingress Controller 就可以将 Ingress 转化成 Kong 的配置。与 Nginx Ingress Controller 不同,Kong Ingress Controller 不对外提供服务,只作为 k8s Ingress 资源的解析转化服务,将解析转化的结果(Kong 的配置:比如 Service、Route 实体等)通过与 Kong Admin API 写入 Kong 数据库,所以 Kong Ingress Controller 需要和 Kong Admin API 打通。所以当我们需要配置 Kong 的路由时,既可以通过创建 k8s Ingress 实现,也可以通过 Kong Admin API 直接配置。

helm 部署 Kong(包含 Kong Ingress Controller)

说明:本地集群部署,为了方便 Kong Proxy 和 Kong Admin 没有独立开,共用一个进程,同时提供流量转发和 Admin 管理 API。
使用 helm 官方 Chart: stable/kong,由于我是在本地裸机集群部署,很多云的功能不支持,比如:LoadBalancer、PV、PVC 等,所以需要对 Chart 的 values 文件做一些定制化以符合本地需求:

  • 由于本地裸机集群不支持 LoadBalancer,所以采用 nodePort 方式对外暴露 Kong proxy 和 Kong admin 服务,Chart 默认是 nodePort 方式,在这里自定义下端口:Kong proxy 指定 nodePort 80 和 443 端口,Kong Admin 指定 8001 端口:Values.proxy.http.nodePort: 80 Values.proxy.tls.nodePort: 443, Values.admin.nodePort: 8001;

    注意:默认 k8s nodePort 端口范围是 30000~32767,手动分配该范围之外的端口会报错!该限制可以调整,具体见之前文章:Kubernetes 调整 nodePort 端口范围

  • 启用 Kong admin 和 Kong proxy Ingress,部署时会创建相应的 Ingress 资源,实现服务对外访问:Values.admin.ingress.enabled: true, Values.proxy.ingress.enabled: true,另外还得设置对外访问的域名(没有域名的话可以随便起个域名,然后绑 /etc/hosts 访问):Values.admin.ingress.hosts: [admin.kong.com], Values.proxy.ingress.hosts: [proxy.kong.com];

  • 作为练习,为了方便,Kong admin 改用监听 HTTP 8001 端口:Values.admin.useTLS: false, .Values.admin.servicePort: 8001, .Values.admin.containerPort: 8001。另外也需要将 Pod 探针协议也改为 HTTP:Values.livenessProbe.httpGet.scheme: HTTP, Values.readinessProbe.httpGet.scheme: HTTP;

  • Kong proxy Ingress 启用 HTTPS,这样后续 kong 就可以同时支持 HTTP 和 HTTP 代理了,这里展开下具体过程:

    1. 创建 TLS 证书:域名为 proxy.kong.com

      1
      openssl req -x509 -nodes -days 65536 -newkey rsa:2048 -keyout proxy-kong.key -out proxy-kong.crt -subj "/CN=proxy.kong.com/O=proxy.kong.com"
    2. 使用生成的证书创建 k8s Secret 资源:

      1
      kubectl create secret tls proxy-kong-ssl --key proxy-kong.key --cert proxy-kong.crt -n kong

      编辑 values 文件启用 Kong Proxy Ingress tls,引用上面创建的 Secret:Values.proxy.ingress.tls:

      1
      2
      3
      - hosts:
      - proxy.kong.com
      secretName: proxy-kong-ssl
  • 启用 Kong Ingress Controller,默认是不会部署 Kong Ingress Controller:ingressController.enabled: true;

  • 由于本地裸机环境不支持 PV 存储,所以在部署时禁用 Postgres 数据持久化:helm 安装时指定 --set postgresql.persistence.enabled=false,这样 Postgres 存储会使用 emptyDir 方式挂载卷,在 Pod 重启后数据会丢失,本地自己玩的话可以先这么搞。当然要复杂点的话,可以自己再搭个 nfs 支持 PV 资源对象。

定制后的 values 文件在这里:https://raw.githubusercontent.com/qhh0205/helm-charts/master/kong-values.yml

helm 部署

1
helm install stable/kong --name kong --set postgresql.persistence.enabled=false -f https://raw.githubusercontent.com/qhh0205/helm-charts/master/kong-values.yml --namespace kong

验证部署

1
2
3
4
5
6
7
8
9
10
11
[root@master kong]# kubectl get pod -n kong                          
NAME READY STATUS RESTARTS AGE
kong-kong-controller-76d657b78-r6cj7 2/2 Running 1 58s
kong-kong-d889cf995-dw7kj 1/1 Running 0 58s
kong-kong-init-migrations-c6fml 0/1 Completed 0 58s
kong-postgresql-0 1/1 Running 0 58s

[root@master kong]# kubectl get ingress -n kong
NAME HOSTS ADDRESS PORTS AGE
kong-kong-admin admin.kong.com 80 84s
kong-kong-proxy prox.kong.com 80, 443 84s

curl 测试

1
2
3
4
5
6
7
[root@master kong]# curl -I http://admin.kong.com       
HTTP/1.1 200 OK
Content-Type: application/json
...

[root@master kong]# curl http://proxy.kong.com
{"message":"no Route matched with those values"}

部署 Konga

上面已经将整个 Kong 平台运行在了 Kubernetes 集群,并启用了 Kong Ingress Controller,但是目前做 Kong 相关的路由配置只能通过 curl 调 Kong Admin API,配置起来不是很方便。所以需要将针对 Kong 的 UI 管理服务 Konga 部署到集群,并和 Kong 打通,这样就可以可视化做 Kong 的配置了。由于 Konga 的部署很简单,官方也没有 Chart,所以我们通过 yaml 文件创建相关资源。

为了节省资源,Konga 和 Kong 共用一个 Postgresql,Konga 和 Kong 本身对数据库资源占用很少,所以两个同类服务共用一个数据库是完全合理的。下面为 k8s 资源文件,服务对外暴露方式为 Kong Ingress,域名设为(名字随便起的,绑 host 访问):konga.kong.com:

数据库密码在前面安装 Kong 时 Chart 创建的 Secret 中,获取方法:
kubectl get secret kong-postgresql -n kong -o yaml | grep password | awk -F ':' '{print $2}' | tr -d ' ' | base64 -d

konga.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: konga
name: konga
spec:
replicas: 1
selector:
matchLabels:
app: konga
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: konga
spec:
containers:
- env:
- name: DB_ADAPTER
value: postgres
- name: DB_URI
value: "postgresql://kong:K9IV9pHTdS@kong-postgresql:5432/konga_database"
image: pantsel/konga
imagePullPolicy: Always
name: konga
ports:
- containerPort: 1337
protocol: TCP
restartPolicy: Always

---
apiVersion: v1
kind: Service
metadata:
name: konga
spec:
ports:
- name: http
port: 1337
targetPort: 1337
protocol: TCP
selector:
app: konga

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: konga-ingress
spec:
rules:
- host: konga.kong.com
http:
paths:
- path: /
backend:
serviceName: konga
servicePort: 1337

kubectl 部署 Konga

1
kubectl create -f konga.yml -n kong

部署完成后绑定 host 将 konga.kong.com 指向集群节点 IP 即可访问:

接下来随便注册个账号,然后配置连接到 Kong Admin 地址,由于都在集群内部,所以直接用 Kong Admin 的 ServiceName + 端口号连就可以:


连接没问题后,主页面会显示 Kong 相关的全局信息:

示例:通过 Konga 配置对外访问 Kubernetes Dashboard

之前我们基于 Nginx Ingress Controller 对外暴露 Kubernetes Dashboard,现在我们基于集群中 Kong 平台配置对外访问,通过 Konga 可视化操作。
通过 Konga 配置服务对外访问只需要两步:

  1. 创建一个对应服务的 Service(不是 k8s 的 Servide,是 Kong 里面 Service 的概念:反向代理上游服务的抽象);
  2. 创建对应 Service 的路由;

下面以配置 Kubernetes dashboard 服务对外访问为例,对外域名设为 dashboard.kube.com (名字随便起的,绑 host 访问)

  1. 创建 Kong Service:

  2. 创建服务路由:



配置完成,浏览器测试访问:https://dashboard.kube.com

相关文档

https://konghq.com/solutions/kubernetes-ingress/ | Kong on Kubernetes
https://konghq.com/blog/kubernetes-ingress-controller-for-kong/ | Announcing the Kubernetes Ingress Controller for Kong
https://docs.konghq.com/install/kubernetes/ | Kong and Kong Enterprise on Kubernetes
https://github.com/Kong/kubernetes-ingress-controller | GitHub Kong Ingress Controller

Kubernetes 调整 nodePort 端口范围

发表于 2019-08-15 | 分类于 Kubernetes

默认情况下,k8s 集群 nodePort 分配的端口范围为:30000-32767,如果我们指定的端口不在这个范围就会报类似下面这样的错误:

Error: release kong failed: Service “kong-kong-admin” is invalid: spec.ports[0].nodePort: Invalid value: 8444: provided port is not in the valid range. The range of valid ports is 30000-32767

解决方法就是调整 kube-apiserver 组件启动参数,指定 nodePort 范围。如果是用 kubeadm 安装的集群,那么 apiserver 是以静态 pod 的形式运行,pod 文件定义在 /etc/kubernetes/manifests/kube-apiserver.yaml。/etc/kubernetes/manifests 目录下是所有静态 pod 文件的定义,kubelet 会监控该目录下文件的变动,只要发生变化,pod 就会重建,响应相应的改动。所以我们修改 /etc/kubernetes/manifests/kube-apiserver.yaml 文件,添加 nodePort 范围参数后会自动生效,无需进行其他操作:
vim /etc/kubernetes/manifests/kube-apiserver.yaml
在 command 下添加 --service-node-port-range=1-65535 参数,修改后会自动生效,无需其他操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --service-node-port-range=1-65535
- --advertise-address=192.168.26.10
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --enable-admission-plugins=NodeRestriction
- --enable-bootstrap-token-auth=true
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
- --etcd-servers=https://127.0.0.1:2379
- --insecure-port=0
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
- --requestheader-allowed-names=front-proxy-client
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --requestheader-extra-headers-prefix=X-Remote-Extra-
- --requestheader-group-headers=X-Remote-Group
- --requestheader-username-headers=X-Remote-User
- --secure-port=6443
- --service-account-key-file=/etc/kubernetes/pki/sa.pub
- --service-cluster-ip-range=10.96.0.0/12
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
image: registry.aliyuncs.com/google_containers/kube-apiserver:v1.15.2
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 8
httpGet:
host: 192.168.26.10
path: /healthz
port: 6443
scheme: HTTPS
initialDelaySeconds: 15
timeoutSeconds: 15
name: kube-apiserver
resources:
requests:
cpu: 250m
volumeMounts:
- mountPath: /etc/ssl/certs
name: ca-certs
readOnly: true
- mountPath: /etc/pki
name: etc-pki
readOnly: true
- mountPath: /etc/kubernetes/pki
name: k8s-certs
readOnly: true
hostNetwork: true
priorityClassName: system-cluster-critical
volumes:
- hostPath:
path: /etc/ssl/certs
type: DirectoryOrCreate
name: ca-certs
- hostPath:
path: /etc/pki
type: DirectoryOrCreate
name: etc-pki
- hostPath:
path: /etc/kubernetes/pki
type: DirectoryOrCreate
name: k8s-certs
status: {}

相关文档:
http://www.thinkcode.se/blog/2019/02/20/kubernetes-service-node-port-range

基于 docker-compose 容器化构建 Kong 微服务网关平台

发表于 2019-08-14 | 分类于 微服务网关

本文主要介绍如何使用 docker-compose 快速体验 Kong 微服务网关,先简单介绍基本概念,然后做了一个 Demo 测试使用,涉及到的相关话题有:

  • Kong 简介;
  • Konga 简介;
  • 基于 docker-compose 容器化构建 Kong 微服务网关平台;
  • 使用 Konga 可视化创建一个 Service 及路由;

Kong 简介

Kong 是微服务网关模式架构中连接服务消费方和服务提供方的中间件系统,即我们经常所说的微服务网关,网关将各自的业务系统的演进和发展做了天然的隔离,使业务系统更加专注于业务服务本身,同时微服务网关还可以为服务提供和沉淀更多附加功能。微服务网关的主要作用如下:

  • 请求接入:管理所有请求接入,作为所有 API 接口的请求入口;
  • 业务聚合:所有微服务后端可以注册在 API 网关,通过 API 网关统一暴露服务;
  • 拦截策略:可以通过统一的安全、路由、流控等公共服务组件;
  • 统一管理:提供统一的监控工具,配置管理工具等基础设施;

Kong 作为完全开源的微服务网关,基于 Nginx 实现,所以其性能表现是毋庸置疑的,另外和其他网关系统类似,也支持插件化扩展,提供了丰富的插件可供使用。Kong 进程启动后会启动多个端口,每个端口功能也不一样:

  • 8001 端口:http 管理 API;
  • 8444 端口:https 管理 API;
  • 8000 端口:接收处理 http 流量;
  • 8443 端口:接收处理 https 流量;

Kong 的使用特别简单,需要搞懂几个概念就可以快速使用了:

  • Service:Service 是要对外暴露的上游服务,类似于 Nginx 反向代理配置的 upstream;
  • Route:Route 定义了路由规则,外部流量如何路由到相应的 Service;
  • Consumer:类似账号的概念,可以设置不同的 Consumer 对 API 的访问限制;

关于 Kong 数据持久化
Kong 有两种运行模式,以 db-less 模式运行时所有路由、Service 等信息都存储在内存中,这些信息都是通过 declarative 配置文件动态生成,然后加在到内存。另一种是以 db 模式运行,需要额外的数据库支持,用于存储路由、Service 等信息,这种方式是生产环境推荐的方式。Kong 支持两种数据库持久化:Postgres 或者 Cassandra。

Konga 简介

Konga 是一个第三方开源的针对 Kong 网关的 UI 管理界面,与 Kong 没有关系。通过 Konga 我们可以可视化配置 Kong 相关的配置,在没有可视化界面的情况下只能通过 curl 调用 Kong 提供的 Admin API 来管理 Kong 配置,相对于可视化配置来说复杂度是显而易见的。Konga 支持的主要特性如下:

  • 管理所有 Kong Admin API 对象;
  • 多个 Kong 节点管理;
  • 使用快照备份、恢复 Kong 节点;
  • 通过健康检查健康节点和 API 状态;
  • 支持邮寄和 Slack 告警通知;
  • 多用户管理;
  • 支持数据库集成(MySQL,postgresSQL,MongoDB,SQL Server);

Konga 默认将用户信息和配置信息存在在本地磁盘文件,我们可以选择和数据库集成,将相关信息存储到数据库,这也是生产环境推荐的做法。

使用 docker-compose 容器化构建 Kong 微服务网关平台

使用 docker-compose 将 Kong 网关、Konga UI 管理页面、数据库三个服务组合起来,组成一个完整、可用的网关系统,Kong 网关和 Konga 服务共用一个 postgres 数据库。下面为启动整个系统完整的 docker-compose 文件,来自:https://gist.github.com/pantsel/73d949774bd8e917bfd3d9745d71febf 在其基础上对存在的问题进行了修复:
docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
version: "3"

networks:
kong-net:
driver: bridge

services:

#######################################
# Postgres: The database used by Kong
#######################################
kong-database:
image: postgres:9.6
restart: always
networks:
- kong-net
environment:
POSTGRES_USER: kong
POSTGRES_DB: kong
ports:
- "5432:5432"
healthcheck:
test: ["CMD", "pg_isready", "-U", "kong"]
interval: 5s
timeout: 5s
retries: 5

#######################################
# Kong database migration
#######################################
kong-migration:
image: kong:latest
command: "kong migrations bootstrap"
networks:
- kong-net
restart: on-failure
environment:
KONG_PG_HOST: kong-database
links:
- kong-database
depends_on:
- kong-database

#######################################
# Kong: The API Gateway
#######################################
kong:
image: kong:latest
restart: always
networks:
- kong-net
environment:
KONG_PG_HOST: kong-database
KONG_PROXY_LISTEN: 0.0.0.0:8000
KONG_PROXY_LISTEN_SSL: 0.0.0.0:8443
KONG_ADMIN_LISTEN: 0.0.0.0:8001
depends_on:
- kong-migration
- kong-database
healthcheck:
test: ["CMD", "curl", "-f", "http://kong:8001"]
interval: 5s
timeout: 2s
retries: 15
ports:
- "8001:8001"
- "8000:8000"
- "8443:8443"

#######################################
# Konga database prepare
#######################################
konga-prepare:
image: pantsel/konga:next
command: "-c prepare -a postgres -u postgresql://kong@kong-database:5432/konga_db"
networks:
- kong-net
restart: on-failure
links:
- kong-database
depends_on:
- kong-database

#######################################
# Konga: Kong GUI
#######################################
konga:
image: pantsel/konga:latest
restart: always
networks:
- kong-net
environment:
DB_ADAPTER: postgres
DB_HOST: kong-database
DB_USER: kong
TOKEN_SECRET: km1GUr4RkcQD7DewhJPNXrCuZwcKmqjb
DB_DATABASE: konga_db
NODE_ENV: production
depends_on:
- kong-database
ports:
- "1337:1337"

docker-compose 一键启动相关服务:

1
docker-compose up -d

启动后访问 http://nodeIP:1337 即可看到 Konga 登陆注册页面,首次访问需要注册用户,随便填写用户名称、邮箱等相关信息即可:

在登陆后页面比较简单,因为还没有连接到 Kong,下面配置连接到要管理的 Kong 节点:
Name:随便填写;
Kong Admin URL:填写 Kong Admin API 地址;


连接到 Kong 节点后即可看到节点更详细的信息:

使用 Konga 可视化创建一个 Service 及路由

Kong 官方文档给了一个 Kong 的使用 例子:通过 curl 调用 Kong Admin API 创建一个 Service 和路由,然后通过 curl 测试访问。这里我们演示如何通过 Konga 可视化做同样的配置:

首先创建一个 Service,指向 http://mockbin.org 服务:


创建一个对外访问的路由,路由到上一步创建的 Service:



上面配置中 Hosts 即为对外访问的 host 名称,只要将 api.example.com 绑定到网关 IP 地址即可进行访问,会路由到绑定的 Service:

1
curl http://api.example.com:8000

相关文档

https://docs.konghq.com/ | Kong 官方文档
https://github.com/pantsel/konga/ | Konga GitHub 仓库
https://gist.github.com/pantsel/73d949774bd8e917bfd3d9745d71febf | kong docker-compose

使用 Kubernetes Ingress 对外暴露服务

发表于 2019-08-12 | 分类于 Kubernetes

本文主要介绍如何通过 Kubernetes Ingress 资源对象实现从外部对 k8s 集群中服务的访问,介绍了 k8s 对外暴露服务的多种方法、Ingress 及 Ingress Controller 的概念。涉及到的话题有:

  • k8s 对外暴露服务的方法;
  • Ingress 及 Ingress Controller 简介;
  • helm 裸机部署 Nginx Ingress Controller;
  • 使用 Ingress 对外暴露服务;
  • 通过 Ingress 访问 kubernetes dashboard(支持 HTTPS 访问);

k8s 对外暴露服务的方法

向 k8s 集群外部暴露服务的方式有三种: nodePort,LoadBalancer 和本文要介绍的 Ingress。每种方式都有各自的优缺点,nodePort 方式在服务变多的情况下会导致节点要开的端口越来越多,不好管理。而 LoadBalancer 更适合结合云提供商的 LB 来使用,但是在 LB 越来越多的情况下对成本的花费也是不可小觑。Ingress 是 k8s 官方提供的用于对外暴露服务的方式,也是在生产环境用的比较多的方式,一般在云环境下是 LB + Ingress Ctroller 方式对外提供服务,这样就可以在一个 LB 的情况下根据域名路由到对应后端的 Service,有点类似于 Nginx 反向代理,只不过在 k8s 集群中,这个反向代理是集群外部流量的统一入口。

Ingress 及 Ingress Controller 简介

Ingress 是 k8s 资源对象,用于对外暴露服务,该资源对象定义了不同主机名(域名)及 URL 和对应后端 Service(k8s Service)的绑定,根据不同的路径路由 http 和 https 流量。而 Ingress Contoller 是一个 pod 服务,封装了一个 web 前端负载均衡器,同时在其基础上实现了动态感知 Ingress 并根据 Ingress 的定义动态生成 前端 web 负载均衡器的配置文件,比如 Nginx Ingress Controller 本质上就是一个 Nginx,只不过它能根据 Ingress 资源的定义动态生成 Nginx 的配置文件,然后动态 Reload。个人觉得 Ingress Controller 的重大作用是将前端负载均衡器和 Kubernetes 完美地结合了起来,一方面在云、容器平台下方便配置的管理,另一方面实现了集群统一的流量入口,而不是像 nodePort 那样给集群打多个孔。

所以,总的来说要使用 Ingress,得先部署 Ingress Controller 实体(相当于前端 Nginx),然后再创建 Ingress (相当于 Nginx 配置的 k8s 资源体现),Ingress Controller 部署好后会动态检测 Ingress 的创建情况生成相应配置。Ingress Controller 的实现有很多种:有基于 Nginx 的,也有基于 HAProxy的,还有基于 OpenResty 的 Kong Ingress Controller 等,更多 Controller 见:https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/,本文使用基于 Nginx 的 Ingress Controller:ingress-nginx。

helm 裸机部署 Nginx Ingress Controller

基于 Nginx 的 Ingress Controller 有两种,一种是 k8s 社区提供的 ingress-nginx,另一种是 Nginx 社区提供的 kubernetes-igress,关于两者的区别见 这里。

在这里我们部署 k8s 社区提供的 ingress-nginx,Ingress Controller 对外暴露方式采用 hostNetwork,在裸机环境下更多其他暴露方式见:https://kubernetes.github.io/ingress-nginx/deploy/baremetal/

使用 Helm 官方提供的 Chart stable/nginx-ingress,修改 values 文件:

  • 使用 DaemonSet 控制器,默认是 Deployment:controller.kind 设为 DaemonSet;
  • pod 使用主机网络:controller.hostNetwork 设为 true;
  • 在hostNetwork 下 pod 使用集群提供 dns 服务:controller.dnsPolicy 设为 ClusterFirstWithHostNet;
  • Service 类型设为 ClusterIP,默认是 LoadBalancer:controller.service.type 设为 ClusterIP;
  • 默认后端镜像使用 docker hub 提供的镜像,Google 国内无法访问;

修改后的 values 文件:https://raw.githubusercontent.com/qhh0205/helm-charts/master/nginx-ingress-values.yml
helm 部署

1
helm install stable/nginx-ingress --name nginx-ingress -f https://raw.githubusercontent.com/qhh0205/helm-charts/master/nginx-ingress-values.yml

验证部署是否成功

1
2
3
4
[root@master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-mg8df 1/1 Running 2 2m14s
nginx-ingress-default-backend-577857cd9c-gfsnd 1/1 Running 0 2m14s

浏览器访问节点 ip 出现:default backend - 404 页面,部署成功。

至此 Nginx Ingress Controller 已部署完成,接下来讲解如何通过 Ingress 结合 Ingress Controller 实现集群服务对外访问。

使用 Ingress 对外暴露服务

为了快速体验 Ingress,下面部署一个 nginx 服务,然后通过 Ingress 对外暴露 nginx service 进行访问。
首先部署 nginx 服务:
Deployment + Service:nginx.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80

kubectl create -f nginx.yml

接下来创建 Ingress 对外暴露 nginx service 80 端口:
ingress.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-nginx
annotations:
# use the shared ingress-nginx
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: nginx.kube.com
http:
paths:
- path: /
backend:
serviceName: nginx
servicePort: 80

说明:

  • kubernetes.io/ingress.class: "nginx":Nginx Ingress Controller 根据该注解自动发现 Ingress;
  • host: nginx.kube.com:对外访问的域名;
  • serviceName: nginx:对外暴露的 Service 名称;
  • servicePort: 80:nginx service 监听的端口;

注意:创建的 Ingress 必须要和对外暴露的 Service 在同一命名空间下!

将域名 nginx.kube.com 绑定到 k8s 任意节点 ip 即可访问:http://nginx.kube.com

上面的示例不支持 https 访问,下面举一个支持 https 的 Ingress 例子:通过 Ingress 访问 kubernetes dashboard 服务。

通过 Ingress 访问 kubernetes dashboard(支持 HTTPS 访问)

之前我们使用 helm 以 nodePort 的方式部署了 kubernetes dashboard:「helm 部署 kubernetes-dashboard」,从集群外部只能通过 nodeIP:nodePort 端口号 访问,接下来基于之前部署的 kubernetes-dashboard 配置如何通过 Ingress 访问,并且支持 HTTPS 访问,HTTP 自动跳转到 HTTPS。 :
首先,练习使用,先用自签名证书来代替吧:

1
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout kube-dashboard.key -out kube-dashboard.crt -subj "/CN=dashboard.kube.com/O=dashboard.kube.com"

使用生成的证书创建 k8s Secret 资源,下一步创建的 Ingress 会引用这个 Secret:

1
kubectl create secret tls kube-dasboard-ssl --key kube-dashboard.key --cert kube-dashboard.crt -n kube-system

创建 Ingress 资源对象(支持 HTTPS 访问):
kube-dashboard-ingress.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-kube-dashboard
annotations:
# use the shared ingress-nginx
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
tls:
- hosts:
- dashboard.kube.com
secretName: kube-dasboard-ssl
rules:
- host: dashboard.kube.com
http:
paths:
- path: /
backend:
serviceName: kubernetes-dashboard
servicePort: 443

kubectl create -f kube-dashboard-ingress.yml -n kube-system
说明:

  • kubernetes.io/ingress.class: "nginx":Inginx Ingress Controller 根据该注解自动发现 Ingress;
  • nginx.ingress.kubernetes.io/backend-protocol: Controller 向后端 Service 转发时使用 HTTPS 协议,这个注解必须添加,否则访问会报错,可以看到 Ingress Controller 报错日志:kubectl logs -f nginx-ingress-controller-mg8df

    2019/08/12 06:40:00 [error] 557#557: *56049 upstream sent no valid HTTP/1.0 header while reading response header from upstream, client: 192.168.26.10, server: dashboard.kube.com, request: “GET / HTTP/1.1”, upstream: “http://10.244.1.8:8443/“, host: “dashboard.kube.com”

    报错原因主要是 dashboard 服务后端只支持 https,但是 Ingress Controller 接到客户端的请求时往后端 dashboard 服务转发时使用的是 http 协议,解决办法就是给 创建的 Ingress 设置:nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" 注解。解决方法参考自 StackOverflow:https://stackoverflow.com/questions/48324760/ingress-configuration-for-dashboard

  • secretName: kube-dasboard-ssl:https 证书 Secret;
  • host: dashboard.kube.com:对外访问的域名;
  • serviceName: kubernetes-dashboard:集群对外暴露的 Service 名称;
  • servicePort: 443:service 监听的端口;

注意:创建的 Ingress 必须要和对外暴露的 Service 在同一命名空间下!

将域名 dashboard.kube.com 绑定到 k8s 任意节点 ip 即可访问:https://dashboard.kube.com

相关文档

https://kubernetes.io/docs/concepts/services-networking/ingress/ | 官方文档
https://mritd.me/2017/03/04/how-to-use-nginx-ingress/ | Kubernetes Nginx Ingress 教程
https://github.com/nginxinc/kubernetes-ingress | Inginx Ingress Controller:nginx 社区提供
https://github.com/kubernetes/ingress-nginx | Inginx Ingress Controller:k8s 社区提供
https://github.com/nginxinc/kubernetes-ingress/blob/master/docs/nginx-ingress-controllers.md | 两种基于 nginx 的 Ingress Controller 区别
https://kubernetes.github.io/ingress-nginx/deploy/ | Inginx Ingress Controller k8s 社区版安装文档
https://kubernetes.github.io/ingress-nginx/deploy/baremetal/ | 在 裸机环境下 Inginx Ingress Controller 对外暴露方案

kubectl 多集群访问配置

发表于 2019-08-09 | 分类于 Kubernetes

配置 KUBECONFIG 环境变量,是 kubectl 工具支持的变量,变量内容是冒号分隔的 kubernetes config 认证文件路径。假如我们有两个集群:A 和 B,A 集群的 config 文件为:$HOME/.kube/config,B 集群的 config 文件为:$HOME/.kube/config-local。要配置 kubectl 随时在两个集群间切换,只需要设置 KUBECONFIG 环境变量为:$HOME/.kube/config:$HOME/.kube/config-local

1
export KUBECONFIG=$HOME/.kube/config:$HOME/.kube/config-local

当进行上面配置后,使用 kubectl config view 查看 kubectl 配置时,结果为两个文件的合并。
当需要切换集群时,使用 kubectl config use-context <context 名称>

参考文档:
https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/

Mac OS 启用 ssh 远程登陆

发表于 2019-08-08 | 分类于 Mac

检查 ssh 远程登陆是否启用

1
sudo systemsetup -getremotelogin

启用 ssh 远程登陆

1
sudo systemsetup -setremotelogin on

启用后就可以用 ssh 来登陆 mac 系统了,账号和密码为系统的账号密码。

关闭 ssh 远程登陆

1
sudo systemsetup -f -setremotelogin off

1234…14

haohao

Talk is cheap. Show me the code.

134 日志
35 分类
43 标签
GitHub CSDN 开源中国 E-Mail
© 2017 — 2021 haohao
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.3
访问人数 总访问量 次