aiohttp框架及其常用模块

[TOC]

简介

aiohttp是一个为Python提供异步HTTP 客户端/服务端编程,基于asyncio的异步库。他的核心功能如下:

  • 同时支持客户端使用(可以理解为request的异步执行方式)和服务端使用。
  • 同时支持服务端WebSockets组件和客户端WebSockets组件,开箱即用呦。
  • web服务器具有中间件,信号组件和可插拔路由的功能。

这个可插拔的路由意思就是说,你可以在代码运行的过程中增加某个接口,或者减少某个接口。

安装方式:

1
pip install aiohttp

客户端例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import aiohttp
import asyncio
import async_timeout

async def fetch(session, url):
with async_timeout.timeout(10):
async with session.get(url) as response:
return await response.text()

async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://python.org')
print(html)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

服务端例子:

1
2
3
4
5
6
7
8
9
10
11
12
from aiohttp import web

async def handle(request):
name = request.match_info.get('name', "Anonymous")
text = "Hello, " + name
return web.Response(text=text)

app = web.Application()
app.router.add_get('/', handle)
app.router.add_get('/{name}', handle)

web.run_app(app)

常见扩展模块

模块名称 描述
aiohttp_session 处理用户会话
aiohttp-session-mongo
aiomysql
aiopg
aioredis
aiohttp_cors 解决跨域问题
aiojobs
aiohttp_jinja2
aiobotocore aws文件存储服务器的异步模块
pytest-aiohttp
aiohttp-swagger3
aioelasticsearch
aiologstash
aiokafka

更多可以去aio-libs github官方仓库查看 https://github.com/aio-libs

服务端使用

aiohttp的服务端程序都是 aiohttp.web.Application实例对象。用于创建信号,连接路由等。

使用下列代码可以创建一个应用程序:

1
2
3
4
5
6
7
8
9
10
11
12
from aiohttp import web

# 视图
async def index(request):
return web.Response(text='Hello Aiohttp!')

app = web.Application()

# 路由
app.router.add_get('/', index)

web.run_app(app, host='127.0.0.1', port=8080)

在浏览器中打开 http://127.0.0.1:8080 即可访问。

创建视图

不罗嗦直接上代码

1
2
3
4
from aiohttp import web

async def index(request):
return web.Response(text='Hello Aiohttp!')

index就是我们创建的展示页,然后我们创建个路由连接到这个展示页上。加上路由就可以用啦。

aiohttp.web提供django风格的基础试图类。

你可以从 View 类中继承,并自定义http请求的处理方法:

1
2
3
4
5
6
7
class MyView(web.View):
async def get(self):
return await get_resp(self.request)

async def post(self):
return await post_resp(self.request)

处理器应是一个协程方法,且只接受self参数,并且返回标准web处理器的响应对象。请求对象可使用View.request中取出。

当然还需要在路由中注册:

1
2
app.router.add_route('*', '/path/to', MyView)

这样/path/to既可使用GET也可使用POST进行请求,不过其他未指定的HTTP方法会抛出405错误。

处理器

处理器对象可以被任意调用,它只接受Request实例对象,并且返回StreamResponse的派生对象实例(如Response):

1
2
def handler(request):
return web.Response()

它还可以是协程方法,这样aiohttp.web会等待处理:

1
2
async def handler(request):
return web.Response()

处理器必须被注册在路由上才能使用:

1
2
app.router.add_get('/', handler)
app.router.add_post('/post', post_handler)

add_route()同样支持使用通配符方法:

1
app.router.add_route('*', '/path', all_handler)

之后可以使用Request.method来获知请求所使用的HTTP方法。

默认情况下,add_get()也会接受HEAD请求,并像使用GET请求一样返回响应头。你也可以禁用它:

1
app.router.add_get('/', handler, allow_head=False)

如果处理器不能被调用,服务器将会返回405。

模板

我们来添加些更有用的页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@aiohttp_jinja2.template('detail.html')
async def poll(request):
async with request['db'].acquire() as conn:
question_id = request.match_info['question_id']
try:
question, choices = await db.get_question(conn,
question_id)
except db.RecordNotFound as e:
raise web.HTTPNotFound(text=str(e))
return {
'question': question,
'choices': choices
}

编写页面时使用模板是很方便的。我们返回带有页面内容的字典,aiohttp_jinja2.template装饰器会用jinja2模板加载它。

配置文件

  1. 绝大部分服务器都有配置文件。
  2. 一般程序都不会将配置文件作为源码的一部分。
  3. 使用配置文件是公认的好方法,在部署产品时可以预防一些小错误。

配置文件建议:

  1. 将配置信息写在一个文件中。(yaml, json或ini都可以)
  2. 在一个预先设定好的目录中加载,就是有一个默认加载
  3. 拥有能通过命令行来设置配置文件的功能。如: ./run_app –config=/opt/config/app_cfg.json
  4. 对要加载的字典执行严格检测,以确保其数据类型符合预期。可以使用: trafaret, colander 或 JSON schema等库。

构建数据库

准备工作

PostgreSQL数据库为例

数据库架构

使用SQLAlchemy来写数据库架构。我们只要创建两个简单的模块——questionchoice:

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
import sqlalchemy as sa 

meta = sa.MetaData()

question - sq.Table(
'question', meta,
sa.Column('id', sa.Integer, nullable=False),
sa.Column('question_text', sa.String(200), nullable=False),
sa.Column('pub_date', sa.Date, nullable=False),
# Indexes #
sa.PrimaryKeyConstraint('id', name='question_id_pkey')
)

choice = sa.Table(
'choice', meta,
sa.Column('id', sa.Integer, nullable=False),
sa.Column('question_id', sa.Integer, nullable=False),
sa.Column('choice_text', sa.String(200), nullable=False),
sa.Column('votes', server_default="0", nullable=False),
# Indexes #
sa.PrimayKeyConstraint('id', name='choice_id_pkey'),
sa.ForeignKeyContraint(['question_id'], [question.c.id],
name='choice_question_id_fkey',
ondelete='CASCADE'),
)

你会看到如下数据库结构:

第一张表 question: |question| |id| |question_text| |pub_date|

第二张表 choice: |choice| |id| |choice_text| |votes| |question_id|

创建连接引擎

为了从数据库中查询数据我们需要一个引擎实例对象。假设conf变量是一个带有连接信息的配置字典,Postgres会使用异步的方式完成该操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
async def init_pg(app):
conf = app['config']
engine = await aiopg.sa.create_engine(
database=conf['database'],
user=conf['user'],
password=conf['password'],
host=conf['host'],
port=conf['host'],
minsize=conf['minsize'],
maxsize=conf['maxsize'])

app['db'] = engine

静态文件

每个web站点都有一些静态文件: 图片啦,JavaScript,CSS文件啦等等。 在生产环境中处理这些静态文件最好的方法是使用NGINX或CDN服务做反向代理。 但在开发环境中使用aiohttp服务器处理静态文件是很方便的。

只需要简单的调用一个信号即可:

1
2
3
4
app.router.add_static('/static/',
path=str(project_root/ 'static'),
name='static')

project_root表示项目根目录。

中间件

中间件是每个web处理器必不可少的组件。它的作用是在处理器处理请求前预处理请求以及在得到响应后发送出去。

aiohttp.web提供一个强有力的中间件组件来编写自定义请求处理器。

中间件是一个协程程序帮助修正请求和响应。下面这个例子是在响应中添加’wink’字符串:

1
2
3
4
5
6
7
8
from aiohttp.web import middleware

@middleware
async def middleware(request, handler):
resp = await handler(request)
resp.text = resp.text + ' wink'
return resp

WebSockets

aiohttp.web直接提供WebSockets支持。

在处理器中创建一个WebSocketResponse对象即可设置WebSocket,之后即可进行通信:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async def websocket_handler(request):

ws = web.WebSocketResponse()
await ws.prepare(request)

async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
if msg.data == 'close':
await ws.close()
else:
await ws.send_str(msg.data + '/answer')
elif msg.type == aiohttp.WSMsgType.ERROR:
print('ws connection closed with exception %s' %
ws.exception())

print('websocket connection closed')

return ws

websockets处理器需要用HTTP GET方法注册:

1
2
app.router.add_get('/ws', websocket_handler)

WebSocket中读取数据(await ws.receive())必须在请求处理器内部完成,不过写数据(ws.send_str(...)),关闭(await ws.close())和取消操作可以在其他任务中完成。

aiohttp.web 隐式地使用asyncio.Task处理每个请求。

异常

aiohttp.web定义了所有HTTP状态码的异常。

每个异常都是HTTPException的子类和某个HTTP状态码。 同样还都是Response的子类,所以就允许你在请求处理器中返回或抛出它们。 请看下面这些代码:

1
2
3
async def handler(request):
return aiohttp.web.HTTPFound('/redirect')

1
2
3
async def handler(request):
raise aiohttp.web.HTTPFound('/redirect')

每个异常的状态码是根据RFC 2068规定来确定的: 100-300不是由错误引起的; 400之后是客户端错误,500之后是服务器端错误。

异常等级图:

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
HTTPException
HTTPSuccessful
* 200 - HTTPOk
* 201 - HTTPCreated
* 202 - HTTPAccepted
* 203 - HTTPNonAuthoritativeInformation
* 204 - HTTPNoContent
* 205 - HTTPResetContent
* 206 - HTTPPartialContent
HTTPRedirection
* 300 - HTTPMultipleChoices
* 301 - HTTPMovedPermanently
* 302 - HTTPFound
* 303 - HTTPSeeOther
* 304 - HTTPNotModified
* 305 - HTTPUseProxy
* 307 - HTTPTemporaryRedirect
* 308 - HTTPPermanentRedirect
HTTPError
HTTPClientError
* 400 - HTTPBadRequest
* 401 - HTTPUnauthorized
* 402 - HTTPPaymentRequired
* 403 - HTTPForbidden
* 404 - HTTPNotFound
* 405 - HTTPMethodNotAllowed
* 406 - HTTPNotAcceptable
* 407 - HTTPProxyAuthenticationRequired
* 408 - HTTPRequestTimeout
* 409 - HTTPConflict
* 410 - HTTPGone
* 411 - HTTPLengthRequired
* 412 - HTTPPreconditionFailed
* 413 - HTTPRequestEntityTooLarge
* 414 - HTTPRequestURITooLong
* 415 - HTTPUnsupportedMediaType
* 416 - HTTPRequestRangeNotSatisfiable
* 417 - HTTPExpectationFailed
* 421 - HTTPMisdirectedRequest
* 422 - HTTPUnprocessableEntity
* 424 - HTTPFailedDependency
* 426 - HTTPUpgradeRequired
* 428 - HTTPPreconditionRequired
* 429 - HTTPTooManyRequests
* 431 - HTTPRequestHeaderFieldsTooLarge
* 451 - HTTPUnavailableForLegalReasons
HTTPServerError
* 500 - HTTPInternalServerError
* 501 - HTTPNotImplemented
* 502 - HTTPBadGateway
* 503 - HTTPServiceUnavailable
* 504 - HTTPGatewayTimeout
* 505 - HTTPVersionNotSupported
* 506 - HTTPVariantAlsoNegotiates
* 507 - HTTPInsufficientStorage
* 510 - HTTPNotExtended
* 511 - HTTPNetworkAuthenticationRequired

