当前位置: 首页 > Linux

记得有一次K3sMySQL启动OOM排查

时间:2023-04-07 03:26:03 Linux

我之前使用的是DockerCompose作为开发环境,MySQL也在Docker中运行,一切正常。后来开发环境迁移到了K3s(轻量级的K8s),但是MySQL一启动就被OOMKiller杀掉了,所以一直没有迁移MySQL。Reproduction使用kubectl直接运行一个MySQL来重现:apiVersion:apps/v1kind:Deploymentmetadata:name:mysqlspec:replicas:1selector:matchLabels:app:mysqltemplate:metadata:labels:app:mysqlspec:containers:-name:mysqlimage:mysql:5.7imagePullPolicy:IfNotPresentenv:-name:MYSQL_ROOT_PASSWORDvalue:rootresources:limits:memory:4Gcpu:500mdmesg可以看到mysqld分配了超过3.7G的内存被killed:[839.399262]Tasksstate(memovalues??inpages):[839.399263][pid]uidtgidtotal_vmrsspgtables_bytesswapentsoom_score_adjname...[839.399278][34888]034888420824097417779626240-998mysqld[839.399280]oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=...,mems_allowed=0,oom_memcg=/kubepods/pod..,task_memcg=/kubepods/pod../56..,task=mysqld,pid=34888,uid=0[839.399294]内存cgroup内存不足:杀死process34888(mysqld)total-vm:16832960kB,anon-rss:3895388kB,file-rss:1320kB,shmem-rss:0kB[839.496988]oom_reaper:reapedprocess34888(mysqld),nowanon-rss:0kB,file-rs:0kB,shmem-rss:0kB排查优化MySQL配置第一反应是MySQL配置有问题,于是修改配置减少各种buffer的大小:[mysqld]innodb_buffer_pool_size=32Minnodb_buffer_pool_instances=1innodb_log_file_size=64Minnodb_log_buffer_size=8Mkey_buffer_size=16kmyisam_sort_buffer_size=16kmax_connections=50open_files_limit=4096max_allowed_packet=1Mtable_open_cache=16sort_buffer_size=512knet_buffer_length=8Kread_buffer_size=256Kread_rnd_buffer_size=256Kthread_cache_size=64query_cache_size=0tmp_table_size=12Mthread_stack=256K然而问题依旧,改了不少配置都没有效果,看来需要跟踪或调试用bpftrace定位内存分配这里没有使用strace主要Itisbecauseitiskilledwhenitisstarted,anditisnecessarytomodifytheimagestartupcommandandtheninstallandexecutestraceinthecontainer,whilebpftracecanbetracedgloballybythesystem,andthetracecanbecompletedoutsidethecontainer,whichismoreconvenient.内存分配主要通过skb和mmap系统调用,bpftrace用于跟踪这两个系统调用:#!/usr/bin/bpftracetracepoint:syscalls:sys_enter_mmap/comm=="mysqld"/{printf("%d%saddr=%ldlen=%ldflags=%ld\n",pid,probe,args->addr,args->len,args->flags);/*printf("%s\n",ustack(perf,10));*/}tracepoint:syscalls:sys_enter_brk/comm=="mysqld"/{printf("%d%sbrk%d\n",pid,probe,args->brk);}sudo./mysql-oom.btAttaching2probes...57950tracepoint:syscalls:sys_enter_brkbrk057950tracepoint:syscalls:sys_enter_mmapaddr=0len=8740flags=2...57950tracepoint:syscalls:sys_enter_brkbrk169908633657_callsys950tracepoint_addrsys950:mmappsys950:er0len=17179869184flags=34可以看到最后用mmap分配了16G的内存,然后就被kill掉了。尝试获取调用堆栈(ustack),但无法获取任何有用的信息。可能在未编译mysql时未启用帧指针:97694tracepoint:syscalls:sys_enter_mmapaddr=0len=12288flags=347f84c662730a0x7f84c662730a([unknown])97694tracepoint:syscalls:sys_enter_mmapaddr=0len=171794flags=869187f84c4c4064a0x7f84c4c4064a([unknown])无法获取堆栈,再次尝试跟踪所有系统调用:#!/usr/bin/bpftracetracepoint:syscalls:sys_enter_*/comm=="mysqld"/{printf("%d%s\n",pid,probe);}...输出:附加331个探测器...115490tracepoint:syscalls:sys_enter_close115490tracepoint:syscalls:sys_enter_brk115490tracepoint:syscalls:sys_enter_newstat115490tracepoint:syscalls:sys_enter_getrlimit115490tracepoint:sys_enter_getrlimit115490tracepoint:sys_enter_getrlimit115490tracepoint:sys_enter_getrlimit115490=0len=17179869184flags=34可以看到在最后一个mmap之前调用了getrlimit,猜测是mysql会根据系统资源限制分配内存。MySQL源码分析MySQL源码中直接调用getrlimit的地方并不多。排除ndb、innodb_memcached、libevent后,直接调用只有一个:staticuintset_max_open_files(uintmax_file_limit){structrlimitrlimit;uintold_cur;DBUG_ENTER("set_max_open_files");DBUG_PRINT("enter",("files:%u",max_file_limit));如果(!getrlimit(RLIMIT_NOFILE,&rlimit)){old_cur=(uint)rlimit.rlim_cur;DBUG_PRINT("信息",("rlim_cur:%urlim_max:%u",(uint)rlimit.rlim_cur,(uint)rlimit.rlim_max));如果(rlimit.rlim_cur==(rlim_t)RLIM_INFINITY)rlimit.rlim_cur=max_file_limit;如果(rlimit.rlim_cur>=max_file_limit)DBUG_RETURN(rlimit.rlim_cur);/*purecov:检查*/rlimit.rlim_cur=rlimit.rlim_max=max_file_limit;如果(setrlimit(RLIMIT_NOFILE,&rlimit))max_file_limit=old_cur;/*使用原始值*/else{m_climit.rli0;/*如果下次调用是安全的失败*/(void)getrlimit(RLIMIT_NOFILE,&rlimit);DBUG_PRINT("信息",("rlim_cur:%u",(uint)rlimit.rlim_cur));if(rlimit.rlim_cur)/*如果调用没有失败*/max_file_limit=(uint)rlimit.rlim_cur;}}DBUG_PRINT("退出",("最大文件限制:%u",最大文件限制));DBUG_RETURN(最大文件限制);其中的逻辑是:如果系统的文件打开限制为RLIM_INFINITY或大于如果要设置的max_file_limit很大,则返回系统的限制这个函数也只被直接调用一次:uintmy_set_max_open_files(uintfiles){structst_my_file_info*tmp;DBUG_ENTER("my_set_max_open_files");DBUG_PRINT("enter",("files:%umy_file_limit:%u",files,my_file_limit));文件+=MY_FILE_MIN;文件=set_max_open_files(MY_MIN(文件,OS_FILE_LIMIT));//获取最大打开文件数if(files<=MY_NFILE)DBUG_RETURN(files);//分配内存//初始化/*复制任何初始化文件*/memcpy((char*)tmp,(char*)my_file_info,sizeof(*tmp)*MY_MIN(my_file_limit,files));memset((tmp+my_file_limit),0,MY_MAX((int)(files-my_file_limit),0)*sizeof(*tmp));my_free_open_file_info();/*如果已经分配则释放*/my_file_info=tmp;my_file_limit=文件;DBUG_PRINT("exit",("files:%u",files));DBUG_RETURN(files);}原来MySQL会根据最大打开文件数,提前为每个文件分配并初始化内存,此时,它可能会分配太多内存,导致OOM解决启动前设置ulimit的问题,因为K8s目前不支持设置ulimit,很多HelmCharts在启动前都设置了ulimitcommand:["sh","-c","ulimit-n4096&&exec/usr/local/bin/docker-entrypoint.shmysqld"]修改K3s设置K3s通过systemd启动,可以修改k3s.service,限制K3s打开的文件数,这个限制会过去到containerd:LimitNOFILE=1048576