kubernetes技术介绍

Kubernetes技术介绍

白话容器(Docker)

容器VS虚拟机

虚拟机

  • 硬件虚拟化
  • 操作系统
  • 应用

容器

  • 共享操作系统
  • 应用

容器本质: 一组资源被限制和隔离的进程

限制与隔离

Linux限制技术

  • Linux Cgroup

Linux Cgroups 的全称是 Linux Control Group。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。

Linux隔离技术

  • PID Namespace
  • Mount Namespace
  • Net Namespace
  • IPC Namespace
  • User Namespace
  • UTS Namespace

Namespace 技术实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容。

容器 Tech Demo

#define _GNU_SOURCE
#include <fcntl.h>
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    int pid_ns_fd;
    int net_ns_fd;
    int mnt_ns_fd;

    pid_ns_fd = open(argv[1], O_RDONLY);
    net_ns_fd = open(argv[2], O_RDONLY);
    mnt_ns_fd = open(argv[3], O_RDONLY);
    //printf("%s %s %s", argv[1], argv[2], argv[3]);

    // 设置pid namespace
    if (setns(pid_ns_fd, CLONE_NEWPID) < 0)
    {
        printf("set pid namespace error");
        return -1;
    }
    // 设置network namespace
    if (setns(net_ns_fd, CLONE_NEWNET) < 0)
    {
        printf("set ns namespace error");
        return -1;
    }
    // 设置mount namespace
    if (setns(mnt_ns_fd, CLONE_NEWNS) < 0)
    {
        printf("set ns namespace error");
        return -1;
    }
    // 执行命令
    pid_t pid = fork();
    if (pid < 0)
    {
        printf("fork error");
        return -1;
    }
    else if (pid == 0)
    {
        return execvp(argv[4], &argv[4]);
    }
    else
    {
        return waitpid(pid, NULL, 0);
    }
}
sudo ./setns /proc/11773/ns/pid /proc/11773/ns/net /proc/11773/ns/mnt bash

容器网络 overview

容器网络 veth pair

veth-pair 就是一对的虚拟设备接口,它都是成对出现的。一端连着协议栈,一端彼此相连着。

容器网络实践

  • 单机容器网络连通

容器到容器:通过docker0,直接互通
容器到宿主机/宿主机到容器:本地路由

  • 跨节点容器网络连通

一个简单不自动化方案:设置节点容器网段,在节点上配置路由

白话Kubernetes

  • Kubernetes架构
  • Kubernetes资源概念和使用
  • Kubernetes扩展

Kubernetes架构

Kubernetes资源概念和使用

资源类型

  • Nodes
  • Pods
  • Namespaces
  • Depolyments
  • Daemon Sets
  • Jobs
  • ConfigMaps
  • Secrets
  • ServiceAccounts
  • Services
  • more…

管理方式

kubectl (create|get|apply|delete) -f myResource.yaml

Kubernetes扩展

  • CRD
  • Operator模式

Kubernetes实践

Kubernetes部署

资源准备

  • Etcd
  • 机器

节点要求

  • 机器配置
  • 容器运行时
  • 内核参数
  • 网络设置

复杂点

  • 组件证书!!!
  • 组件参数

高可用

  • Etcd高可用
  • Master节点(Control plane)高可用

Kubernetes部署实践

Etcd

  • 动态平台etcd服务

master节点

  • kube-api-server
  • kube-controller-manager
  • kube-scheduler

node节点

  • kubelet
  • kube-proxy

Ansible demo

Kubernetes网络

Kubernetes网络约定

约定

  • Every pod gets its own IP address.
  • Containers within a pod share the pod IP address and can communicate freely with each other.
  • Pods can communicate with all other pods in the cluster using pod IP addresses (without NAT).
  • Isolation (restricting what each pod can communicate with) is defined using network policies.

网络实现和CNI(Container Network Interface)

Kubernetes组件中并没有实现约定的网络,而是制定了网络接口规范,将具体实现留给了插件。
简单来说,kubernetes组件中kubelet实现了cni插件接口,在创建Pod过程中调用插件,为Pod配置网络。

Kubernetes网络之overlay网络