所有的异常都拥有相同的结构:

1
2
3
HTTPNotFound(*, headers=None, reason=None,
body=None, text=None, content_type=None)

如果没有指定headers,默认是响应中的headers。 其中HTTPMultipleChoices, HTTPMovedPermanently, HTTPFound, HTTPSeeOther, HTTPUseProxy, HTTPTemporaryRedirect的结构是下面这样的:

1
2
3
HTTPFound(location, *, headers=None, reason=None,
body=None, text=None, content_type=None)

location参数的值会写入到HTTP头部的Location中。

HTTPMethodNotAllowed的结构是这样的:

1
2
3
4
HTTPMethodNotAllowed(method, allowed_methods, *,
headers=None, reason=None,
body=None, text=None, content_type=None)

method是不支持的那个方法,allowed_methods是所支持的方法。

数据共享

将类全局变量存储到Application实例对象中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async def create_app(branch):
"""Create and return aiohttp webserver app."""
app = web.Application()

CONFIG_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "conf", f"ailawyer-{branch}.json")
with open(CONFIG_PATH, "r") as f:
CONFIG_DICT = json.loads(f.read())
print(CONFIG_DICT)

app['db'] = init_mongo(CONFIG_DICT)
app['redis_conn'] = await init_redis(CONFIG_DICT, app['db'])
app['alipay'] = init_alipay(CONFIG_DICT)
app['domain'] = CONFIG_DICT["alipay"]["domain"]
app['msg_sender'] = init_tencent_msg()
app['email_sender'] = init_tencent_email()
app['minio'] = connection.MinioAPI(CONFIG_DICT["minio"])

return app

之后就可以在web处理器中获取出来:

1
2
3
async def handler(request):
redis_conn = request.app['redis_conn']
...

如果变量的生命周期是一次请求,可以在请求中存储。

1
2
3
async def handler(request):
request['my_private_key'] = "data"
...

关闭退出

可能会有一些websockets或流,在服务器关闭时这些连接还是打开状态。 aiohttp没有内置如何进行关闭,但可以使用Applicaiton.on_shutdown信号来完善这一功能。 下面的代码是关闭websocket处理器的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app = web.Application()
app['websockets'] = []

async def websocket_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)

request.app['websockets'].append(ws)
try:
async for msg in ws:
...
finally:
request.app['websockets'].remove(ws)

return ws

日志

aiohttp使用标准库logging追踪库活动。

aiohttp中有以下日志记录器(以名字排序):

  • ‘aiohttp.access’
  • ‘aiohttp.client’
  • ‘aiohttp.internal’
  • ‘aiohttp.server’
  • ‘aiohttp.web’
  • ‘aiohttp.websocket’

你可以追踪这些记录器来查看日志信息。

默认情况下访问日志是开启的使用的是’aiohttp.access’记录器。
可以调用aiohttp.web.Applicaiton.make_handler()来控制日志。
将logging.Logger实例通过access_log参数传递即可覆盖默认记录器。

测试

aiohttp有一个pytest插件(pytest-aiohttp)可以轻松构建web服务器测试程序,同时该插件还有一个用于测试其他框架(单元测试等)的测试框架包。

服务器部署

常见的可以有两种

  • nginx + supervisord
  • nginx + gunicorn

将aiohttp服务器组运行在nginx之后有好多好处:

  • nginx是个很好的前端服务器。它可以预防很多攻击如格式错误的http协议的攻击
  • 部署nginx后可以同时运行多个aiohttp实例,这样可以有效利用CPU
  • nginx提供的静态文件服务器要比aiohttp内置的静态文件支持快很多

Nginx + supervisord

配置Nginx

下面是一份简短的配置Nginx参考,并没涉及到所有的Nginx选项。

你可以阅读Nginx指南官方文档来找到所有的参考。

好啦,首先我们要配置HTTP服务器本身:

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
http {
upstream aiohttp {
server 127.0.0.1:8081 fail_timeout=0;
server 127.0.0.1:8082 fail_timeout=0;
server 127.0.0.1:8083 fail_timeout=0;
server 127.0.0.1:8084 fail_timeout=0;
}

server {
listen 80;
client_max_body_size 4G;

server_name example.com;

location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_buffering off;
proxy_pass http://aiohttp;
}

location /static {
# path for static files
root /path/to/app/static;
}
}
}

这样配置之后会监听80端口,服务器名为example.com,所有的请求都会被重定向到aiohttp后端处理组。默认情况下,Nginx使用轮询调度算法(round-robin)来选择后端服务器。

Supervisord

配置完Nginx,我们要开始配置aiohttp后端服务器了。使用些工具可以在系统重启或后端出现错误时更快地自动启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[program:aiohttp]
numprocs = 4
numprocs_start = 1
process_name = example_%(process_num)s

; Unix socket paths are specified by command line.
command=/path/to/aiohttp_example.py --path=/tmp/example_%(process_num)s.sock

; We can just as easily pass TCP port numbers:
; command=/path/to/aiohttp_example.py --port=808%(process_num)s

user=nobody
autostart=true
autorestart=true

aiohtto服务器

最后我们要让aiohttp服务器在supervisord上工作。
假设我们已经正确配置aiohttp.web.Application,端口也被正确指定,这些工作挺烦的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# aiohttp_example.py
import argparse
from aiohttp import web

parser = argparse.ArgumentParser(description="aiohttp server example")
parser.add_argument('--path')
parser.add_argument('--port')


if __name__ == '__main__':
app = web.Application()
# configure app

args = parser.parse_args()
web.run_app(app, path=args.path, port=args.port)

当然在真实环境中我们还要做些其他事情,比如配置日志等等。

Nginx + Gunicorn

我们还可以使用Gunicorn来部署aiohttp,Gunicorn基于pre-fork worker模式。Gunicorn将你的app当做worker进程来处理即将到来的请求。
与部署Ngnix相反,使用Gunicorn不需要我们手动启动aiohttp进程,也不需要使用如supervisord之类的工具进行监控。

应用程序

我们写一个简单的应用程序,将其命名为 my_app_module.py:

1
2
3
4
5
6
7
8
9
from aiohttp import web

def index(request):
return web.Response(text="Welcome home!")


my_web_app = web.Application()
my_web_app.router.add_get('/', index)

启动Gunicorn

启动Gunicorn时我们要将模块名字(如my_app_module)和应用程序的名字(如my_web_app)传入,可以一起在配置Gunicorn其他选项时写入也可以写在配置文件中。
本章例子所使用到的选项:

  • -bind 用于设置服务器套接字地址。
  • -worker-class 表示使用我们自定义的worker代替Gunicorn的默认worker。
1
2
3
4
>> gunicorn my_app_module:my_web_app --bind localhost:8080 --worker-class aiohttp.GunicornWebWorker
... Starting gunicorn 19.3.0
... Listening at: http://127.0.0.1:8080

现在,Gunicorn已成功运行,随时可以将请求交由应用程序的worker处理。

参考:

aiortc

[TOC]

aiortc

aiortc是 WebRTC 和 ORTC 的Python异步实现。

install

1
2
3
apt install libavdevice-dev libavfilter-dev libopus-dev libvpx-dev pkg-config
# ffmpeg >= 3.2
pip install aiortc

example

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
from aiortc import RTCPeerConnection, RTCSessionDescription
from aiortc import VideoStreamTrack

async def offer(paramse):
# 解析 offer
request_offer = RTCSessionDescription(
sdp=params['sdp'],
type=params['type'])

# 创建一个连接class
pc = RTCPeerConnection()
pcs.add(pc)

# 监听数据通道
@pc.on("datachannel")
def on_datachannel(channel):

# 监听接收数据
@channel.on("message")
def on_message(message):
message_recieve = json.loads(message)
print("message_recieve:{message_recieve}")

# 监听断开连接
@pc.on('iceconnectionstatechange')
async def on_iceconnectionstatechange():
print(f'ICE connection state is {pc.iceConnectionState}')
if pc.iceConnectionState == 'failed':
await pc.close()
pcs.discard(pc)

@pc.on('track')
def on_track(track):
print('Track %s received' % track.kind)
if track.kind == 'audio':
# 接收语音数据
frame = await audio_track.recv()
pass
elif track.kind == 'video':
local_video = VideoTransformTrack(track)
pc.addTrack(local_video)

@track.on('ended')
async def on_ended():
print('Track %s ended' % track.kind)

# 设置 请求端 连接信息: request_offer
await pc.setRemoteDescription(request_offer)

# 设置 响应端 连接信息: answer
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
# 将 响应端 信息返回给 请求端: offer_result
offer_result = json.dumps({'sdp': pc.localDescription.sdp, 'type': pc.localDescription.type})
return offer_result

更多例子: https://github.com/aiortc/aiortc/tree/master/examples

webrtc

[TOC]

webrtc

WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准

官方教程

mozilla学习地址

重要API

WebRTC原生APIs文件是基于WebRTC规格书[21]撰写而成,这些API可分成Network Stream API、 RTCPeerConnection、Peer-to-peer Data API三类。

Network Stream API

  • MediaStream:MediaStream用来表示一个媒体数据流。
  • MediaStreamTrack在浏览器中表示一个媒体源。

RTCPeerConnection

  • RTCPeerConnection:一个RTCPeerConnection对象允许用户在两个浏览器之间直接通讯。
  • RTCIceCandidate:表示一个ICE协议的候选者。
  • RTCIceServer:表示一个ICE Server。

Peer-to-peer Data API

  • DataChannel:数据通道(DataChannel)接口表示一个在两个节点之间的双向的数据通道。

WebRTC协议

ICE

交互式连接建立Interactive Connectivity Establishment (ICE) 是一个允许你的浏览器和对端浏览器建立连接的协议框架。在实际的网络当中,有很多原因能导致简单的从A端到B端直连不能如愿完成。这需要绕过阻止建立连接的防火墙,给你的设备分配一个唯一可见的地址(通常情况下我们的大部分设备没有一个固定的公网地址),如果路由器不允许主机直连,还得通过一台服务器转发数据。ICE通过使用以下几种技术完成上述工作。

STUN

NAT的会话穿越功能Session Traversal Utilities for NAT (STUN) (缩略语的最后一个字母是NAT的首字母)是一个允许位于NAT后的客户端找出自己的公网地址,判断出路由器阻止直连的限制方法的协议。

客户端通过给公网的STUN服务器发送请求获得自己的公网地址信息,以及是否能够被(穿过路由器)访问。

webrtc-stun

NAT

网络地址转换协议Network Address Translation (NAT) 用来给你的(私网)设备映射一个公网的IP地址的协议。一般情况下,路由器的WAN口有一个公网IP,所有连接这个路由器LAN口的设备会分配一个私有网段的IP地址(例如192.168.1.3)。私网设备的IP被映射成路由器的公网IP和唯一的端口,通过这种方式不需要为每一个私网设备分配不同的公网IP,但是依然能被外网设备发现。

一些路由器严格地限定了部分私网设备的对外连接。这种情况下,即使STUN服务器识别了该私网设备的公网IP和端口的映射,依然无法和这个私网设备建立连接。这种情况下就需要转向TURN协议。

TURN

