首页
Search
1
yamux: how to work?
79 阅读
2
The Art of Memory Allocation: Malloc, Slab, C++ STL, and GoLang Memory Allocation
71 阅读
3
How to receive a network packet in Linux
63 阅读
4
Maps and Memory Leaks in Go
54 阅读
5
C++ redis connection pool
52 阅读
测试
Wireguard
K8s
Redis
C++
Golang
Libcurl
Tailscale
Nginx
Linux
web3
Uniswap V2
Uniswap V3
EVM
security
solidity
openzeppelin
登录
Search
标签搜索
web3
solidity
web3 security
c++
uniswapV3
redis
evm
uniswap
性能测试
k8s
wireguard
CNI
http
tailscale
nginx
linux
设计模式
Jericho
累计撰写
51
篇文章
累计收到
13
条评论
首页
栏目
测试
Wireguard
K8s
Redis
C++
Golang
Libcurl
Tailscale
Nginx
Linux
web3
Uniswap V2
Uniswap V3
EVM
security
solidity
openzeppelin
页面
搜索到
51
篇与
的结果
2022-12-02
C++ redis connection pool
学习teamtalk服务端源码,把redis连接池的实现记录下,用到了hiredis三方库和头文件。整个redis缓存池是三个类实现的,redis-manager,redis-pool,redis-conn。{lamp/}main函数获取redis-manager对象实例CacheManager* CacheManager::getInstance() { if (!s_cache_manager) { s_cache_manager = new CacheManager(); if (s_cache_manager->Init()) { delete s_cache_manager; s_cache_manager = NULL; } } return s_cache_manager; } main() { //初始化redis CacheManager* pCacheManager = CacheManager::getInstance(); if (!pCacheManager) { log("CacheManager init failed"); return -1; } }通过redis-manager类init函数读取配置文件,创建对应的缓存池对象,并初始化缓存池,每个缓存池有一个缓存池名对应一个redis-pool指针加入到map中管理。int CacheManager::Init() { CConfigFileReader config_file("dbproxyserver.conf"); //CacheInstances=unread,group_set,token,sync,group_member char* cache_instances = config_file.GetConfigName("CacheInstances"); if (!cache_instances) { log("not configure CacheIntance"); return 1; } char host[64]; char port[64]; char db[64]; char maxconncnt[64]; CStrExplode instances_name(cache_instances, ','); for (uint32_t i = 0; i < instances_name.GetItemCnt(); i++) { char* pool_name = instances_name.GetItem(i); //printf("%s", pool_name); snprintf(host, 64, "%s_host", pool_name); snprintf(port, 64, "%s_port", pool_name); snprintf(db, 64, "%s_db", pool_name); snprintf(maxconncnt, 64, "%s_maxconncnt", pool_name); char* cache_host = config_file.GetConfigName(host); char* str_cache_port = config_file.GetConfigName(port); char* str_cache_db = config_file.GetConfigName(db); char* str_max_conn_cnt = config_file.GetConfigName(maxconncnt); if (!cache_host || !str_cache_port || !str_cache_db || !str_max_conn_cnt) { log("not configure cache instance: %s", pool_name); return 2; } CachePool* pCachePool = new CachePool(pool_name, cache_host, atoi(str_cache_port), atoi(str_cache_db), atoi(str_max_conn_cnt)); if (pCachePool->Init()) { log("Init cache pool failed"); return 3; } m_cache_pool_map.insert(make_pair(pool_name, pCachePool)); } return 0; }缓存池init的时候会创建redis连接对象,默认数量为2,并将创建的redis连接对象加入到缓存池的成员变量空闲redis连接数组中,等待被取用。int CachePool::Init() { for (int i = 0; i < m_cur_conn_cnt; i++) { CacheConn* pConn = new CacheConn(this); if (pConn->Init()) { delete pConn; return 1; } m_free_list.push_back(pConn); } log("cache pool: %s, list size: %lu", m_pool_name.c_str(), m_free_list.size()); return 0; }redis连接对象init的时候主要用于连接的初始化和重连操作,每次对redis增删改查的之前都会调用init这个函数,每4秒尝试重连一次。/* * redis初始化连接和重连操作,类似mysql_ping() */ int CacheConn::Init() { if (m_pContext) { return 0; } // 4s 尝试重连一次 uint64_t cur_time = (uint64_t)time(NULL); if (cur_time < m_last_connect_time + 4) { return 1; } m_last_connect_time = cur_time; // 200ms超时 struct timeval timeout = {0, 200000}; m_pContext = redisConnectWithTimeout(m_pCachePool->GetServerIP(), m_pCachePool->GetServerPort(), timeout); if (!m_pContext || m_pContext->err) { if (m_pContext) { log("redisConnect failed: %s", m_pContext->errstr); redisFree(m_pContext); m_pContext = NULL; } else { log("redisConnect failed"); } return 1; } redisReply* reply = (redisReply *)redisCommand(m_pContext, "SELECT %d", m_pCachePool->GetDBNum()); if (reply && (reply->type == REDIS_REPLY_STATUS) && (strncmp(reply->str, "OK", 2) == 0)) { freeReplyObject(reply); return 0; } else { log("select cache db failed"); return 2; } }整个初始化的过程大概结束,下面就是通过redis-manager从缓存池中获取释放redis连接的过程。缓存池名是通过配置文件中读入的,获取缓存池中的redis连接需要传入缓存池名,通过map获取到缓存池名对应的缓存池对象指针,并调用其成员函数getCacheConn获取redis连接。CacheConn* CacheManager::GetCacheConn(const char* pool_name) { map<string, CachePool*>::iterator it = m_cache_pool_map.find(pool_name); if (it != m_cache_pool_map.end()) { return it->second->GetCacheConn(); } else { return NULL; } }调用缓存池CachePool成员函数getCacheConn获取redis连接这块代码有可学之处,他用了pthread库里面的信号量来优雅地等待空闲连接数组中的连接对象,为了线程安全需要在访问空闲连接数组的时候加锁,不断判断数组中是否存在可用的redis连接对象,如果没有可用的连接,则将判断此时正在使用的redis连接对象个数是否超过指定最大值,超过则将用信号量阻塞,直到有空闲的连接被唤醒。如果此时连接数小于指定最大值,则将继续创建redis连接对象并初始化一个可用的redis连接对象加入到空闲redis连接数组中。如果数组中有可用的redis连接则将从数组头部取出一个元素并返回。CacheConn* CachePool::GetCacheConn() { m_free_notify.Lock(); while (m_free_list.empty()) { if (m_cur_conn_cnt >= m_max_conn_cnt) { m_free_notify.Wait(); } else { CacheConn* pCacheConn = new CacheConn(this); int ret = pCacheConn->Init(); if (ret) { log("Init CacheConn failed"); delete pCacheConn; m_free_notify.Unlock(); return NULL; } else { m_free_list.push_back(pCacheConn); m_cur_conn_cnt++; log("new cache connection: %s, conn_cnt: %d", m_pool_name.c_str(), m_cur_conn_cnt); } } } CacheConn* pConn = m_free_list.front(); m_free_list.pop_front(); m_free_notify.Unlock(); return pConn; }释放redis连接对象,传入需要释放的redis连接对象指针,加锁,遍历redis连接对象数组中是否存在该连接对象,不存在则将连接对象加入到该空闲redis连接数组中,信号量通知唤醒被阻塞的线程,redis连接数组中有空闲连接可用。void CachePool::RelCacheConn(CacheConn* pCacheConn) { m_free_notify.Lock(); list<CacheConn*>::iterator it = m_free_list.begin(); for (; it != m_free_list.end(); it++) { if (*it == pCacheConn) { break; } } if (it == m_free_list.end()) { m_free_list.push_back(pCacheConn); } m_free_notify.Signal(); m_free_notify.Unlock(); }
2022年12月02日
52 阅读
2 评论
0 点赞
2022-12-02
C++ thread pool
学习teamtalk服务端源码,记录下线程池的实现。主要是实现的类分为任务类(抽象类)、线程池类、工作线程类、线程同步类。 Task抽象任务类 虚基类为了能够处理不同任务#ifndef __TASK_H__ #define __TASK_H__ class CTask { public: CTask(){} virtual ~CTask(){} virtual void run() = 0; private: }; #endif /*defined(__TASK_H__) */线程池类threadPoolinit初始化并启动工作线程int CThreadPool::Init(uint32_t worker_size) { m_worker_size = worker_size; m_worker_list = new CWorkerThread [m_worker_size]; if (!m_worker_list) { return 1; } for (uint32_t i = 0; i < m_worker_size; i++) { m_worker_list[i].SetThreadIdx(i); m_worker_list[i].Start(); } return 0; }工作线程创建线程,遍历任务队列并处理。这里遍历任务队列的方式和遍历redis空闲连接队列一样,用一个锁和信号量优雅地等待任务。void* CWorkerThread::StartRoutine(void* arg) { CWorkerThread* pThread = (CWorkerThread*)arg; pThread->Execute(); return NULL; } void CWorkerThread::Start() { (void)pthread_create(&m_thread_id, NULL, StartRoutine, this); } void CWorkerThread::Execute() { while (true) { m_thread_notify.Lock(); // put wait in while cause there can be spurious wake up (due to signal/ENITR) while (m_task_list.empty()) { m_thread_notify.Wait(); } CTask* pTask = m_task_list.front(); m_task_list.pop_front(); m_thread_notify.Unlock(); pTask->run(); delete pTask; m_task_cnt++; //log("%d have the execute %d task\n", m_thread_idx, m_task_cnt); } }初始化工作做完说一下add task,通过线程池对象添加任务,根据线程在线程数组中的索引随机添加任务到一个随机线程中,调用工作线程的pushtask function,加锁并唤醒等待任务而阻塞的线程解锁。void CThreadPool::AddTask(CTask* pTask) { /* * select a random thread to push task * we can also select a thread that has less task to do * but that will scan the whole thread list and use thread lock to get each task size */ uint32_t thread_idx = random() % m_worker_size; m_worker_list[thread_idx].PushTask(pTask); } void CWorkerThread::PushTask(CTask* pTask) { m_thread_notify.Lock(); m_task_list.push_back(pTask); m_thread_notify.Signal(); m_thread_notify.Unlock(); }线程同步类通过封装pthread的互斥量和信号量实现构造函数负责初始化互斥量和信号量,析构函数负责destroy操作CThreadNotify::CThreadNotify() { pthread_mutexattr_init(&m_mutexattr); pthread_mutexattr_settype(&m_mutexattr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&m_mutex, &m_mutexattr); pthread_cond_init(&m_cond, NULL); } CThreadNotify::~CThreadNotify() { pthread_mutexattr_destroy(&m_mutexattr); pthread_mutex_destroy(&m_mutex); pthread_cond_destroy(&m_cond); }实现线程同步lock、unlock、wait、signalvoid Lock() { pthread_mutex_lock(&m_mutex); } void Unlock() { pthread_mutex_unlock(&m_mutex); } void Wait() { pthread_cond_wait(&m_cond, &m_mutex); } void Signal() { pthread_cond_signal(&m_cond); }在dbproxy服务中用到了线程池,proxy连接类处理proxy任务时如下:static CThreadPool g_thread_pool; int init_proxy_conn(uint32_t thread_num) { //省略其余代码 g_thread_pool.Init(thread_num); } void CProxyConn::HandlePduBuf(uchar_t* pdu_buf, uint32_t pdu_len) { //省略其余代码 CTask* pTask = new CProxyTask(m_uuid, handler, pPdu); g_thread_pool.AddTask(pTask); }proxytask继承了task抽象类,并重写了run方法,使用task抽象类可以处理任何任务,代码质量很高。{lamp/}
2022年12月02日
17 阅读
0 评论
0 点赞
2022-12-01
redis debug asynchronous aof fsync is taking too long (disk is busy?)
一.背景 线上redis的监控数据不太好看,领导觉得看看能不能优化下,分析下什么情况,于是就.....二.现象 看到redis日志里面一堆这个,于是就开始了分析排查先放一张前后对比图吧{callout color="#f0ad4e"}p99延时优化一百倍 😏 {/callout}{lamp/}三.排查 之前在网上找,也看到有人说了相关的解释,不过还是自己决定在多研究下其他的缘由,直接去官网 看看官方的解释 redis troubleshoting 好早之前分析的,现发现,redis的官网变了,这玩意都找不到,直接把之前的图贴上来吧重点看红框部分,此项警告在redis代码中是在AOF fsync刷盘模式是everysecs,代码中的注释 /* Otherwise fall trough, and go write since we can't wait over two seconds,redis中的AOF持久化主要是又两个系统调用实现,一个是fdatasync,另外一个是write,redis发现文件有在执行 fdatasync(2) 时,就先不调用 write(2),只存在 cache 里,免得被 Block。但如果已经超过两秒都还是如此,则会强行执行 write(2),导致redis 会被 Block 住 。居然知道了大概的方向,我们就直接去看日志然后再通过strace去监控write和fdatasync系统调用时间来进行排(线上还是谨慎使用strace吧,这玩意系统调用还是很危险的,优选ebpf)此时观测redis日志,发现redis 主线程 PID:2418redis AOF线程 PID:2412 (服务器配置的AOF同步策略是everysec,故每秒执行一次)redis RDB子进程 PID:14603有了这个日志和数据,我们直接开启硬核的步骤,去看redis的源码,看看RDB和AOF的执行流redis执行流分析 redis进程是一个事件循环,分为文件事件和时间事件文件事件:主要对套接字操作的抽象,服务器通过监听并处理这些事件来完成一系列网络通信的操作,完成客户端的命令请求和回复时间事件:主要分为周期的和定时的事件,做一些统计、清理、aof等的工作因为服务器在处理文件事件时可能会执行命令,使得一些内容被追加到aof_buf缓冲区,所以服务器每次执行一次事件循环都会调用flushAppendOnlyFile函数,考虑要不要将aof_buf缓冲区中的内容写入保存到AOF文件中。RDB后台同步流程rdbSave将数据写入同步到磁盘,write写完文件之后,进行了(fflush,fsync)强行刷盘,导致IO繁忙RDB后台子进程发送退出信号给父进程,父进程捕获然后处理redis主进程AOF 刷盘逻辑根据上一次刷盘的延时,来决定要不要return,如果此时延时超过2s则将延迟加1,打出log,接着主进程为了保证AOF的数据一致性,需要将AOF写命令写入到AOF缓冲区,强行调用write如果当前没有再做AOF_FSYNC的后台任务,根据设置的sync策略进行调用FSYNC刷盘四.解决方案 方法一:配置修改Redis在执行write的时候,由操作系统自身被动控制何时进行fsync。这里如果我们要主动触发操作系统的fsync,可以设置操作系统级别的参数:查看内存的脏页字节大小,设置为0代表由系统自己控制何时调用fsyncsysctl -a | grep vm.dirty_bytesvm.dirty_bytes = 0修改为一个小的值,例如32MB,达到这个数据量就fsync,让操作系统fsync这个动作更频繁一点,避免单次fsync太多数据,导致阻塞echo "vm.dirty_bytes=33554432" >> /etc/sysctl.conf 方法二:关闭RDB或者AOF(安全性换效率)关闭RDB可减少RDB生成的时候,对磁盘的压力,而关闭AOF则可以减少AOF刷盘对磁盘的压力,从而让Redis的性能达到极致。不过这个方法只能用在纯缓存场景,如果有持久化的要求,则一般不靠谱,因为安全性大大降低,一旦宕机,则数据无法恢复。一种折中的方案,是从库开启AOF,而主库关闭AOF。
2022年12月01日
17 阅读
0 评论
1 点赞
2022-12-01
wireguard protocol
wireguard使用了ECDH协商算法及noise安全框架故研究了相关知识,针对wireguard握手、数据传输通过源码进行研究。{lamp/}ECDHE密钥协商算法DH数学性质已知数据独有数据离散对数及离散对数的幂运算有交换律指数P、原根a指数P、原根a离散对数及离散对数的幂运算有交换律 指数P、原根a 各自有自己的离散对数数学公式:离散对数如果对于一个整数b和质数p的一个原根a,可以找到一个唯一的指数i,使得: 其中 成立,那么指数i称为b的以a为基数的模p的离散对数。离散对数难题是指:当已知一个大质数p和它的一个原根a,如果给定一个b,要计算i的值是相当困难的a为A的私钥 b为B的私钥 a或b为静态公开 A与BA:A = G ^ a ( mod P ) B:B = G ^ b ( mod P )A: B ^ a ( mod P ) = k B: A ^ b ( mod P ) = kDHE随机生成临时的私钥a与bECDHE利用了 ECC 椭圆曲线特性,可以用更少的计算量计算出公钥,以及最终的会话密钥数学性质已知数据独有数据椭圆曲线及椭圆曲线上满足乘法交换和结合律椭圆曲线、椭圆曲线上一基点G各自有自己的随机数私钥A: 私钥 d1 公钥 Q1 = d1xG B: 私钥 d2 公钥 Q2 = d2xGA: d1xQ2 = k B:d2xQ1Noise安全协议框架为那些想创建自己的安全协议的开发者提供了一套模板,Noise 协议虽然其初衷是为网络协议提供安全信道,但它并没有规定使用什么样的通讯协议 — TCP / UDP 甚至是任何满足 read / write 接口的子系统,比如文件,管道(pipe),都可以使用 Noise 协议在握手阶段,发起者和应答者(initiator / responder,注意 Noise 没有使用 Client / Server,但我们可以简单认为 initiator 是 client,而 responder 是 server)通过交换信息对使用何种算法,密钥是什么达成一致。握手阶段双方需要使用同样的协议变量 —— 和 TLS 不同的是,Noise 把协议变量设计为静态而非协商出来的。这是一个很大的简化,而从用户的角度,用户写出来的使用 Noise 的应用往往是自己的节点跟自己的节点通讯,因而无需协商。WireGuard 使用的是这样的变量:Noise_IKpsk2_25519_ChaChaPoly_BLAKE2sI:发起者的固定公钥未加密就直接发给应答者K:应答者的公钥发起者预先就知道psk2:把预设的密码(Pre-Shared-Key )放在第 2 个握手包之后ChaChaPoly:对称加密算法BLAKE2s:哈希算法协议变量各个部分是这样规定的:Noise <握手的模式> <公钥算法> <对称加密算法> <哈希算法>。其中握手模式有很多种,适用于各种不同的场合。握手的过程中,Noise 会生成一个 HandshakeState,它用来记录当前收到的对端传来的固定公钥和临时公钥,以及通过 ECDH 算法算出来的临时数据。当整个握手结束后,双方都有对等的信息,可以生成一致的密钥。于是发起者和应答者对于它们各自的接收端和发送端生成密钥对。之后,Noise 允许用户将协议状态切换成传输模式(Transport Mode)。切换过程中,HandshakeState 转换成 CipherState,然后就可以用 encrypt / decrypt 来加密和解密应用的数据了。wireguard中具体细节见 wireguard.pdf wireguard内核模块源码分析源码分析数据结构wireguard的消息主要分为三种 message_handshake_initiation、message_handshake_response、message_handshake_cookie发起者 : 发送第一条握手消息// 消息类型 enum message_type { MESSAGE_INVALID = 0, MESSAGE_HANDSHAKE_INITIATION = 1, MESSAGE_HANDSHAKE_RESPONSE = 2, MESSAGE_HANDSHAKE_COOKIE = 3, MESSAGE_DATA = 4 }; message_handshake_initiation = handshake_initiation { u8 message_type //消息类型 u32 sender_index u8 unencrypted_ephemeral[32] //发送方为这次握手临时生成的公钥(未加密,用于 ECDH) u8 encrypted_static[AEAD_LEN(32)] //用对端公钥和临时生成的私钥 ECDH 出的临时密钥 key1 对称加密对方的公钥 u8 encrypted_timestamp[AEAD_LEN(12)] //用对端公钥和自己的私钥 ECDH 出 key2,key2 混淆进 key1,来加密当前的时间戳 u8 mac1[16] //自己的固定公钥加上整个报文内容后的哈希 u8 mac2[16] } initiator.chaining_key = HASH(CONSTRUCTION) initiator.hash = HASH(HASH(initiator.chaining_key || IDENTIFIER) || responder.static_public) initiator.ephemeral_private = DH_GENERATE() msg.message_type = 1 msg.reserved_zero = { 0, 0, 0 } msg.sender_index = little_endian(initiator.sender_index) msg.unencrypted_ephemeral = DH_PUBKEY(initiator.ephemeral_private) initiator.hash = HASH(initiator.hash || msg.unencrypted_ephemeral) temp = HMAC(initiator.chaining_key, msg.unencrypted_ephemeral) initiator.chaining_key = HMAC(temp, 0x1) temp = HMAC(initiator.chaining_key, DH(initiator.ephemeral_private, responder.static_public)) initiator.chaining_key = HMAC(temp, 0x1) key = HMAC(temp, initiator.chaining_key || 0x2) msg.encrypted_static = AEAD(key, 0, initiator.static_public, initiator.hash) initiator.hash = HASH(initiator.hash || msg.encrypted_static) temp = HMAC(initiator.chaining_key, DH(initiator.static_private, responder.static_public)) initiator.chaining_key = HMAC(temp, 0x1) key = HMAC(temp, initiator.chaining_key || 0x2) msg.encrypted_timestamp = AEAD(key, 0, TAI64N(), initiator.hash) initiator.hash = HASH(initiator.hash || msg.encrypted_timestamp) msg.mac1 = MAC(HASH(LABEL_MAC1 || responder.static_public), msg[0:offsetof(msg.mac1)]) if (initiator.last_received_cookie is empty or expired) msg.mac2 = [zeros] else msg.mac2 = MAC(initiator.last_received_cookie, msg[0:offsetof(msg.mac2)])接收者:发送第一条握手回复消息// 消息类型 enum message_type { MESSAGE_INVALID = 0, MESSAGE_HANDSHAKE_INITIATION = 1, MESSAGE_HANDSHAKE_RESPONSE = 2, MESSAGE_HANDSHAKE_COOKIE = 3, MESSAGE_DATA = 4 }; message_handshake_initiation = handshake_initiation { u8 message_type //消息类型 u32 sender_index u32 receiver_index u8 unencrypted_ephemeral[32] //接收方为这次握手临时生成的公钥(未加密,用于 ECDH) u8 encrypted_nothing[AEAD_LEN(0)] u8 mac1[16] //对端公钥加上整个报文内容后的哈希 u8 mac2[16] }1.wireguard 维护一张哈希表(key: peer-pubkey value: &peer),如果不做peer移除处理peer数量将会持续增长,超出peer最大限制10^20个。2.wireguard中对于peer是通过配置文件读取创建的,对peer的处理只能通过用户使用命令进行增删。3.wireguard中keeplive包只有initiator或者responder设置了peer的persistent keeplive time会发送,目前逻辑中responder peer没有设置persistent keeplive time 故responder端不会发送keeplive包。4.wireguard中initiator与responder握手成功之后,initiator会主动发一个数据包或者keeplive包给responder,为了交换并检验双方session key协商的结果。5.wireguard中的session key有9分钟过期时间,创建密session key的时候双方将会启动内核定时器,定时9分钟清空peer对应的session key与临时公私钥。
2022年12月01日
30 阅读
1 评论
2 点赞
2022-12-01
K8s bridge、CNI
一、项目地址 k8s CNI 插件 二、项目介绍bridge、IPAM 插件都在K8s的网络插件里面bridge插件首先创建一个虚拟网桥,其功能类似物理交换机,将所有容器接入一个二层网络;容器接入是通过创建的veth pair(虚拟网卡对)实现,将网卡对的一端放在容器中并分配IP地址,另一端放在host的namespace内连上虚拟网桥,网桥上可以配置IP作为容器的网关,通过网桥连接了两个不同namespace内的网卡,容器内发出的数据包可通过网桥转发到host网络协议栈或进入另一个容器,最终实现容器之间、容器和主机间、容器和服务间的通信。2.工作流程创建容器时的动作1)按名称检查网桥是否存在,若不存在则创建一个虚拟网桥2)创建虚拟网卡对,将host端的veth口连接到网桥上3)IPAM从地址池中分配IP给容器使用,并计算出对应网关配置到网桥4)进入容器网络名称空间,修改容器端的网卡ip并配置路由5)使用iptables增加容器内部网段到外部网段的masquerade规则6)获取当前网桥信息,返回给调用者删除容器时的动作1)按输入参数找到要删除容器的IP地址,调用ipam插件删除地址并将IP归还地址池2) 进入容器网络隔离ns,根据容器IP找到对应的网络接口并删除3) 在节点主机上删除创建网络时添加的所有iptables规则数据结构解析bridge配置需转化的json结构接口网桥提供了三个接口add、del、checkadd:解析配置文件,创建网桥,虚拟网卡对、获取当前namespace,用虚拟网卡对将网桥和namespace连接,调用ipam插件给namespace侧的虚拟网卡对分配ip,配置路由,配置snat,返回bridge信息del: 通过ipam插件删除容器申请的IP地址,并归还到地址池,进入容器ns删除对应的网络接口veth和IP地址,snatcheck: 调用ipam插件ExecCheck进行检查, 检测bridge端口类型和mac地址,检测容器接口类型和mac地址,检查bridge和容器的veth是否配对,检查ip是否配置正确,检查route是否配置正确四、IP Address Management (IPAM)DHCP模式一个DHCP的服务流程需要两个组件DHCP Client 和DHCP Server,而DHCP IPAM扮演的角色就是DHCP Client,DHCP Client运作的模式发送DHCP 请求等待DHCP 响应设定IP到目标网络介质上定期RenewDHCP模式下有两种运行的形式,一种是CNI模式(提供函数接口),一种是启动一个不停运行的daemon模式(使用rpc提供接口)。daemon 模式的功用很简单,接受所有来自 CNI 模式的请求,然后切换到目标的 namespace 里面去根据目标的网络介质发送一个 DHCP 请求封包,所以运行 DHCP IPAM 之前,要先在系统上跑一个 daemon,然后会通过 unix socket 的方式等待 DHCP IPAM CNI 发送命令过来,该命令需要包含 目标的namespace和目标的网卡名称。执行流程图首先当 DHCP CNI被调用后,会先通过 unix socket 的方式通知 daemondaemon 接着到该 ns 之中,确认这个ns存在就发送对应的DHCP 請求magic 的意思代表沒有限定数据包要怎么处理,总之 DHCP 封包要有办法出去最后外面的(甚至同一台机器)上面的 DHCP Server 可以看到 Request 并且回复Reply最后当DHCP Daemon 发送 DHCP 请求的那个 thread 接收到 DHCP 回复后,就会给目标网卡设置 IP 地址。Static模式static IPAM 是一个非常简单的 IPAM 插件,可以将 IPv4 和 IPv6 地址静态分配给容器。 这将有助于调试目的以及在不同 vlan/vxlan 中为容器分配相同 IP 地址的情况。需要手动设置IP 地址,包含了 ipv4/ipv6Route 路由表DNS 设置Host-Local模式大部分CNI 会使用这个IPAM去处理IP分配的问题1.config格式{ "ipam": { "type": "host-local", "ranges": [ [ { "subnet": "10.10.0.0/16", "rangeStart": "10.10.1.20", "rangeEnd": "10.10.3.50", "gateway": "10.10.0.254" }, { "subnet": "172.16.5.0/24" } ] ] } }返回的ip格式"cniVersion": "0.2.0", "ip4": { "ip": "10.10.1.20/16", "gateway": "10.10.0.254" }, "dns": {} }2.IP分配原理host-local IPAM 插件从一组地址范围中分配 IP 地址。 它将状态本地存储在主机文件系统上,从而确保单个主机上 IP 地址的唯一性。 分配器可以分配多个范围,并支持多个(不相交的)子网集。 分配策略在每个范围集中进行round-robin3.状态存储路径默认路径 /var/lib/cni/networks/sudo find /var/lib/cni/networks/ -type f/var/lib/cni/networks/last_reserved_ip.0/var/lib/cni/networks/10.10.1.20/var/lib/cni/networks/10.10.1.22/var/lib/cni/networks/10.10.1.21sudo find /var/lib/cni/networks/ -type f | xargs -I % sh -c 'echo -n "%: ->"; cat %; echo "";'/var/lib/cni/networks/last_reserved_ip.0: ->10.10.1.22/var/lib/cni/networks/10.10.1.20: ->ns1/var/lib/cni/networks/10.10.1.22: ->ns1/var/lib/cni/networks/10.10.1.21: ->ns1每个被用过的 IP 都会产生一个以该 IP 为名的文件,该文件中的內容非常简单,就是使用的 container ID(ns1),last_reserved_ip 的文件,该文件用来记住每个 range 目前分配的最后一个 IP 是哪个
2022年12月01日
19 阅读
0 评论
1 点赞
1
...
9
10
11