Kubernetes网络约定思考

Kernernetes网络约定中"Pod到Pod的网络使用Pod IP无需Nat,即使跨节点",这定义了Cluster Pod网络是一个扁平的网络。
这样扁平的网络指什么呢?

Overlay

图中所示以vxlan实现举例基于四层(UDP),实现一个虚拟的二层网络。

Kubernetes网络插件之calico

回顾上文容器网络章节中,我们设计了一个方案“规划容器网段,配置路由”可实现容器跨节点网络,值得注意的是,这个简单方式所实现的网络是满足kubernetes的一个网络,容器之间通过IP互通,无需NAT。同时注意,这个网络No Overlay!。

calico

Calico主要由Felix、etcd、BGP client、BGP Route Reflector组成。

  • Etcd:负责存储网络信息
  • BGP client:负责将Felix配置的路由信息分发到其他节点
  • Felix:Calico Agent,每个节点都需要运行,主要负责配置路由、配置ACLs、报告状态
  • BGP Route Reflector:大规模部署时需要用到,作为BGP client的中心连接点,可以避免每个节点互联
阅读全文

eBPF开发

eBPF开发

libbpf-bootstrap

git clone git@github.com:libbpf/libbpf-bootstrap.git
git submodule update --init --recursive

开发环境

# ubuntu 20.04
sudo apt install clang clang-12
sudo apt install linux-image-5.8.0-63-generic linux-headers-5.8.0-63-generic linux-modules-extra-5.8.0-63-generic
sudo apt install libelf-dev pkg-config
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-10 1 --slave /usr/bin/clang++ clang++ /usr/bin/clang++-10
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-12 2 --slave /usr/bin/clang++ clang++ /usr/bin/clang++-12
sudo update-alternatives --install /usr/bin/llvm-strip llvm-strip /usr/bin/llvm-strip-10 1
sudo update-alternatives --install /usr/bin/llvm-strip llvm-strip /usr/bin/llvm-strip-12 2

相关链接

阅读全文

什么是eBPF

什么是eBPF

从Linux Tracing说起

在我们做程序调试和性能分析时候经常需要跟踪(Tracing)程序执行过程,收集执行信息,综合收集到的信息分析程序问题。基于这样的需求,Linux生态便出现了各种各样的工具,我们在编程开发中或多或少已经接触到了这样的工具,比如GDB、perf,甚至自己代码中的Printf也算工具之一。概括来讲,Tracing工具是实现“采集目标程序执行信息目的”的程序。

那么实现Tracing,需要解决什么问题呢?

  1. 数据源在哪里。
  2. 采集方式。

以GDB为例,我们知道GDB调试,需要开发人员编译加特定参数,编译器会在编译结果程序中加入特定Debug信息。这个加参数暴露Debug信息是一个下探针的过程,探针是Tracing数据的来源。然后我执行GDB命令,单步执行、输出状态信息,这是采集和使用Tracing数据的过程。

先加探针,然后采集调试的过程,在线上程序出性能问题的时候,很难实现,而且很多程序也是到线上场景才暴露性能问题的,能不能随时加探针,随时跟踪目标程序呢?操作系统作为应用程序的执行环境,可以拥有上帝视角,借助内核加探针使我们拥有随时加探针的超能力!

Linux Tracing技术不断发展,诞生了各种各样的工具,形成了一个Tracing系统生态。

在Linux内核中已经有了各种各样的探针,按照形态分为:

  • 硬件探针,硬件直接暴露信息,比如CPU cycles数。
  • 静态探针,重新编译内核或者通过加载内核模块形式下的探针,代表技术: Tracepoint。
  • 动态探针,可以动态在内核函数上添加Hook的探针机制,代表技术Kprobe。

简单深入下静态探针和动态探针,假设我们需要Hook一个内核函数,静态探针需要我们改内核代码,在目标函数前后加入我们采集数据的代码,动态探针则是我们动态指定对应函数和Hook脚本就可以了。相比静态探针需要重新编译内核添加新探针,动态探针灵活几乎可以Hook所有内核函数。