一些路由器使用一种“对称型NAT”的NAT模型。这意味着路由器只接受和对端先前建立的连接(就是下一次请求建立新的连接映射)。

NAT的中继穿越方式Traversal Using Relays around NAT (TURN) 通过TURN服务器中继所有数据的方式来绕过“对称型NAT”。你需要在TURN服务器上创建一个连接,然后告诉所有对端设备发包到服务器上,TURN服务器再把包转发给你。很显然这种方式是开销很大的,所以只有在没得选择的情况下采用。

webrtc-turn

SDP

会话描述协议Session Description Protocol (SDP) 是一个描述多媒体连接内容的协议,例如分辨率,格式,编码,加密算法等。所以在数据传输时两端都能够理解彼此的数据。本质上,这些描述内容的元数据并不是媒体流本身。

aiortc

aiortc是 WebRTC 和 ORTC 的Python异步实现。后面我会记录一些aiortc的基本使用。

turn server

install

1
sudo apt install coturn

configuration file

1
/etc/turnserver.conf

run

1
2
3
4
5
turnserver
# 这里有很多参数,比如
-o 后台运行
-S 只运行stun server
-h 查看帮助咯

wave librosa pyaudio

[TOC]

wave

wave 模块提供了一个处理 WAV 声音格式的便利接口。它不支持压缩/解压,但是支持单声道/立体声。

install

1
pip install wave

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 读取音频文件数据
def read_wav(audio_name):
with wave.open(audio_name, 'rb') as rf:
frames = []
frame = rf.readframes(3200)
while frame:
frames.append(frame)
frame = rf.readframes(3200)
return frames

# 保存音频文件数据
def write_wav(save_file, frames, CHANNELS=1, SIMPLE_SIZE=2, RATE=16000):
if save_file is not None:
wf = wave.open(save_file, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(SIMPLE_SIZE)
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

wave可以读取和保存音频文件,但是不能做时频处理、特征提取等问题,如果你读取rate=16000的文件,保存为rate=8000的文件,音频的时长增加了一倍,播放速度降低了一倍。

参考:https://docs.python.org/zh-cn/3/library/wave.html#module-wave

Librosa

Librosa 是一个用于音频、音乐分析、处理的python工具包,一些常见的时频处理、特征提取、绘制声音图形等功能应有尽有,功能十分强大。

install

1
2
3
4
pip install librosa

# conda install
conda install -c conda-forge librosa

example

1
2
3
4
5
# 改变频谱并保存
def change_sample_rate(read_file, save_file, orig_sr=48000, target_sr=8000):
y, sr = librosa.load(read_file, sr=orig_sr)
y_16k = librosa.resample(y, sr, target_sr)
librosa.output.write_wav(save_file, y_16k, target_sr)

参考: http://librosa.github.io/librosa/tutorial.html

pyaudio

pyaudio是一个可以读取麦克风和音频文件和播放音频的Python模块。

install

1
pip install pyaudio

example

wave读取音频文件,pyaudio实现播放音频

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
"""PyAudio Example: Play a wave file."""

import pyaudio
import wave
import sys

CHUNK = 1024

if len(sys.argv) < 2:
print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
sys.exit(-1)

wf = wave.open(sys.argv[1], 'rb')

# instantiate PyAudio (1)
p = pyaudio.PyAudio()

# open stream (2)
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)

# read data
data = wf.readframes(CHUNK)

# play stream (3)
while len(data) > 0:
stream.write(data)
data = wf.readframes(CHUNK)

# stop stream (4)
stream.stop_stream()
stream.close()

# close PyAudio (5)
p.terminate()

读取麦克风并通过阿里语音识别API实时识别。

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
class MyCallback(SpeechRecognizerCallback):

def __init__(self, name='default'):
self._name = name
self.completed = None
self.result = None

def on_started(self, message):
print('MyCallback.OnRecognitionStarted: %s' % message)

def on_result_changed(self, message):
self.result = message['payload']['result']
print(self.result)

def on_completed(self, message):
self.completed = {'status': message['header']['status'], 'file': self._name,
'task_id': message['header']['task_id'],
'result': message['payload']['result']}

def on_task_failed(self, message):
print('MyCallback.OnRecognitionTaskFailed: %s' % message)

def on_channel_closed(self):
print('MyCallback.OnRecognitionChannelClosed')

class Ali_Speech():
def __init__(self):
access_key_id = 'access_key_id'
access_key_secret = 'access_key_secret'
self.token, _ = ali_speech.NlsClient.create_token(access_key_id, access_key_secret)
self.client = ali_speech.NlsClient()
self.client.set_log_level('INFO')
self.callback = MyCallback()

self.CHUNK = 8092
self.FORMAT = 8
self.CHANNELS = 1
self.RATE = 16000

def start(self):
self.p = pyaudio.PyAudio()
self.stream = self.p.open(format=self.FORMAT, channels=self.CHANNELS,
rate=self.RATE, input=True, frames_per_buffer=self.CHUNK)

def stop(self):
self.stream.stop_stream()
self.stream.close()
self.p.terminate()

def ali_api(self, record_seconds=60, wave_save_path=None):
self.recognizer = self.client.create_recognizer(self.callback)
self.recognizer.set_appkey("set_appkey")
self.recognizer.set_token(self.token)
self.recognizer.set_format(ASRFormat.PCM)
self.recognizer.set_sample_rate(ASRSampleRate.SAMPLE_RATE_16K)
self.recognizer.set_enable_intermediate_result(True)
self.recognizer.set_enable_punctuation_prediction(True)
self.recognizer.set_enable_inverse_text_normalization(True)

RECORD_SECONDS = record_seconds
try:
ret = self.recognizer.start()
if ret < 0:
return ret
for i in range(0, int(self.RATE / self.CHUNK * RECORD_SECONDS)):
data = self.stream.read(self.CHUNK)
ret = self.recognizer.send(data)
if ret < 0:
break
self.recognizer.stop()
res = self.callback.completed
return res
except Exception as e:
print(str(e))
finally:
self.recognizer.close()

ffmepg

[TOC]

FFmpeg

ffmpeg也可以读取文件和从设备中读取视频信号,还可以从多个音视频文件中读取,然后输出多个音视频文件。

centos

1
2
3
4
5
6
yum install ffmpeg
# centos系统自带的ffmpeg一般都是 2.x的版本,很难满足现在很多需求,可以添加其他第三方源来安装,或者编译安装。
yum install yum-utils
yum-config-manager --add-repo https://negativo17.org/repos/epel-multimedia.repo
yum remove libva1-1.3.1-11.el7.x86_64
yum install ffmpeg

ubuntu

1
sudo apt install ffmpeg

conda

1
2
# conda安装的ffmpeg值存在于当前env环境中。
conda install ffmpeg

语法格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...

# 从视频文件读取
-i test.avi

# 从设备读取
-i /dev/video0

# 从视频流读取
-i rtsp://your_ip:port/

# 设置尺寸
-s 640*480

# 输出为文件,直接在后面写出文件名即可
ffmpeg -f video4linux -r 10 -i /dev/video0 test.asf

# 输出到视频流
ffmpeg -i /dev/video0 -f mpegts -codec:v mpeg1video http://localhost:8081/supersecret

ffmpeg功能强大,参数巨多。详情请看ffmpeg官方文档

常见实用命令

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
# 把图片合成转成视频
ffmpeg -y -r 24 -i rfcn/00%4d.JPEG -vcodec libx264 rfcn.mp4

# 把视频转成图片
ffmpeg -i 1.mp4 test/%6d.JPEG

# 格式转换
ffmpeg animals.avi animals.mp4

# 保留音频
ffmpeg -i animals.mp4 animals.mp3

# 合成音视频
ffmpeg -i animals.mp4 -i animals.mp3 animals_combine.mp4

$ cat file.list
file 'test1.mp4'
file 'test1.mp4'
file 'test1.mp4'
ffmpeg -f concat -i file.list -c copy test.mp4


# 截取视频 (从162秒开始截取30秒)
ffmpeg -ss 162 -t 30 -i animals_ori.mp4 face.mp4

FFmpeg + Opencv

ffmpeg还可以从管道pipe作为输入,或者输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# opencv获取视频,然后通过pipe管道传入ffmpeg的输入,再通过ts流输出到远程页面
# 中间可以加上预测结果,这样网页上显示的就是预测的结果
import subprocess
import cv2