基于内核各种各样的探针,诞生了各种工具、库、框架(Kprobe,Tracepoint,LTTng,SystemTap,eBPF等),这些工具、库底层共用内核的探针技术,用户实现一个Tracing目的,可以选择使用不同的工具、库。

为什么是eBPF

eBPF并不是一个新的Tracing探针技术,而是新的Tracing框架技术,以更安全、更高效、更灵活的方式,方便用户实现Linux Tracing!

采用eBPF可以对接几乎已有的所有探针技术,eBPF的灵活形态,方便用户实现特定化需求。

eBPF工作原理

eBPF程序分为两部分:

  • 内核态代码,被加载进内核执行的代码,加载之前需要被验证器校验,确保不会对内核造成伤害。
  • 用户态代码,和我们正常实现一个应用代码没有区别,在用户进程空间执行,但是额外做一些帮助内核态代码load进内核的工作。

内核态代码会被限制可以调用的函数,通常干采集数据的工作,用户态代码调用函数不限,方便实现业务逻辑,那么内核态代码和用户态代码如何沟通数据呢?eBPF提供了bpf_maps,是用户态和内核态共享的数据空间,通过k-v结构共享存取数据。

内核态代码也不同与我们写正常C语言代码,编译成机器码二进制进入内核执行的,而是内核约定的一套Bytecode指令,eBPF的验证器验证ByteCode通过后,eBPF的JIT编译器将ByteCode编译成机器码进入内核执行。这样一套过程,验证确保安全,JIT确保高效。

我们写eBPF内核态代码不能直接写ByteCode(类似汇编)吧?目前Clang实现了将C语言代码编译成eBPF Bytecode的后端,所以可以使用C编写eBPF内核态代码。用户态代码是在eBPF程序用户进程执行,所以可以便随意选择编程语言。

例子:

内核态代码
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>


struct key_t {
u32 prev_pid;
u32 curr_pid;
};

// 创建与用户态代码共享数据的eBPF maps
BPF_HASH(stats, struct key_t, u64, 1024);
int count_sched(struct pt_regs *ctx, struct task_struct *prev) {
struct key_t key = {};
u64 zero = 0, *val;


key.curr_pid = bpf_get_current_pid_tgid();
key.prev_pid = prev->pid;


// could also use `stats.increment(key);`
val = stats.lookup_or_try_init(&key, &zero);
if (val) {
(*val)++;
}
return 0;
}
用户态代码

#!/usr/bin/python
# Copyright (c) PLUMgrid, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")

from bcc import BPF
from time import sleep

# 帮助load 内核态代码
b = BPF(src_file="task_switch.c")
# 指定内核态代码Hook点,指定探针
b.attach_kprobe(event_re="^finish_task_switch$|^finish_task_switch\.isra\.\d$",fn_name="count_sched")

# generate many schedule events
for i in range(0, 100): sleep(0.01)

for k, v in b["stats"].items():
print("task_switch[%5d->%5d]=%u" % (k.prev_pid, k.curr_pid, v.value))

相关链接

阅读全文

在离线业务混部

什么是在离线业务

  • 在线服务:运行时间长,服务流量及资源利用率有潮汐特征,时延敏感,对服务 SLA 要求极高,如消息流 Feed 服务、电商交易服务等。

  • 离线作业:运行时间分区间,运行期间资源利用率较高,时延不敏感,容错率高,中断一般允许重运行,如 Hadoop 生态下的 MapReduce、Spark 作业。

技术门槛

  • 可观测性体系
  • 调度决策

在离线混部的调度决策是决定混部效果的核心,目前主要有几种决策方式:

整机分时复用:在固定的时间点(比如凌晨以后)跑离线作业,白天让出资源给在线服务。这种以时间维度切分的混部方式比较简单易理解,但整体资源利用率提升有限。

资源部分共享:将单机的资源整体划分为在线资源、离线资源以及在离线共享资源,各资源之间隔离,提前划分预留。这种从单机资源维度切分的混部方式比分时复用相对更精细一些,但是需要资源规格较大的机器切分才有意义。

资源完全共享:通过及时准确的资源预测手段、快速响应资源变化的能力,以及一套可以在资源水位发生变化时的服务保障措施,更高效自动化地实现机器资源复用。资源归属不预设,完全依据实时指标决策。