video_capture = cv2.VideoCapture(0)
cmd = 'ffmpeg -f rawvideo -vcodec rawvideo -pixel_format bgr24 -video_size 640x480 -i pipe:0 -f mpegts -codec:v mpeg1video -s 512x512 -bf 0 http://localhost:8081/supersecret'.split()
converter = subprocess.Popen(cmd, stdin=subprocess.PIPE)
while True:
_, frame = video_capture.read()
# 在这里,可以对获取到的frame进行预测处理
# frame = predict(frame)
byte_frame = frame.tostring()
converter.stdin.write(byte_frame)
cv2.imshow("image", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break

opencv

[TOC]

opencv

opencv既可以从视频文件读取,也可以从视频设备上获取

install

1
pip install opencv-python

how to use

1
2
3
4
5
6
7
8
9
import cv2
# video_capture = cv2.VideoCapture("/home/alex/1.mp4")
# video_capture = cv.CaptureFromFile('rtsp://192.168.1.2:8080/out.h264')
video_capture = cv2.VideoCapture(0)
while True:
_, frame = video_capture.read()
cv2.imshow("image", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break

可以从本地文件,视频设备,或者远程视频流中获取视频信号。

1
2
3
# 查看本地视频设备号
$ ls /dev/video*
/dev/video0

常见实用示例

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
# 图片读写
def image_detector(self, imname, wait=0):
# 读取图片
image = cv2.imread(imname)

# 得到新的图片image
result = self.detect(image)
self.draw_result(image, result)

# 显示图片
# cv2.imshow('Image', image)
# cv2.waitKey(wait)

# 保存图片
cv2.imwrite(imname.replace(".jpg", "_result.jpg"), image)

# 写入视频
def camera_detector(self, cap, wait=10):
# 获取 原视频的fps,fourcc编码,height,wieght等信息
fps = int(cap.get(cv2.CAP_PROP_FPS))
# fourcc = int(cap.get(cv2.CAP_PROP_FOURCC))
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
weidth = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
print("fourcc: ", fourcc)

# 创建新的写入writer
videowriter = cv2.VideoWriter('test/test.avi', fourcc, fps, (weidth, height))

# 读取一帧图片
ret, frame = cap.read()

while frame is not None:
# 得到新的图片image
result = self.detect(frame)
self.draw_result(frame, result)
# cv2.imshow('Camera', frame)
# if cv2.waitKey(wait) & 0xFF == ord('q'):
# break

# 写入一帧图片
videowriter.write(frame)

# 读取下一帧图片
ret, frame = cap.read()

# 释放writer
videowriter.release()

# 调用 camera_detector
cap = cv2.VideoCapture(video_read)
detector.camera_detector(cap, wait=1)

opencv mac error

正常情况下,在mac上面使用opencv是没有问题的,但是在Python子进程中使用opencv会发现子进程的代码会无厘头的没有结果。参考 这里 ,不要问我怎么知道的 /(ㄒoㄒ)/~~

解决办法就是避开咯,例如:

1
2
3
4
5
6
7
8
9
10
# 转灰度图
gray_img = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
gray_img = np.dot(img_rgb[..., :3], [0.299, 0.587, 0.114])

# bgr 转 rgb
img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
img_rgb = image[:, :, ::-1]

# cv2.mean --> np.mean
# cv2.copyMakeBorder --> np.pad

k8s学习笔记-26-kubeasz+ansible部署集群

[TOC]

官方学习文档:https://github.com/easzlab/kubeasz

1、环境说明

IP 主机名 角色 虚拟机配置
192.168.56.11 k8s-master deploy、master1、lb1、etcd 4c4g
192.168.56.12 k8s-master2 master2、lb2 4c4g
192.168.56.13 k8s-node01 etcd、node 2c2g
192.168.56.14 k8s-node02 etcd、node 2c2g
192.168.56.110 vip
系统内核 3.10 docker版本 18.09
k8s版本 1.13 etcd版本 3.0

2、准备工作

  • 四台机器,全部执行:
1
2
3
yum install -y epel-release
yum update -y
yum install python -y
  • deploy节点安装ansible并配置密钥认证
1
2
3
yum install -y ansible
ssh-keygen
for ip in 11 12 13 14;do ssh-copy-id 192.168.56.$ip;done
  • deploy节点编排K8S
1
2
[root@k8s-master ~]# git clone https://github.com/gjmzj/kubeasz.git
[root@k8s-master ~]# mv kubeasz/* /etc/ansible/

可以根据自己所需版本,下载对应的tar包,这里我下载1.13
经过一番折腾,最终把k8s.1-13-5.tar.gz的tar包放到了depoly上

1
2
[root@k8s-master ~]# tar -zxf k8s.1-13-5.tar.gz 
[root@k8s-master ~]# mv bin/* /etc/ansible/bin/
  • 配置集群参数
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
[root@k8s-master ~]# cd /etc/ansible/
[root@k8s-master ansible]# cp example/hosts.m-masters.example hosts
cp: overwrite ‘hosts’? y
[root@k8s-master ansible]# vim hosts #根据实际情况的ip进行更改
[deploy]
192.168.56.11 NTP_ENABLED=no #设置集群是否安装 chrony 时间同步

[etcd] #etcd集群请提供如下NODE_NAME,注意etcd集群必须是1,3,5,7...奇数个节点
192.168.56.11 NODE_NAME=etcd1
192.168.56.13 NODE_NAME=etcd2
192.168.56.14 NODE_NAME=etcd3

[kube-master]
192.168.56.11
192.168.56.12

[kube-node]
192.168.56.13
192.168.56.14

[lb] # 负载均衡(目前已支持多于2节点,一般2节点就够了) 安装 haproxy+keepalived
192.168.56.12 LB_ROLE=backup
192.168.56.11 LB_ROLE=master

## 集群 MASTER IP即 LB节点VIP地址,为区别与默认apiserver端口,设置VIP监听的服务端口8443
# 公有云上请使用云负载均衡内网地址和监听端口
[all:vars]
DEPLOY_MODE=multi-master
MASTER_IP="192.168.56.110" #设置vip
KUBE_APISERVER="https://{{ MASTER_IP }}:8443"
CLUSTER_NETWORK="flannel"
SERVICE_CIDR="10.68.0.0/16"
CLUSTER_CIDR="172.20.0.0/16"
NODE_PORT_RANGE="20000-40000"
CLUSTER_KUBERNETES_SVC_IP="10.68.0.1"
CLUSTER_DNS_SVC_IP="10.68.0.2"
CLUSTER_DNS_DOMAIN="cluster.local."
bin_dir="/opt/kube/bin"
ca_dir="/etc/kubernetes/ssl"
base_dir="/etc/ansible"

#修改完成后,测试hosts
[root@k8s-master ansible]# ansible all -m ping
192.168.56.12 | SUCCESS => {
"changed": false,
"ping": "pong"
}
192.168.56.13 | SUCCESS => {
"changed": false,
"ping": "pong"
}
192.168.56.14 | SUCCESS => {
"changed": false,
"ping": "pong"
}
192.168.56.11 | SUCCESS => {
"changed": false,
"ping": "pong"
}

3、分步骤安装

3.1、创建证书和安装准备

1
[root@k8s-master ansible]# ansible-playbook 01.prepare.yml 

3.2、安装etcd集群

1
2
3
4
5
6
7
8
9
10
11
[root@k8s-master ansible]# ansible-playbook 02.etcd.yml
[root@k8s-master ansible]# bash

#验证etcd集群状态
[root@k8s-master ansible]# systemctl status etcd

#在任一 etcd 集群节点上执行如下命令
[root@k8s-master ansible]# for ip in 11 13 14;do ETCDCTL_API=3 etcdctl --endpoints=https://192.168.56.$ip:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem endpoint health;done
https://192.168.56.11:2379 is healthy: successfully committed proposal: took = 7.967375ms
https://192.168.56.13:2379 is healthy: successfully committed proposal: took = 12.557643ms
https://192.168.56.14:2379 is healthy: successfully committed proposal: took = 9.70078ms

3.3、安装docker

1
[root@k8s-master ansible]# ansible-playbook 03.docker.yml

3.4、安装master节点

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@k8s-master ansible]# ansible-playbook 04.kube-master.yml 

#查看进程状态
[root@k8s-master ansible]# systemctl status kube-apiserver
[root@k8s-master ansible]# systemctl status kube-controller-manager
[root@k8s-master ansible]# systemctl status kube-scheduler
[root@k8s-master ansible]# kubectl get componentstatus #查看集群状态
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-0 Healthy {"health":"true"}
etcd-1 Healthy {"health":"true"}
etcd-2 Healthy {"health":"true"}

3.5、安装node节点

1
2
3
4
5
6
7
8
9
[root@k8s-master ansible]# ansible-playbook 05.kube-node.yml
[root@k8s-master ansible]# systemctl status kubelet
[root@k8s-master ansible]# systemctl status kube-proxy
[root@k8s-master ansible]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
192.168.56.11 Ready,SchedulingDisabled master 6m56s v1.13.5
192.168.56.12 Ready,SchedulingDisabled master 6m57s v1.13.5
192.168.56.13 Ready node 40s v1.13.5
192.168.56.14 Ready node 40s v1.13.5

3.6、部署集群网络

1
2
3
4
5
6
7
[root@k8s-master ansible]# ansible-playbook 06.network.yml 
[root@k8s-master ansible]# kubectl get pod -n kube-system #查看flannel相关pod
NAME READY STATUS RESTARTS AGE
kube-flannel-ds-amd64-856rg 1/1 Running 0 115s
kube-flannel-ds-amd64-j4542 1/1 Running 0 115s
kube-flannel-ds-amd64-q9cmh 1/1 Running 0 115s
kube-flannel-ds-amd64-rhg66 1/1 Running 0 115s

3.7、部署集群插件(dns,dashboard)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master ansible]# ansible-playbook 07.cluster-addon.yml 

[root@k8s-master ansible]# kubectl get svc -n kube-system #查看服务
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
heapster ClusterIP 10.68.29.48 <none> 80/TCP 64s
kube-dns ClusterIP 10.68.0.2 <none> 53/UDP,53/TCP,9153/TCP 71s
kubernetes-dashboard NodePort 10.68.117.7 <none> 443:24190/TCP 64s
metrics-server ClusterIP 10.68.107.56 <none> 443/TCP 69s

[root@k8s-master ansible]# kubectl cluster-info #查看集群信息
Kubernetes master is running at https://192.168.56.110:8443
CoreDNS is running at https://192.168.56.110:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
kubernetes-dashboard is running at https://192.168.56.110:8443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

[root@k8s-master ansible]# kubectl top node #查看节点资源使用率
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
192.168.56.11 523m 13% 2345Mi 76%
192.168.56.12 582m 15% 1355Mi 44%
192.168.56.13 182m 10% 791Mi 70%
192.168.56.14 205m 11% 804Mi 71%

一步ansible安装k8s集群命令如下:

1
ansible-playbook 90.setup.yml

3.8、测试DNS解析

1
2
3
4
5
6
7
8
[root@k8s-master ansible]# kubectl run nginx --image=nginx --expose --port=80
[root@k8s-master ansible]# kubectl run busybox --rm -it --image=busybox /bin/sh
/ # nslookup nginx.default.svc.cluster.local
Server: 10.68.0.2
Address: 10.68.0.2:53

Name: nginx.default.svc.cluster.local
Address: 10.68.149.79

k8s学习笔记-25-Helm程序包管理器

[TOC]

1、Helm的概念和架构

每个成功的软件平台都有一个优秀的打包系统,比如 Debian、Ubuntu 的 apt,Redhat、Centos 的 yum。而 Helm 则是 Kubernetes 上的包管理器。

**思考??**Helm 到底解决了什么问题?为什么 Kubernetes 需要 Helm?

Kubernetes 能够很好地组织和编排容器,但它缺少一个更高层次的应用打包工具,而 Helm 就是来干这件事的。

举个例子,我们需要部署一个MySQL服务,Kubernetes则需要部署以下对象:

① 为了能够让外界访问到MySQL,需要部署一个mysql的service;

②需要进行定义MySQL的密码,则需要部署一个Secret;

③Mysql的运行需要持久化的数据存储,此时还需要部署PVC;

④保证后端mysql的运行,还需要部署一个Deployment,以支持以上的对象。

针对以上对象,我们可以使用YAML文件进行定义并部署,但是仅仅对于单个的服务支持,如果应用需要由一个甚至几十个这样的服务组成,并且还需要考虑各种服务的依赖问题,可想而知,这样的组织管理应用的方式就显得繁琐。为此就诞生了一个工具Helm,就是为了解决Kubernetes这种应用部署繁重的现象。

Helm的核心术语:

  • Chart:一个helm程序包,是创建一个应用的信息集合,包含各种Kubernetes对象的配置模板、参数定义、依赖关系、文档说明等。可以将Chart比喻为yum中的软件安装包;
  • Repository:Charts仓库,用于集中存储和分发Charts;
  • Config:应用程序实例化安装运行时所需要的配置信息;
  • Release:特定的Chart部署于目标集群上的一个实例,代表这一个正在运行的应用。当chart被安装到Kubernetes集群,就会生成一个release,chart可以多次安装到同一个集群,每次安装都是一个release。

Helm的程序架构:

Helm主要由Helm客户端、Tiller服务器和Charts仓库组成,如下图:

  • helm:客户端,GO语言编写,实现管理本地的Chart仓库,可管理Chart,与Tiller服务进行交互,用于发送Chart,实例安装、查询、卸载等操作。
  • Tiller:服务端,通常运行在K8S集群之上。用于接收helm发来的Charts和Conifg,合并生成release,完成部署。

简单的说:Helm 客户端负责管理 chart;Tiller 服务器负责管理 release。

2、部署Helm

helm部署文档

Helm的部署方式有两种:预编译的二进制程序和源码编译安装,这里使用二进制的方式进行安装

(1)下载helm

1
2
3
4
5
6
7
8
[root@k8s-master ~]# wget https://storage.googleapis.com/kubernetes-helm/helm-v2.9.1-linux-amd64.tar.gz --no-check-certificate
[root@k8s-master ~]# tar -xf helm-v2.9.1-linux-amd64.tar.gz
[root@k8s-master ~]# cd linux-amd64/
[root@k8s-master linux-amd64]# ls
helm LICENSE README.md
[root@k8s-master linux-amd64]# mv helm /usr/bin
[root@k8s-master linux-amd64]# helm version
Client: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}

(2)部署Tiller

helm第一次init时,需要链接api-server并进行认证,所以在运行helm时,会去读取kube-config文件,所以必须确认当前用户存在kube-config文件。

Tiller运行在K8s集群之上,也必须拥有集群的管理权限,也就是需要一个serviceaccount,进行一个clusterrolebinding到cluster-admin。

Tiller的RBAC配置示例链接:

https://github.com/helm/helm/blob/master/docs/rbac.md

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
#创建tiller的rbac清单
[root@k8s-master ~]# cd mainfests/
[root@k8s-master mainfests]# mkdir helm
[root@k8s-master mainfests]# cd helm/
[root@k8s-master helm]# vim tiller-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: tiller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: tiller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: tiller
namespace: kube-system

[root@k8s-master helm]# kubectl apply -f tiller-rbac.yaml
serviceaccount/tiller created
clusterrolebinding.rbac.authorization.k8s.io/tiller created
[root@k8s-master helm]# kubectl get sa -n kube-system |grep tiller
tiller 1 18s

#helm init命令进行初始化时,会用到gcr.io/kubernetes-helm中的景象,需要提前下载,镜像标签和Helm同版本号
[root@k8s-node01 ~]# docker pull jmgao1983/tiller:v2.9.1
v2.9.1: Pulling from jmgao1983/tiller
53969ec691ff: Pull complete
ea45de95cb26: Pull complete
495df31ed85a: Pull complete
Digest: sha256:417aae19a0709075df9cc87e2fcac599b39d8f73ac95e668d9627fec9d341af2
Status: Downloaded newer image for jmgao1983/tiller:v2.9.1
[root@k8s-node01 ~]# docker tag jmgao1983/tiller:v2.9.1 gcr.io/kubernetes-helm/tiller:v2.9.1

[root@k8s-master ~]# helm init
Creating /root/.helm
Creating /root/.helm/repository
Creating /root/.helm/repository/cache
Creating /root/.helm/repository/local
Creating /root/.helm/plugins
Creating /root/.helm/starters
Creating /root/.helm/cache/archive
Creating /root/.helm/repository/repositories.yaml
Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com
Adding local repo with URL: http://127.0.0.1:8879/charts
$HELM_HOME has been configured at /root/.helm.

Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.

Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!


[root@k8s-master ~]# kubectl get pods -n kube-system |grep tiller
tiller-deploy-759cb9df9-ls47p 1/1 Running 0 16m

#安装完成后,执行helm version可以看到客户端和服务端的版本号,两个都显示表示正常安装。
[root@k8s-master ~]# helm version
Client: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.9.1", GitCommit:"20adb27c7c5868466912eebdf6664e7390ebe710", GitTreeState:"clean"}

如果希望在安装时自定义一些参数,可以参考一下的一些参数:

  • –canary-image:安装canary分支,即项目的Master分支
  • –tiller-image:安装指定版本的镜像,默认和helm同版本
  • –kube-context:安装到指定的Kubernetes集群
  • –tiller-namespace:安装到指定的名称空间,默认为kube-system

Tiller将数据存储在ConfigMap资源当中,卸载或重装不会导致数据丢失,卸载Tiller的方法有以下两种:

1
2
(1)kubectl delete deployment tiller-deploy --n kube-system
(2)heml reset

3、helm的使用

官方可用的Chart列表:

https://hub.kubeapps.com

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
helm常用命令:
- helm search: 搜索charts
- helm fetch: 下载charts到本地目录
- helm install: 安装charts
- helm list: 列出charts的所有版本

用法:
helm [command]

命令可用选项:
completion 为指定的shell生成自动补全脚本(bash或zsh)
create 创建一个新的charts
delete 删除指定版本的release
dependency 管理charts的依赖
fetch 下载charts并解压到本地目录
get 下载一个release
history release历史信息
home 显示helm的家目录
init 在客户端和服务端初始化helm
inspect 查看charts的详细信息
install 安装charts
lint 检测包的存在问题
list 列出release
package 将chart目录进行打包
plugin add(增加), list(列出), or remove(移除) Helm 插件
repo add(增加), list(列出), remove(移除), update(更新), and index(索引) chart仓库
reset 卸载tiller
rollback release版本回滚
search 关键字搜索chart
serve 启动一个本地的http server
status 查看release状态信息
template 本地模板
test release测试
upgrade release更新
verify 验证chart的签名和有效期
version 打印客户端和服务端的版本信息

Charts是Helm的程序包,它们都存在在Charts仓库当中。Kubernetes官方的仓库保存了一系列的Charts,仓库默认的名称为stable。安装Charts到集群时,Helm首先会到官方仓库获取相关的Charts,并创建release。可执行 helm search 查看当前可安装的 chart 。

1
2
3
4
5
6
[root@k8s-master ~]# helm search
NAME CHART VERSION APP VERSION DESCRIPTION
stable/acs-engine-autoscaler 2.1.3 2.1.1 Scales worker nodes within agent pools
stable/aerospike 0.1.7 v3.14.1.2 A Helm chart for Aerospike in Kubernetes
stable/anchore-engine 0.1.3 0.1.6 Anchore container analysis and policy evaluatio...
......

这些 chart 都是从哪里来的?

前面说过,Helm 可以像 yum 管理软件包一样管理 chart。 yum 的软件包存放在仓库中,同样的,Helm 也有仓库。

1
2
3
4
[root@k8s-master ~]# helm repo list
NAME URL
local http://127.0.0.1:8879/charts
stable https://kubernetes-charts.storage.googleapis.com

Helm 安装时已经默认配置好了两个仓库:stablelocalstable 是官方仓库,local 是用户存放自己开发的chart的本地仓库。可以通过helm repo list进行查看。由于网络原因,国内无法更新仓库源,这里更改为阿里云的仓库源。

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
[root@k8s-master helm]# helm repo update 		#仓库更新有时会提示无法连接
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Unable to get an update from the "stable" chart repository (https://kubernetes-charts.storage.googleapis.com):
Get https://kubernetes-charts.storage.googleapis.com/index.yaml: dial tcp 216.58.220.208:443: connect: connection refused
Update Complete. ⎈ Happy Helming!⎈

[root@k8s-master helm]# helm repo list
NAME URL
stable https://kubernetes-charts.storage.googleapis.com
local http://127.0.0.1:8879/charts

[root@k8s-master helm]# helm repo remove stable #移除stable repo
"stable" has been removed from your repositories
[root@k8s-master helm]# helm repo list
NAME URL
local http://127.0.0.1:8879/charts

#增加阿里云的charts仓库
[root@k8s-master helm]# helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
"stable" has been added to your repositories
[root@k8s-master helm]# helm repo list
NAME URL
local http://127.0.0.1:8879/charts
stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
[root@k8s-master helm]# helm repo update #再次更新repo
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈

与 yum 一样,helm 也支持关键字搜索:

1
2
3
4
5
6
7
[root@k8s-master ~]# helm search mysql
NAME CHART VERSION APP VERSION DESCRIPTION
stable/mysql 0.3.5 Fast, reliable, scalable, and easy to use open-...
stable/percona 0.3.0 free, fully compatible, enhanced, open source d...
stable/percona-xtradb-cluster 0.0.2 5.7.19 free, fully compatible, enhanced, open source d...
stable/gcloud-sqlproxy 0.2.3 Google Cloud SQL Proxy
stable/mariadb 2.1.6 10.1.31 Fast, reliable, scalable, and easy to use open-...

包括 DESCRIPTION 在内的所有信息,只要跟关键字匹配,都会显示在结果列表中。

安装 chart 也很简单,执行如下命令可以安装 MySQL。

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
[root@k8s-master ~]# helm install stable/mysql
Error: no available release name found

#如果看到上面的报错,通常是因为 Tiller 服务器的权限不足。执行以下命令添加权限:
kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
kubectl patch deploy --namespace kube-system tiller-deploy -p '{"spec":{"template":{"spec":{"serviceAccount":"tiller"}}}}'


#helm安装mysql
[root@k8s-master helm]# helm install stable/mysql
NAME: reeling-bronco ①
LAST DEPLOYED: Wed Mar 27 03:10:31 2019
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES: ②
==> v1/Secret
NAME TYPE DATA AGE
reeling-bronco-mysql Opaque 2 0s

==> v1/PersistentVolumeClaim
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
reeling-bronco-mysql Pending 0s

==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
reeling-bronco-mysql ClusterIP 10.99.245.169 <none> 3306/TCP 0s

==> v1beta1/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
reeling-bronco-mysql 1 1 1 0 0s

==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
reeling-bronco-mysql-84b897b676-59qhh 0/1 Pending 0 0s


NOTES: ③
MySQL can be accessed via port 3306 on the following DNS name from within your cluster:
reeling-bronco-mysql.default.svc.cluster.local

To get your root password run:

MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace default reeling-bronco-mysql -o jsonpath="{.data.mysql-root-password}" | base64 --decode; echo)

To connect to your database:

1. Run an Ubuntu pod that you can use as a client:

kubectl run -i --tty ubuntu --image=ubuntu:16.04 --restart=Never -- bash -il

2. Install the mysql client:

$ apt-get update && apt-get install mysql-client -y

3. Connect using the mysql cli, then provide your password:
$ mysql -h reeling-bronco-mysql -p

To connect to your database directly from outside the K8s cluster:
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306

# Execute the following commands to route the connection:
export POD_NAME=$(kubectl get pods --namespace default -l "app=reeling-bronco-mysql" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward $POD_NAME 3306:3306

mysql -h ${MYSQL_HOST} -P${MYSQL_PORT} -u root -p${MYSQL_ROOT_PASSWORD}

输出分为三部分:

  • ① chart 本次部署的描述信息:

NAME 是 release 的名字,因为我们没用 -n 参数指定,Helm 随机生成了一个,这里是 reeling-bronco

NAMESPACE 是 release 部署的 namespace,默认是 default,也可以通过 --namespace 指定。

STATUSDEPLOYED,表示已经将 chart 部署到集群。

  • ② 当前 release 包含的资源:Service、Deployment、Secret 和 PersistentVolumeClaim,其名字都是 reeling-bronco-mysql,命名的格式为 ReleasName-ChartName
  • NOTES 部分显示的是 release 的使用方法。比如如何访问 Service,如何获取数据库密码,以及如何连接数据库等。

通过 kubectl get 可以查看组成 release 的各个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@k8s-master helm]# kubectl get service reeling-bronco-mysql
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
reeling-bronco-mysql ClusterIP 10.99.245.169 <none> 3306/TCP 3m

[root@k8s-master helm]# kubectl get deployment reeling-bronco-mysql
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
reeling-bronco-mysql 1 1 1 0 3m

[root@k8s-master helm]# kubectl get pvc reeling-bronco-mysql
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
reeling-bronco-mysql Pending 4m

[root@k8s-master helm]# kubectl get secret reeling-bronco-mysql
NAME TYPE DATA AGE
reeling-bronco-mysql Opaque 2 4m