前一种属于静态决策,相对来说对底层可观测性体系的要求、对调度系统的高可用高性能的要求较低。
后两种属于动态决策,在资源利用率的提升上比静态决策更优,但对前述支撑系统要求也更高。

  • 调度执行
  • 资源隔离
  • 任务冲突时的资源保障
  • 服务平行扩缩容能力

业界在离线混部方案

  • 独占内核 + 物理机 + 静态决策

入门级的在离线混部选择,比如物理机运行服务且分时整机腾挪。

  • 独占内核 + 容器 + 动态决策

如果公司研发团队底层技术积累比较少,想快速、安全、低成本地用上在离线混部,先享受部分混部的成本优化红利,则独占内核+ 容器 + 动态决策组合的方案是首选。

  • 共享内核 + 容器 + 动态决策

如果有比较强的研发实力,能够较好解决第二部分中讲到的几乎所有技术门槛,就可以挑战共享内核 + 容器 + 动态决策组合的方案,以追求极致的资源利用率和成本优化效果。

阅读全文

开源函数计算平台Refunc

Serverless介绍

云计算服务从IaaS到PaaS到如今炙手可热的Serverless,服务对象始终是计算资源使用者。Serverless是一种让开发者无需关心基础设施(服务器等),而是专注到应用程序业务逻辑上的计算服务模型。

加州大学伯克利分校在论文中尝试给出Serverless的定义:Serverless computing = FaaS + BaaS。

Serverless 包含两个组成部分 BaaS(后端即服务)和 FaaS(函数即服务)。对象存储、关系型数据库以及 MQ 等基础支撑服务属于BaaS。FaaS为开发人员提供了一种运行应用的抽象方式,在层次上更靠近应用程序开发者。

FaaS理解

FaaS为开发人员提供了一种运行应用的抽象方式,可以在无需管理服务器的情况下响应事件。例如,上载文件可触发自定义代码,从而将文件转码为各种格式。

FaaS通过事件驱动执行,它会随时待命,但不需要任何服务器进程在后台持续运行。当请求到来时,FaaS在毫秒内启动服务并处理各个请求,当请求减少时,FaaS会自动减少服务副本数量甚至关停服务。

动态扩缩容使FaaS在成本效益上颇具弹性空间,提供商可以仅对使用的资源收费,而不对空闲时间收费。

FaaS平台设计

从服务形态看,FaaS平台需要实现几个关键部分:

  • 函数环境与执行
  • 从0到N动态扩缩容
  • 事件驱动框架

函数环境可以分两个点,系统环境和函数依赖,系统环境主要指系统平台、RootFS等,函数依赖是用户代码本身依赖的第三方库等。函数执行需要为用户代码分配计算实体,具体可以是进程、容器、虚拟机、Wasm等等。

动态扩缩容是FaaS平台对函数状态感知到反馈的一个体现,可以收集函数执行环境和依赖的服务指标来实现,比如采集CPU、内存、带宽、IOPS等等指标判断函数负载采取扩缩容行为。

函数执行依靠事件驱动,事件框架是事件采集、传输的关键。所以设计FaaS平台事件框架需要考虑扩展性,事件传输效率,事件采集协议等等问题。

开源函数计算平台Refunc

先上项目地址: https://github.com/refunc/refunc

Refunc是一个基于Kubernetes的开源函数计算平台,架构如下图:

基于Kubernetes开发实现,核心的CRD包括:

  • Funcdef 函数定义
  • Funcinsts 函数实例
  • Xenv 运行环境
  • Trigger 触发器

工作原理概括为,基于Funcdef和Xenv解决函数环境与执行,定义的Trigger收集事件通过基于Nats的事件框架传递到Funcinsts驱动函数执行并返回结果,核心Operator扩展了Kubernetes的HPA实现了从0到N的函数动态扩缩容。

Refunc拥抱AWS Lambda生态,函数Runtime完全兼容Lambda,基于Lambda的Runtime生态支持各种编程语言,平台接口API兼容Lambda核心接口,完全可以使用aws官方cli工具。

阅读全文