由于我们还没有准备 PersistentVolume,当前 release 还不可用。

helm list 显示已经部署的 release,helm delete 可以删除 release。

1
2
3
4
5
6
[root@k8s-master helm]# helm list
NAME REVISION UPDATED STATUS CHART NAMESPACE
reeling-bronco 1 Wed Mar 27 03:10:31 2019 DEPLOYED mysql-0.3.5 default

[root@k8s-master helm]# helm delete reeling-bronco
release "reeling-bronco" deleted

4、chart 目录结构

chart 是 Helm 的应用打包格式。chart 由一系列文件组成,这些文件描述了 Kubernetes 部署应用时所需要的资源,比如 Service、Deployment、PersistentVolumeClaim、Secret、ConfigMap 等。

单个的 chart 可以非常简单,只用于部署一个服务,比如 Memcached;chart 也可以很复杂,部署整个应用,比如包含 HTTP Servers、 Database、消息中间件、cache 等。

chart 将这些文件放置在预定义的目录结构中,通常整个 chart 被打成 tar 包,而且标注上版本信息,便于 Helm 部署。

以前面 MySQL chart 为例。一旦安装了某个 chart,我们就可以在 ~/.helm/cache/archive 中找到 chart 的 tar 包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@k8s-master ~]# cd .helm/cache/archive/
[root@k8s-master archive]# ll
-rw-r--r-- 1 root root 5536 Oct 29 22:04 mysql-0.3.5.tgz
-rw-r--r-- 1 root root 6189 Oct 29 05:03 redis-1.1.15.tgz
[root@k8s-master archive]# tar -xf mysql-0.3.5.tgz
[root@k8s-master archive]# tree mysql
mysql
├── Chart.yaml
├── README.md
├── templates
│ ├── configmap.yaml
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── NOTES.txt
│ ├── pvc.yaml
│ ├── secrets.yaml
│ └── svc.yaml
└── values.yaml
  • Chart.yaml:YAML 文件,描述 chart 的概要信息。
  • README.md:Markdown 格式的 README 文件,相当于 chart 的使用文档,此文件为可选。
  • LICENSE:文本文件,描述 chart 的许可信息,此文件为可选。
  • requirements.yaml :chart 可能依赖其他的 chart,这些依赖关系可通过 requirements.yaml 指定。
  • values.yaml:chart 支持在安装的时根据参数进行定制化配置,而 values.yaml 则提供了这些配置参数的默认值。
  • templates目录:各类 Kubernetes 资源的配置模板都放置在这里。Helm 会将 values.yaml 中的参数值注入到模板中生成标准的 YAML 配置文件。
  • templates/NOTES.txt:chart 的简易使用文档,chart 安装成功后会显示此文档内容。 与模板一样,可以在 NOTE.txt 中插入配置参数,Helm 会动态注入参数值。

5、chart模板

Helm 通过模板创建 Kubernetes 能够理解的 YAML 格式的资源配置文件,我们将通过例子来学习如何使用模板。

templates/secrets.yaml 为例:

img

从结构上看,文件的内容和我们在定义Secret的配置上大致相似,只是大部分的属性值变成了。这些实际上是模板的语法。Helm采用了Go语言的模板来编写chart。

1
{{ template "mysql.fullname" . }}

定义 Secret 的 name

关键字 template 的作用是引用一个子模板 mysql.fullname。这个子模板是在 templates/_helpers.tpl 文件中定义的。

img

这个定义还是很复杂的,因为它用到了模板语言中的对象、函数、流控制等概念。现在看不懂没关系,这里我们学习的重点是:如果存在一些信息多个模板都会用到,则可在 templates/_helpers.tpl 中将其定义为子模板,然后通过 templates 函数引用。

这里 mysql.fullname 是由 release 与 chart 二者名字拼接组成。

根据 chart 的最佳实践,所有资源的名称都应该保持一致,对于我们这个 chart,无论 Secret 还是 Deployment、PersistentVolumeClaim、Service,它们的名字都是子模板 mysql.fullname 的值。

  • ChartRelease 是 Helm 预定义的对象,每个对象都有自己的属性,可以在模板中使用。如果使用下面命令安装 chart:
1
2
3
4
[root@k8s-master templates]# helm search stable/mysql
NAME CHART VERSION APP VERSION DESCRIPTION
stable/mysql 0.3.5 Fast, reliable, scalable, and easy to use open-...
[root@k8s-master templates]# helm install stable/mysql -n my

那么:

1
2
3
4
5
{{ .Chart.Name }}  的值为 mysql。
{{ .Chart.Version }} 的值为 0.3.5。
{{ .Release.Name }} 的值为 my。
{{ .Release.Service }} 始终取值为 Tiller。
{{ template "mysql.fullname" . }} 计算结果为 my-mysql。
  • ③ 这里指定 mysql-root-password 的值,不过使用了 if-else 的流控制,其逻辑为:

如果 .Values.mysqlRootPassword 有值,则对其进行 base64 编码;否则随机生成一个 10 位的字符串并编码。

Values 也是预定义的对象,代表的是 values.yaml 文件。而 .Values.mysqlRootPassword 则是 values.yaml 中定义的 mysqlRootPassword 参数:

img

因为 mysqlRootPassword 被注释掉了,没有赋值,所以逻辑判断会走 else,即随机生成密码。

randAlphaNumb64encquote 都是 Go 模板语言支持的函数,函数之间可以通过管道 | 连接。

1
{{ randAlphaNum 10 | b64enc | quote }}

的作用是首先随机产生一个长度为 10 的字符串,然后将其 base64 编码,最后两边加上双引号。

templates/secrets.yaml 这个例子展示了 chart 模板主要的功能,我们最大的收获应该是:模板将 chart 参数化了,通过 values.yaml 可以灵活定制应用。

无论多复杂的应用,用户都可以用 Go 模板语言编写出 chart。无非是使用到更多的函数、对象和流控制

6、定制安装MySQL chart

(1)chart安装准备

作为准备工作,安装之前需要先清楚 chart 的使用方法。这些信息通常记录在 values.yaml 和 README.md 中。除了下载源文件查看,执行 helm inspect values 可能是更方便的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@k8s-master ~]# helm inspect values stable/mysql
## mysql image version
## ref: https://hub.docker.com/r/library/mysql/tags/
##
image: "mysql"
imageTag: "5.7.14"

## Specify password for root user
##
## Default: random 10 character string
# mysqlRootPassword: testing

## Create a database user
##
# mysqlUser:
# mysqlPassword:

## Allow unauthenticated access, uncomment to enable
##
# mysqlAllowEmptyPassword: true
......

输出的实际上是 values.yaml 的内容。阅读注释就可以知道 MySQL chart 支持哪些参数,安装之前需要做哪些准备。其中有一部分是关于存储的:

1
2
3
4
5
6
7
8
9
10
11
12
13
## Persist data to a persistent volume
persistence:
enabled: true
## database data Persistent Volume Storage Class
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
# storageClass: "-"
accessMode: ReadWriteOnce
size: 8Gi

chart 定义了一个 PersistentVolumeClaim,申请 8G 的 PersistentVolume。由于我们的实验环境不支持动态供给,所以得预先创建好相应的 PV,其配置文件 mysql-pv.yml 内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@k8s-master volumes]# pwd
/root/mainfests/volumes
[root@k8s-master volumes]# cat mysql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv2
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 8Gi
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/volume/db
server: stor01

[root@k8s-master volumes]# kubectl apply -f mysql-pv.yaml
persistentvolume/mysql-pv2 created

[root@k8s-master volumes]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mysql-pv2 8Gi RWO Retain Available 5s

(2)定制化安装chart

除了接受 values.yaml 的默认值,我们还可以定制化 chart,比如设置 mysqlRootPassword

Helm 有两种方式传递配置参数:

  1. 指定自己的 values 文件。
    通常的做法是首先通过 helm inspect values mysql > myvalues.yaml生成 values 文件,然后设置 mysqlRootPassword,之后执行 helm install --values=myvalues.yaml mysql
  2. 通过 --set 直接传入参数值,比如:
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
[root@k8s-master ~]# helm install stable/mysql --set mysqlRootPassword=abc123 -n my
NAME: my
LAST DEPLOYED: Tue Oct 30 22:55:22 2018
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Secret
NAME TYPE DATA AGE
my-mysql Opaque 2 1s

==> v1/PersistentVolumeClaim
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-mysql Bound mysql-pv2 8Gi RWO 1s

==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-mysql ClusterIP 10.103.41.193 <none> 3306/TCP 1s

==> v1beta1/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-mysql 1 1 1 0 1s

==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
my-mysql-79b5d4fdcd-bj4mt 0/1 Pending 0 0s
......

mysqlRootPassword 设置为 abc123。另外,-n 设置 release 为 my,各类资源的名称即为my-mysql

通过 helm listhelm status 可以查看 chart 的最新状态。

(3)升级和回滚release

release 发布后可以执行 helm upgrade 对其升级,通过 --values--set应用新的配置。比如将当前的 MySQL 版本升级到 5.7.15:

1
2
3
4
5
6
7
[root@k8s-master ~]# helm upgrade --set imageTag=5.7.15 my stable/mysql
Release "my" has been upgraded. Happy Helming!
LAST DEPLOYED: Tue Oct 30 23:42:36 2018
......
[root@k8s-master ~]# kubectl get deployment my-mysql -o wide
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
my-mysql 1 1 1 0 11m my-mysql mysql:5.7.15 app=my-mysql

helm history 可以查看 release 所有的版本。通过 helm rollback 可以回滚到任何版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master ~]# helm history my
REVISION UPDATED STATUS CHART DESCRIPTION
1 Tue Oct 30 23:31:42 2018 SUPERSEDED mysql-0.3.5 Install complete
2 Tue Oct 30 23:42:36 2018 DEPLOYED mysql-0.3.5 Upgrade complete
[root@k8s-master ~]# helm rollback my 1
Rollback was a success! Happy Helming!
回滚成功,MySQL 恢复到 5.7.14。

[root@k8s-master ~]# helm history my
REVISION UPDATED STATUS CHART DESCRIPTION
1 Tue Oct 30 23:31:42 2018 SUPERSEDED mysql-0.3.5 Install complete
2 Tue Oct 30 23:42:36 2018 SUPERSEDED mysql-0.3.5 Upgrade complete
3 Tue Oct 30 23:44:28 2018 DEPLOYED mysql-0.3.5 Rollback to 1
[root@k8s-master ~]# kubectl get deployment my-mysql -o wide
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
my-mysql 1 1 1 1 13m my-mysql mysql:5.7.14 app=my-mysql

7、自定义chart

Kubernetes 给我们提供了大量官方 chart,不过要部署微服务应用,还是需要开发自己的 chart,下面就来实践这个主题。

(1)创建chart

执行 helm create mychart 的命令创建 chart mychart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k8s-master ~]# helm create -h
[root@k8s-master ~]# helm create mychart
Creating mychart
[root@k8s-master ~]# tree mychart/
mychart/
├── charts
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── ingress.yaml
│ ├── NOTES.txt
│ └── service.yaml
└── values.yaml

Helm 会帮我们创建目录 mychart,并生成了各类 chart 文件。这样我们就可以在此基础上开发自己的 chart 了。

(2)调试chart

只要是程序就会有 bug,chart 也不例外。Helm 提供了 debug 的工具:helm linthelm install --dry-run --debug

helm lint 会检测 chart 的语法,报告错误以及给出建议。 故意修改mychart中的value.yaml,进行检测:

helm lint mychart 会指出这个语法错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@k8s-master ~]# helm lint mychart
==> Linting mychart
[INFO] Chart.yaml: icon is recommended
[ERROR] values.yaml: unable to parse YAML
error converting YAML to JSON: yaml: line 11: could not find expected ':'

Error: 1 chart(s) linted, 1 chart(s) failed

mychart 目录被作为参数传递给 helm lint。错误修复后则能通过检测。

[root@k8s-master ~]# helm lint mychart
==> Linting mychart
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, no failures

helm install --dry-run --debug 会模拟安装 chart,并输出每个模板生成的 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
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
[root@k8s-master ~]# helm install --dry-run mychart --debug
[debug] Created tunnel using local port: '46807'

[debug] SERVER: "127.0.0.1:46807"

[debug] Original chart version: ""
[debug] CHART PATH: /root/mychart

NAME: sad-orangutan
REVISION: 1
RELEASED: Wed Oct 31 01:55:03 2018
CHART: mychart-0.1.0
USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
affinity: {}
image:
pullPolicy: IfNotPresent
repository: nginx
tag: stable
ingress:
annotations: {}
enabled: false
hosts:
- chart-example.local
path: /
tls: []
nodeSelector: {}
replicaCount: 1
resources: {}
service:
port: 80
type: ClusterIP
tolerations: []

HOOKS:
MANIFEST:

---
# Source: mychart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: sad-orangutan-mychart
labels:
app: mychart
chart: mychart-0.1.0
release: sad-orangutan
heritage: Tiller
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: mychart
release: sad-orangutan
---
# Source: mychart/templates/deployment.yaml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: sad-orangutan-mychart
labels:
app: mychart
chart: mychart-0.1.0
release: sad-orangutan
heritage: Tiller
spec:
replicas: 1
selector:
matchLabels:
app: mychart
release: sad-orangutan
template:
metadata:
labels:
app: mychart
release: sad-orangutan
spec:
containers:
- name: mychart
image: "nginx:stable"
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{}

我们可以检视这些输出,判断是否与预期相符。

(3)安装chart

安装 chart,Helm 支持四种安装方法:

  1. 安装仓库中的 chart,例如:helm install stable/nginx
  2. 通过 tar 包安装,例如:helm install ./nginx-1.2.3.tgz
  3. 通过 chart 本地目录安装,例如:helm install ./nginx
  4. 通过 URL 安装,例如:helm install https://example.com/charts/nginx-1.2.3.tgz

这里通过使用本地目录进行安装:

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
[root@k8s-master ~]# helm install mychart
NAME: anxious-wasp
LAST DEPLOYED: Wed Oct 31 01:57:15 2018
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Pod(related)
NAME READY STATUS RESTARTS AGE
anxious-wasp-mychart-94fcbf7d-dg5qn 0/1 ContainerCreating 0 0s

==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
anxious-wasp-mychart ClusterIP 10.111.51.71 <none> 80/TCP 0s

==> v1beta2/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
anxious-wasp-mychart 1 1 1 0 0s


NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l "app=mychart,release=anxious-wasp" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80

当 chart 部署到 Kubernetes 集群,便可以对其进行更为全面的测试。

(4)将chart添加到仓库

chart 通过测试后可以将其添加到仓库,团队其他成员就能够使用。任何 HTTP Server 都可以用作 chart 仓库,下面演示在 k8s-node1192.168.56.12 上搭建仓库。

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
(1)在 k8s-node1 上启动一个 httpd 容器。
[root@k8s-node01 ~]# mkdir /var/www
[root@k8s-node01 ~]# docker run -d -p 8080:80 -v /var/www/:/usr/local/apache2/htdocs/ httpd

(2)通过 helm package 将 mychart 打包。
[root@k8s-master ~]# helm package mychart
Successfully packaged chart and saved it to: /root/mychart-0.1.0.tgz

(3)执行 helm repo index 生成仓库的 index 文件
[root@k8s-master ~]# mkdir myrepo
[root@k8s-master ~]# mv mychart-0.1.0.tgz myrepo/
[root@k8s-master ~]#
[root@k8s-master ~]# helm repo index myrepo/ --url http://192.168.56.12:8080/charts
[root@k8s-master ~]# ls myrepo/
index.yaml mychart-0.1.0.tgz

Helm 会扫描 myrepo 目录中的所有 tgz 包并生成 index.yaml。--url指定的是新仓库的访问路径。新生成的 index.yaml 记录了当前仓库中所有 chart 的信息:
当前只有 mychart 这一个 chart。

[root@k8s-master ~]# cat myrepo/index.yaml
apiVersion: v1
entries:
mychart:
- apiVersion: v1
appVersion: "1.0"
created: 2018-10-31T02:02:45.599264611-04:00
description: A Helm chart for Kubernetes
digest: 08abeb3542e8a9ab90df776d3a646199da8be0ebfc5198ef032190938d49e30a
name: mychart
urls:
- http://192.168.56.12:8080/charts/mychart-0.1.0.tgz
version: 0.1.0
generated: 2018-10-31T02:02:45.598450525-04:00

(4)将 mychart-0.1.0.tgz 和 index.yaml 上传到 k8s-node1 的 /var/www/charts 目录。
[root@k8s-master myrepo]# scp ./* root@k8s-node01:/var/www/charts/
[root@k8s-node01 ~]# ls /var/www/charts/
index.yaml mychart-0.1.0.tgz

(5)通过 helm repo add 将新仓库添加到 Helm。
[root@k8s-master ~]# helm repo add newrepo http://192.168.56.12:8080/charts
"newrepo" has been added to your repositories
[root@k8s-master ~]# helm repo list
NAME URL
local http://127.0.0.1:8879/charts
stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
newrepo http://192.168.56.12:8080/charts

(6)现在已经可以 repo search 到 mychart 了。
[root@k8s-master ~]# helm search mychart
NAME CHART VERSION APP VERSION DESCRIPTION
local/mychart 0.1.0 1.0 A Helm chart for Kubernetes
newrepo/mychart 0.1.0 1.0 A Helm chart for Kubernetes

除了 newrepo/mychart,这里还有一个 local/mychart。这是因为在执行第 2 步打包操作的同时,mychart 也被同步到了 local 的仓库。

(7)已经可以直接从新仓库安装 mychart 了。
[root@k8s-master ~]# helm install newrepo/mychart

(8)如果以后仓库添加了新的 chart,需要用 helm repo update 更新本地的 index。
[root@k8s-master ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "newrepo" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈ Happy Helming!⎈

这个操作相当于 Centos 的 yum update。

8、总结

  • Helm是Kubernetes的包管理器,Helm 让我们能够像 yum 管理 rpm 包那样安装、部署、升级和删除容器化应用。
  • Helm 由客户端和 Tiller 服务器组成。客户端负责管理 chart,服务器负责管理 release。
  • chart 是 Helm 的应用打包格式,它由一组文件和目录构成。其中最重要的是模板,模板中定义了 Kubernetes 各类资源的配置信息,Helm 在部署时通过 values.yaml 实例化模板。
  • Helm 允许用户开发自己的 chart,并为用户提供了调试工具。用户可以搭建自己的 chart 仓库,在团队中共享 chart。

k8s学习笔记-24-Prometheus监控

[TOC]

1、Prometheus概述

除了前面的资源指标(如CPU、内存)以外,用户或管理员需要了解更多的指标数据,比如Kubernetes指标、容器指标、节点资源指标以及应用程序指标等等。自定义指标API允许请求任意的指标,其指标API的实现要指定相应的后端监视系统。而Prometheus是第一个开发了相应适配器的监控系统。这个适用于PrometheusKubernetes Customm Metrics Adapter是属于Github上的k8s-prometheus-adapter项目提供的。其原理图如下:

img

要知道的是prometheus本身就是一监控系统,也分为server端和agent端,server端从被监控主机获取数据,而agent端需要部署一个node_exporter,主要用于数据采集和暴露节点的数据,那么 在获取Pod级别或者是mysql等多种应用的数据,也是需要部署相关的exporter。我们可以通过PromQL的方式对数据进行查询,但是由于本身prometheus属于第三方的 解决方案,原生的k8s系统并不能对Prometheus的自定义指标进行解析,就需要借助于k8s-prometheus-adapter将这些指标数据查询接口转换为标准的Kubernetes自定义指标。

Prometheus是一个开源的服务监控系统和时序数据库,其提供了通用的数据模型和快捷数据采集、存储和查询接口。它的核心组件Prometheus服务器定期从静态配置的监控目标或者基于服务发现自动配置的目标中进行拉取数据,新拉取到的数据大于配置的内存缓存区时,数据就会持久化到存储设备当中。Prometheus组件架构图如下:
img

如上图,每个被监控的主机都可以通过专用的exporter程序提供输出监控数据的接口,并等待Prometheus服务器周期性的进行数据抓取。如果存在告警规则,则抓取到数据之后会根据规则进行计算,满足告警条件则会生成告警,并发送到Alertmanager完成告警的汇总和分发。当被监控的目标有主动推送数据的需求时,可以以Pushgateway组件进行接收并临时存储数据,然后等待Prometheus服务器完成数据的采集。

任何被监控的目标都需要事先纳入到监控系统中才能进行时序数据采集、存储、告警和展示,监控目标可以通过配置信息以静态形式指定,也可以让Prometheus通过服务发现的机制进行动态管理。下面是组件的一些解析:

  • 监控代理程序:如node_exporter:收集主机的指标数据,如平均负载、CPU、内存、磁盘、网络等等多个维度的指标数据。
  • kubelet(cAdvisor):收集容器指标数据,也是K8S的核心指标收集,每个容器的相关指标数据包括:CPU使用率、限额、文件系统读写限额、内存使用率和限额、网络报文发送、接收、丢弃速率等等。
  • API Server:收集API Server的性能指标数据,包括控制队列的性能、请求速率和延迟时长等等
  • etcd:收集etcd存储集群的相关指标数据
  • kube-state-metrics:该组件可以派生出k8s相关的多个指标数据,主要是资源类型相关的计数器和元数据信息,包括制定类型的对象总数、资源限额、容器状态以及Pod资源标签系列等。

Prometheus 能够直接把 Kubernetes API Server作为服务发现系统使用进而动态发现和监控集群中的所有可被监控的对象。这里需要特别说明的是,Pod 资源需要添加下列注解信息才能被Prometheus 系统自动发现并抓取其内建的指标数据。

  • 1) prometheus. io/ scrape: 用于 标识 是否 需要 被 采集 指标 数据, 布尔 型 值, true 或 false。
  • 2) prometheus. io/ path: 抓取 指标 数据 时 使用 的 URL 路径, 一般 为/ metrics。
  • 3) prometheus. io/ port: 抓取 指标 数据 时 使 用的 套 接 字 端口, 如 8080。

另外, 仅 期望 Prometheus 为 后端 生成 自定义 指标 时 仅 部署 Prometheus 服务器 即可, 它 甚至 也不 需要 数据 持久 功能。 但 若要 配置 完整 功能 的 监控 系统, 管理员 还需 要在 每个 主机 上 部署 node_exporter、 按需部署其他特有类型的 exporter 以及Alertmanager。

2、Prometheus部署

由于官方的YAML部署方式需要使用到PVC,这里使用马哥提供的学习类型的部署,具体生产还是需要根据官方的建议进行。本次部署的YAML

2.1、创建名称空间prom

1
2
3
[root@k8s-master ~]# git clone https://github.com/iKubernetes/k8s-prom.git && cd k8s-prom
[root@k8s-master k8s-prom]# kubectl apply -f namespace.yaml
namespace/prom created

2.2、部署node_exporter

1
2
3
4
5
6
7
8
9
[root@k8s-master k8s-prom]# kubectl apply -f node_exporter/
daemonset.apps/prometheus-node-exporter created
service/prometheus-node-exporter created

[root@k8s-master k8s-prom]# kubectl get pods -n prom
NAME READY STATUS RESTARTS AGE
prometheus-node-exporter-6srrq 1/1 Running 0 32s
prometheus-node-exporter-fftmc 1/1 Running 0 32s
prometheus-node-exporter-qlr8d 1/1 Running 0 32s

2.3、部署prometheus-server

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
[root@k8s-master k8s-prom]# kubectl apply -f prometheus/
configmap/prometheus-config unchanged
deployment.apps/prometheus-server configured
clusterrole.rbac.authorization.k8s.io/prometheus configured
serviceaccount/prometheus unchanged
clusterrolebinding.rbac.authorization.k8s.io/prometheus configured
service/prometheus unchanged


[root@k8s-master k8s-prom]# kubectl get all -n prom
NAME READY STATUS RESTARTS AGE
pod/prometheus-node-exporter-6srrq 1/1 Running 0 11m
pod/prometheus-node-exporter-fftmc 1/1 Running 0 11m
pod/prometheus-node-exporter-qlr8d 1/1 Running 0 11m
pod/prometheus-server-66cbd4c6b-j9lqr 1/1 Running 0 4m

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/prometheus NodePort 10.96.65.72 <none> 9090:30090/TCP 10m
service/prometheus-node-exporter ClusterIP None <none> 9100/TCP 11m

NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/prometheus-node-exporter 3 3 3 3 3 <none> 11m

NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/prometheus-server 1 1 1 1 10m

NAME DESIRED CURRENT READY AGE
replicaset.apps/prometheus-server-65f5d59585 0 0 0 10m
replicaset.apps/prometheus-server-66cbd4c6b 1 1 1 4m

2.4、部署kube-sate-metrics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k8s-master k8s-prom]# kubectl apply -f kube-state-metrics/
deployment.apps/kube-state-metrics created
serviceaccount/kube-state-metrics created
clusterrole.rbac.authorization.k8s.io/kube-state-metrics created
clusterrolebinding.rbac.authorization.k8s.io/kube-state-metrics created
service/kube-state-metrics created

[root@k8s-master k8s-prom]# kubectl get pods -n prom -o wide
NAME READY STATUS RESTARTS AGE IP NODE
kube-state-metrics-78fc9fc745-g66p8 1/1 Running 0 11m 10.244.1.22 k8s-node01
prometheus-node-exporter-6srrq 1/1 Running 0 31m 192.168.56.11 k8s-master
prometheus-node-exporter-fftmc 1/1 Running 0 31m 192.168.56.12 k8s-node01
prometheus-node-exporter-qlr8d 1/1 Running 0 31m 192.168.56.13 k8s-node02
prometheus-server-66cbd4c6b-j9lqr 1/1 Running 0 24m 10.244.0.4 k8s-master

2.5、制作证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master pki]# (umask 077; openssl genrsa -out serving.key 2048)
Generating RSA private key, 2048 bit long modulus
......................+++
....+++
e is 65537 (0x10001)
[root@k8s-master pki]# openssl req -new -key serving.key -out serving.csr -subj "/CN=serving"
[root@k8s-master pki]# openssl x509 -req -in serving.csr -CA ./ca.crt -CAkey ./ca.key -CAcreateserial -out serving.crt -days 3650
Signature ok
subject=/CN=serving
Getting CA Private Key

[root@k8s-master pki]# kubectl create secret generic cm-adapter-serving-certs --from-file=serving.crt=./serving.crt --from-file=serving.key -n prom
secret/cm-adapter-serving-certs created

[root@k8s-master pki]# kubectl get secret -n prom
NAME TYPE DATA AGE
cm-adapter-serving-certs Opaque 2 20s

2.6、部署k8s-prometheus-adapter

这里自带的custom-metrics-apiserver-deployment.yaml和custom-metrics-config-map.yaml有点问题,需要下载k8s-prometheus-adapter项目中的这2个文件

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
[root@k8s-master k8s-prometheus-adapter]# wget https://raw.githubusercontent.com/DirectXMan12/k8s-prometheus-adapter/master/deploy/manifests/custom-metrics-apiserver-deployment.yaml

[root@k8s-master k8s-prometheus-adapter]# vim k8s-prometheus-adapter/custom-metrics-apiserver-deployment.yaml #修改名称空间为prom

[root@k8s-master k8s-prometheus-adapter]# wget https://raw.githubusercontent.com/DirectXMan12/k8s-prometheus-adapter/master/deploy/manifests/custom-metrics-config-map.yaml #也需要修改名称空间为prom

[root@k8s-master k8s-prom]# kubectl apply -f k8s-prometheus-adapter/
clusterrolebinding.rbac.authorization.k8s.io/custom-metrics:system:auth-delegator created
rolebinding.rbac.authorization.k8s.io/custom-metrics-auth-reader created
deployment.apps/custom-metrics-apiserver created
clusterrolebinding.rbac.authorization.k8s.io/custom-metrics-resource-reader created
serviceaccount/custom-metrics-apiserver created
service/custom-metrics-apiserver created
apiservice.apiregistration.k8s.io/v1beta1.custom.metrics.k8s.io created
clusterrole.rbac.authorization.k8s.io/custom-metrics-server-resources created
clusterrole.rbac.authorization.k8s.io/custom-metrics-resource-reader created
clusterrolebinding.rbac.authorization.k8s.io/hpa-controller-custom-metrics created
configmap/adapter-config created

[root@k8s-master k8s-prom]# kubectl get pods -n prom
NAME READY STATUS RESTARTS AGE
custom-metrics-apiserver-65f545496-l5md9 1/1 Running 0 7m
kube-state-metrics-78fc9fc745-g66p8 1/1 Running 0 40m
prometheus-node-exporter-6srrq 1/1 Running 0 1h
prometheus-node-exporter-fftmc 1/1 Running 0 1h
prometheus-node-exporter-qlr8d 1/1 Running 0 1h
prometheus-server-66cbd4c6b-j9lqr 1/1 Running 0 53m

[root@k8s-master k8s-prom]# kubectl api-versions |grep custom
custom.metrics.k8s.io/v1beta1

[root@k8s-master ~]# kubectl get svc -n prom
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
custom-metrics-apiserver ClusterIP 10.99.14.141 <none> 443/TCP 11h
kube-state-metrics ClusterIP 10.107.23.237 <none> 8080/TCP 11h
prometheus NodePort 10.96.65.72 <none> 9090:30090/TCP 11h
prometheus-node-exporter ClusterIP None <none> 9100/TCP 11h

访问192.168.56.11:30090,如下图:选择 需要查看的指标,点击Execute
img

3、Grafana数据展示

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
[root@k8s-master k8s-prom]# cat grafana.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
name: monitoring-grafana
namespace: prom #修改名称空间
spec:
replicas: 1
selector:
matchLabels:
task: monitoring
k8s-app: grafana
template:
metadata:
labels:
task: monitoring
k8s-app: grafana
spec:
containers:
- name: grafana
image: registry.cn-hangzhou.aliyuncs.com/google_containers/heapster-grafana-amd64:v5.0.4
ports:
- containerPort: 3000
protocol: TCP
volumeMounts:
- mountPath: /etc/ssl/certs
name: ca-certificates
readOnly: true
- mountPath: /var
name: grafana-storage
env: #这里使用的是原先的heapster的grafana的配置文件,需要注释掉这个环境变量
#- name: INFLUXDB_HOST
# value: monitoring-influxdb
- name: GF_SERVER_HTTP_PORT
value: "3000"
# The following env variables are required to make Grafana accessible via
# the kubernetes api-server proxy. On production clusters, we recommend
# removing these env variables, setup auth for grafana, and expose the grafana
# service using a LoadBalancer or a public IP.
- name: GF_AUTH_BASIC_ENABLED
value: "false"
- name: GF_AUTH_ANONYMOUS_ENABLED
value: "true"
- name: GF_AUTH_ANONYMOUS_ORG_ROLE
value: Admin
- name: GF_SERVER_ROOT_URL
# If you're only using the API Server proxy, set this value instead:
# value: /api/v1/namespaces/kube-system/services/monitoring-grafana/proxy
value: /
volumes:
- name: ca-certificates
hostPath:
path: /etc/ssl/certs
- name: grafana-storage
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
labels:
# For use as a Cluster add-on (https://github.com/kubernetes/kubernetes/tree/master/cluster/addons)
# If you are NOT using this as an addon, you should comment out this line.
kubernetes.io/cluster-service: 'true'
kubernetes.io/name: monitoring-grafana
name: monitoring-grafana
namespace: prom
spec:
# In a production setup, we recommend accessing Grafana through an external Loadbalancer
# or through a public IP.
# type: LoadBalancer
# You could also use NodePort to expose the service at a randomly-generated port
type: NodePort
ports:
- port: 80
targetPort: 3000
selector:
k8s-app: grafana

[root@k8s-master k8s-prom]# kubectl apply -f grafana.yaml
deployment.apps/monitoring-grafana created
service/monitoring-grafana created

[root@k8s-master k8s-prom]# kubectl get pods -n prom
NAME READY STATUS RESTARTS AGE
custom-metrics-apiserver-65f545496-l5md9 1/1 Running 0 16m
kube-state-metrics-78fc9fc745-g66p8 1/1 Running 0 49m
monitoring-grafana-7c94886cd5-dhcqz 1/1 Running 0 36s
prometheus-node-exporter-6srrq 1/1 Running 0 1h
prometheus-node-exporter-fftmc 1/1 Running 0 1h
prometheus-node-exporter-qlr8d 1/1 Running 0 1h
prometheus-server-66cbd4c6b-j9lqr 1/1 Running 0 1h

[root@k8s-master k8s-prom]# kubectl get svc -n prom
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
custom-metrics-apiserver ClusterIP 10.99.14.141 <none> 443/TCP 11h
kube-state-metrics ClusterIP 10.107.23.237 <none> 8080/TCP 11h
monitoring-grafana NodePort 10.98.174.125 <none> 80:30582/TCP 10h
prometheus NodePort 10.96.65.72 <none> 9090:30090/TCP 11h
prometheus-node-exporter ClusterIP None <none> 9100/TCP 11h

访问grafana的地址:192.168.56.11:30582,默认是没有kubernetes的模板的,可以到grafana.com中去下载相关的kubernetes模板。