[{"data":1,"prerenderedAt":4629},["ShallowReactive",2],{"content:\u002F07-infrastructure\u002F08-scaling-scheduling":3},{"title":4,"description":5,"path":6,"body":7},"Масштабирование и планирование","Kubernetes автоматизирует не только деплой, но и масштабирование приложений и управление размещением Pod'ов на нодах. В этой главе мы разберём полную картину: от базовых ресурсных ограничений (requests\u002Flimits) до автоскейлинга (HPA, VPA, Cluster Autoscaler), механизмов планирования (affinity, taints, tolerations) и admission controllers, которые позволяют встроить собственные политики в API server.","\u002F07-infrastructure\u002F08-scaling-scheduling",{"type":8,"value":9,"toc":4564},"minimark",[10,13,16,21,26,29,166,181,195,198,216,220,225,275,280,315,321,325,335,341,344,351,358,360,364,367,371,374,442,445,449,452,512,515,519,522,531,534,540,542,546,550,553,679,683,789,792,796,828,830,834,838,841,845,1129,1133,1152,1157,1165,1171,1175,1233,1237,1299,1301,1305,1308,1311,1318,1398,1402,1405,1419,1422,1424,1428,1431,1435,1472,1476,1524,1531,1539,1541,1545,1548,1552,1566,1570,1584,1591,1608,1615,1619,1627,1630,1644,1647,1650,1653,1679,1686,1688,1692,1696,1699,1703,1744,1747,1751,1921,1935,1941,1945,2006,2008,2012,2019,2023,2109,2112,2116,2200,2203,2207,2210,2243,2247,2250,2415,2426,2428,2432,2435,2439,2488,2492,2557,2561,2603,2607,2610,2686,2693,2697,2768,2774,2776,2780,2783,2860,2863,2882,2884,2888,2892,2895,2899,2905,2912,2916,2994,2997,3020,3022,3026,3029,3033,3278,3282,3303,3314,3318,3346,3350,3355,3369,3374,3385,3387,3391,3394,3587,3597,3600,3611,3613,3617,3623,3629,3649,3655,3665,3671,3677,3686,3688,3692,3697,3703,3709,3719,3730,3733,3738,3871,3874,3876,3881,3886,3891,3900,3917,3920,3922,3927,3931,3936,3941,3956,3960,4154,4157,4159,4163,4194,4401,4560],[11,12,5],"p",{},[14,15],"hr",{},[17,18,20],"h2",{"id":19},"ресурсы-подов","Ресурсы подов",[22,23,25],"h3",{"id":24},"requests-и-limits","Requests и Limits",[11,27,28],{},"Каждому контейнеру в Pod'е можно задать два параметра ресурсов: requests и limits.",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-yaml shiki shiki-themes github-dark","containers:\n- name: app\n  image: myapp:1.0\n  resources:\n    requests:                    # гарантированный минимум\n      cpu: 250m                  # 0.25 ядра\n      memory: 128Mi              # 128 мебибайт\n    limits:                      # максимальный потолок\n      cpu: 500m                  # 0.5 ядра\n      memory: 256Mi              # 256 мебибайт\n","yaml","",[37,38,39,52,68,79,87,100,114,128,140,153],"code",{"__ignoreMap":35},[40,41,44,48],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"s4JwU","containers",[40,49,51],{"class":50},"s95oV",":\n",[40,53,55,58,61,64],{"class":42,"line":54},2,[40,56,57],{"class":50},"- ",[40,59,60],{"class":46},"name",[40,62,63],{"class":50},": ",[40,65,67],{"class":66},"sU2Wk","app\n",[40,69,71,74,76],{"class":42,"line":70},3,[40,72,73],{"class":46},"  image",[40,75,63],{"class":50},[40,77,78],{"class":66},"myapp:1.0\n",[40,80,82,85],{"class":42,"line":81},4,[40,83,84],{"class":46},"  resources",[40,86,51],{"class":50},[40,88,90,93,96],{"class":42,"line":89},5,[40,91,92],{"class":46},"    requests",[40,94,95],{"class":50},":                    ",[40,97,99],{"class":98},"sAwPA","# гарантированный минимум\n",[40,101,103,106,108,111],{"class":42,"line":102},6,[40,104,105],{"class":46},"      cpu",[40,107,63],{"class":50},[40,109,110],{"class":66},"250m",[40,112,113],{"class":98},"                  # 0.25 ядра\n",[40,115,117,120,122,125],{"class":42,"line":116},7,[40,118,119],{"class":46},"      memory",[40,121,63],{"class":50},[40,123,124],{"class":66},"128Mi",[40,126,127],{"class":98},"              # 128 мебибайт\n",[40,129,131,134,137],{"class":42,"line":130},8,[40,132,133],{"class":46},"    limits",[40,135,136],{"class":50},":                      ",[40,138,139],{"class":98},"# максимальный потолок\n",[40,141,143,145,147,150],{"class":42,"line":142},9,[40,144,105],{"class":46},[40,146,63],{"class":50},[40,148,149],{"class":66},"500m",[40,151,152],{"class":98},"                  # 0.5 ядра\n",[40,154,156,158,160,163],{"class":42,"line":155},10,[40,157,119],{"class":46},[40,159,63],{"class":50},[40,161,162],{"class":66},"256Mi",[40,164,165],{"class":98},"              # 256 мебибайт\n",[11,167,168,172,173,176,177,180],{},[169,170,171],"strong",{},"requests"," -- это гарантированный минимум ресурсов. Scheduler использует requests для выбора ноды: на ноде должно быть достаточно свободных ресурсов (",[37,174,175],{},"Allocatable - sum(requests) >= new request","). Контейнер гарантированно получает эти ресурсы. На уровне cgroups requests транслируются в ",[37,178,179],{},"cpu.shares"," (пропорциональное распределение CPU).",[11,182,183,186,187,190,191,194],{},[169,184,185],{},"limits"," -- это максимальный потолок. Контейнер не может использовать больше. На уровне cgroups: ",[37,188,189],{},"cpu.cfs_quota_us"," (hard cap для CPU), ",[37,192,193],{},"memory.limit_in_bytes"," для памяти.",[11,196,197],{},"Важные правила:",[199,200,201,208],"ul",{},[202,203,204,207],"li",{},[37,205,206],{},"requests > limits"," -- ошибка валидации, манифест не будет принят",[202,209,210,212,213,215],{},[37,211,185],{}," заданы без ",[37,214,171],{}," -- requests автоматически устанавливаются равными limits",[22,217,219],{"id":218},"единицы-измерения","Единицы измерения",[11,221,222],{},[169,223,224],{},"CPU:",[226,227,228,241],"table",{},[229,230,231],"thead",{},[232,233,234,238],"tr",{},[235,236,237],"th",{},"Значение",[235,239,240],{},"Описание",[242,243,244,253,261,268],"tbody",{},[232,245,246,250],{},[247,248,249],"td",{},"1",[247,251,252],{},"1 vCPU \u002F 1 hyperthread",[232,254,255,258],{},[247,256,257],{},"100m",[247,259,260],{},"0.1 ядра (m = millicores)",[232,262,263,265],{},[247,264,110],{},[247,266,267],{},"0.25 ядра",[232,269,270,272],{},[247,271,149],{},[247,273,274],{},"0.5 ядра",[11,276,277],{},[169,278,279],{},"Memory:",[226,281,282,290],{},[229,283,284],{},[232,285,286,288],{},[235,287,237],{},[235,289,240],{},[242,291,292,299,307],{},[232,293,294,296],{},[247,295,124],{},[247,297,298],{},"128 мебибайт (1 Mi = 1024^2 bytes)",[232,300,301,304],{},[247,302,303],{},"1Gi",[247,305,306],{},"1 гибибайт",[232,308,309,312],{},[247,310,311],{},"128M",[247,313,314],{},"128 мегабайт (1 M = 1000^2 bytes)",[11,316,317,320],{},[169,318,319],{},"128Mi и 128M — не одно и то же"," (разница около 5%). В production используйте Mi\u002FGi для предсказуемости.",[22,322,324],{"id":323},"cpu-vs-memory-принципиальная-разница","CPU vs Memory: принципиальная разница",[11,326,327,330,331,334],{},[169,328,329],{},"CPU — сжимаемый (compressible) ресурс."," Если контейнер превышает limit, происходит CFS throttling: процесс замедляется, но не убивается. Симптомы: высокая latency, медленные ответы. Метрика для мониторинга: ",[37,332,333],{},"container_cpu_cfs_throttled_periods_total",".",[11,336,337,340],{},[169,338,339],{},"Memory — несжимаемый (incompressible) ресурс."," Если контейнер превышает limit, происходит OOM kill: контейнер убивается с exit code 137 (SIGKILL). Pod status становится OOMKilled. Дальнейшее поведение определяется restartPolicy.",[11,342,343],{},"Если Pod превышает requests, но не limits — он будет убит только при давлении на ноде (когда суммарное потребление памяти на ноде превышает доступное).",[11,345,346,347,350],{},"Для Go-приложений: используйте ",[37,348,349],{},"GOMEMLIMIT"," (начиная с Go 1.19) — это помогает GC оставаться в рамках memory limit и избегать OOM kill.",[11,352,353,354,357],{},"Для Java: ",[37,355,356],{},"-Xmx"," должен быть строго меньше memory limit, иначе JVM использует больше памяти, чем limit (за счёт off-heap, metaspace и т.д.), и получит OOM kill.",[14,359],{},[22,361,363],{"id":362},"qos-классы-quality-of-service","QoS классы (Quality of Service)",[11,365,366],{},"Kubernetes автоматически назначает QoS class каждому Pod'у на основе заданных requests и limits. QoS class определяет приоритет при нехватке памяти на ноде.",[22,368,370],{"id":369},"guaranteed","Guaranteed",[11,372,373],{},"Все контейнеры в Pod'е имеют requests == limits для CPU и memory:",[30,375,377],{"className":32,"code":376,"language":34,"meta":35,"style":35},"resources:\n  requests:\n    cpu: 500m\n    memory: 256Mi\n  limits:\n    cpu: 500m          # == requests\n    memory: 256Mi      # == requests\n",[37,378,379,386,393,403,413,420,431],{"__ignoreMap":35},[40,380,381,384],{"class":42,"line":43},[40,382,383],{"class":46},"resources",[40,385,51],{"class":50},[40,387,388,391],{"class":42,"line":54},[40,389,390],{"class":46},"  requests",[40,392,51],{"class":50},[40,394,395,398,400],{"class":42,"line":70},[40,396,397],{"class":46},"    cpu",[40,399,63],{"class":50},[40,401,402],{"class":66},"500m\n",[40,404,405,408,410],{"class":42,"line":81},[40,406,407],{"class":46},"    memory",[40,409,63],{"class":50},[40,411,412],{"class":66},"256Mi\n",[40,414,415,418],{"class":42,"line":89},[40,416,417],{"class":46},"  limits",[40,419,51],{"class":50},[40,421,422,424,426,428],{"class":42,"line":102},[40,423,397],{"class":46},[40,425,63],{"class":50},[40,427,149],{"class":66},[40,429,430],{"class":98},"          # == requests\n",[40,432,433,435,437,439],{"class":42,"line":116},[40,434,407],{"class":46},[40,436,63],{"class":50},[40,438,162],{"class":66},[40,440,441],{"class":98},"      # == requests\n",[11,443,444],{},"Последний на убой при OOM на ноде. Подходит для баз данных и критических сервисов.",[22,446,448],{"id":447},"burstable","Burstable",[11,450,451],{},"Хотя бы один контейнер имеет requests или limits, но requests != limits (или заданы не для всех ресурсов):",[30,453,455],{"className":32,"code":454,"language":34,"meta":35,"style":35},"resources:\n  requests:\n    cpu: 250m\n    memory: 128Mi\n  limits:\n    cpu: 500m          # != requests\n    memory: 256Mi\n",[37,456,457,463,469,478,487,493,504],{"__ignoreMap":35},[40,458,459,461],{"class":42,"line":43},[40,460,383],{"class":46},[40,462,51],{"class":50},[40,464,465,467],{"class":42,"line":54},[40,466,390],{"class":46},[40,468,51],{"class":50},[40,470,471,473,475],{"class":42,"line":70},[40,472,397],{"class":46},[40,474,63],{"class":50},[40,476,477],{"class":66},"250m\n",[40,479,480,482,484],{"class":42,"line":81},[40,481,407],{"class":46},[40,483,63],{"class":50},[40,485,486],{"class":66},"128Mi\n",[40,488,489,491],{"class":42,"line":89},[40,490,417],{"class":46},[40,492,51],{"class":50},[40,494,495,497,499,501],{"class":42,"line":102},[40,496,397],{"class":46},[40,498,63],{"class":50},[40,500,149],{"class":66},[40,502,503],{"class":98},"          # != requests\n",[40,505,506,508,510],{"class":42,"line":116},[40,507,407],{"class":46},[40,509,63],{"class":50},[40,511,412],{"class":66},[11,513,514],{},"Средний приоритет при OOM. При нехватке памяти убиваются по проценту превышения requests.",[22,516,518],{"id":517},"besteffort","BestEffort",[11,520,521],{},"Ни один контейнер не задаёт requests или limits:",[30,523,525],{"className":32,"code":524,"language":34,"meta":35,"style":35},"# Секция resources отсутствует полностью\n",[37,526,527],{"__ignoreMap":35},[40,528,529],{"class":42,"line":43},[40,530,524],{"class":98},[11,532,533],{},"Первый на убой при OOM на ноде. Подходит только для некритичных batch jobs.",[11,535,536,539],{},[169,537,538],{},"Приоритет убийства при OOM:"," BestEffort -> Burstable (по проценту превышения requests) -> Guaranteed.",[14,541],{},[22,543,545],{"id":544},"limitrange-и-resourcequota","LimitRange и ResourceQuota",[22,547,549],{"id":548},"limitrange-defaultы-для-namespace","LimitRange — default'ы для namespace",[11,551,552],{},"LimitRange устанавливает значения requests\u002Flimits по умолчанию для контейнеров в namespace. Если разработчик не указал ресурсы — они будут подставлены автоматически через admission controller LimitRanger.",[30,554,556],{"className":32,"code":555,"language":34,"meta":35,"style":35},"apiVersion: v1\nkind: LimitRange\nmetadata:\n  name: defaults\nspec:\n  limits:\n  - default:                     # default limits\n      cpu: 500m\n      memory: 256Mi\n    defaultRequest:              # default requests\n      cpu: 100m\n      memory: 128Mi\n    type: Container\n",[37,557,558,568,578,585,595,602,608,622,630,638,649,659,668],{"__ignoreMap":35},[40,559,560,563,565],{"class":42,"line":43},[40,561,562],{"class":46},"apiVersion",[40,564,63],{"class":50},[40,566,567],{"class":66},"v1\n",[40,569,570,573,575],{"class":42,"line":54},[40,571,572],{"class":46},"kind",[40,574,63],{"class":50},[40,576,577],{"class":66},"LimitRange\n",[40,579,580,583],{"class":42,"line":70},[40,581,582],{"class":46},"metadata",[40,584,51],{"class":50},[40,586,587,590,592],{"class":42,"line":81},[40,588,589],{"class":46},"  name",[40,591,63],{"class":50},[40,593,594],{"class":66},"defaults\n",[40,596,597,600],{"class":42,"line":89},[40,598,599],{"class":46},"spec",[40,601,51],{"class":50},[40,603,604,606],{"class":42,"line":102},[40,605,417],{"class":46},[40,607,51],{"class":50},[40,609,610,613,616,619],{"class":42,"line":116},[40,611,612],{"class":50},"  - ",[40,614,615],{"class":46},"default",[40,617,618],{"class":50},":                     ",[40,620,621],{"class":98},"# default limits\n",[40,623,624,626,628],{"class":42,"line":130},[40,625,105],{"class":46},[40,627,63],{"class":50},[40,629,402],{"class":66},[40,631,632,634,636],{"class":42,"line":142},[40,633,119],{"class":46},[40,635,63],{"class":50},[40,637,412],{"class":66},[40,639,640,643,646],{"class":42,"line":155},[40,641,642],{"class":46},"    defaultRequest",[40,644,645],{"class":50},":              ",[40,647,648],{"class":98},"# default requests\n",[40,650,652,654,656],{"class":42,"line":651},11,[40,653,105],{"class":46},[40,655,63],{"class":50},[40,657,658],{"class":66},"100m\n",[40,660,662,664,666],{"class":42,"line":661},12,[40,663,119],{"class":46},[40,665,63],{"class":50},[40,667,486],{"class":66},[40,669,671,674,676],{"class":42,"line":670},13,[40,672,673],{"class":46},"    type",[40,675,63],{"class":50},[40,677,678],{"class":66},"Container\n",[22,680,682],{"id":681},"resourcequota-лимиты-на-весь-namespace","ResourceQuota — лимиты на весь namespace",[30,684,686],{"className":32,"code":685,"language":34,"meta":35,"style":35},"apiVersion: v1\nkind: ResourceQuota\nmetadata:\n  name: quota\nspec:\n  hard:\n    requests.cpu: \"10\"           # сумма всех requests CPU в namespace\n    requests.memory: 20Gi\n    limits.cpu: \"20\"\n    limits.memory: 40Gi\n    pods: \"50\"                   # макс. Pod'ов в namespace\n",[37,687,688,696,705,711,720,726,733,746,756,766,776],{"__ignoreMap":35},[40,689,690,692,694],{"class":42,"line":43},[40,691,562],{"class":46},[40,693,63],{"class":50},[40,695,567],{"class":66},[40,697,698,700,702],{"class":42,"line":54},[40,699,572],{"class":46},[40,701,63],{"class":50},[40,703,704],{"class":66},"ResourceQuota\n",[40,706,707,709],{"class":42,"line":70},[40,708,582],{"class":46},[40,710,51],{"class":50},[40,712,713,715,717],{"class":42,"line":81},[40,714,589],{"class":46},[40,716,63],{"class":50},[40,718,719],{"class":66},"quota\n",[40,721,722,724],{"class":42,"line":89},[40,723,599],{"class":46},[40,725,51],{"class":50},[40,727,728,731],{"class":42,"line":102},[40,729,730],{"class":46},"  hard",[40,732,51],{"class":50},[40,734,735,738,740,743],{"class":42,"line":116},[40,736,737],{"class":46},"    requests.cpu",[40,739,63],{"class":50},[40,741,742],{"class":66},"\"10\"",[40,744,745],{"class":98},"           # сумма всех requests CPU в namespace\n",[40,747,748,751,753],{"class":42,"line":130},[40,749,750],{"class":46},"    requests.memory",[40,752,63],{"class":50},[40,754,755],{"class":66},"20Gi\n",[40,757,758,761,763],{"class":42,"line":142},[40,759,760],{"class":46},"    limits.cpu",[40,762,63],{"class":50},[40,764,765],{"class":66},"\"20\"\n",[40,767,768,771,773],{"class":42,"line":155},[40,769,770],{"class":46},"    limits.memory",[40,772,63],{"class":50},[40,774,775],{"class":66},"40Gi\n",[40,777,778,781,783,786],{"class":42,"line":651},[40,779,780],{"class":46},"    pods",[40,782,63],{"class":50},[40,784,785],{"class":66},"\"50\"",[40,787,788],{"class":98},"                   # макс. Pod'ов в namespace\n",[11,790,791],{},"Если ResourceQuota активна, все Pod'ы обязаны указывать requests\u002Flimits (или LimitRange должен подставлять defaults). Без этого Pod не будет создан.",[22,793,795],{"id":794},"best-practices","Best Practices",[199,797,798,804,807,810,813,816,819,822,825],{},[202,799,800,803],{},[169,801,802],{},"Всегда"," задавайте requests — scheduler нуждается в них для корректного placement",[202,805,806],{},"Задавайте memory limits — защита от OOM всей ноды",[202,808,809],{},"Для production: requests == limits (QoS Guaranteed)",[202,811,812],{},"Для dev\u002Fstaging: Burstable допустим для экономии ресурсов",[202,814,815],{},"Используйте LimitRange для default'ов в namespace",[202,817,818],{},"Используйте ResourceQuota для мультитенантности",[202,820,821],{},"Не задавайте CPU limits слишком низко — throttling приводит к росту latency. Многие команды сознательно убирают CPU limits, оставляя только requests",[202,823,824],{},"Не запускайте BestEffort Pod'ы в production",[202,826,827],{},"Не путайте Mi и M",[14,829],{},[17,831,833],{"id":832},"автомасштабирование","Автомасштабирование",[22,835,837],{"id":836},"hpa-горизонтальное-автомасштабирование","HPA — горизонтальное автомасштабирование",[11,839,840],{},"HPA (Horizontal Pod Autoscaler) автоматически меняет количество реплик в Deployment или StatefulSet на основе метрик. Это основной инструмент масштабирования в Kubernetes.",[22,842,844],{"id":843},"манифест-hpa","Манифест HPA",[30,846,848],{"className":32,"code":847,"language":34,"meta":35,"style":35},"apiVersion: autoscaling\u002Fv2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: kiada\nspec:\n  scaleTargetRef:\n    apiVersion: apps\u002Fv1\n    kind: Deployment\n    name: kiada\n  minReplicas: 2\n  maxReplicas: 10\n  metrics:\n  - type: Resource                   # встроенные CPU\u002Fmemory метрики\n    resource:\n      name: cpu\n      target:\n        type: Utilization\n        averageUtilization: 70       # target: 70% от requests\n  - type: Resource\n    resource:\n      name: memory\n      target:\n        type: AverageValue\n        averageValue: 500Mi\n  behavior:                          # настройка скорости scaling'а\n    scaleUp:\n      stabilizationWindowSeconds: 60\n    scaleDown:\n      stabilizationWindowSeconds: 300  # 5 мин перед scale down\n",[37,849,850,859,868,874,883,889,896,906,916,925,936,946,953,968,976,987,995,1006,1020,1032,1039,1049,1056,1066,1077,1089,1097,1108,1116],{"__ignoreMap":35},[40,851,852,854,856],{"class":42,"line":43},[40,853,562],{"class":46},[40,855,63],{"class":50},[40,857,858],{"class":66},"autoscaling\u002Fv2\n",[40,860,861,863,865],{"class":42,"line":54},[40,862,572],{"class":46},[40,864,63],{"class":50},[40,866,867],{"class":66},"HorizontalPodAutoscaler\n",[40,869,870,872],{"class":42,"line":70},[40,871,582],{"class":46},[40,873,51],{"class":50},[40,875,876,878,880],{"class":42,"line":81},[40,877,589],{"class":46},[40,879,63],{"class":50},[40,881,882],{"class":66},"kiada\n",[40,884,885,887],{"class":42,"line":89},[40,886,599],{"class":46},[40,888,51],{"class":50},[40,890,891,894],{"class":42,"line":102},[40,892,893],{"class":46},"  scaleTargetRef",[40,895,51],{"class":50},[40,897,898,901,903],{"class":42,"line":116},[40,899,900],{"class":46},"    apiVersion",[40,902,63],{"class":50},[40,904,905],{"class":66},"apps\u002Fv1\n",[40,907,908,911,913],{"class":42,"line":130},[40,909,910],{"class":46},"    kind",[40,912,63],{"class":50},[40,914,915],{"class":66},"Deployment\n",[40,917,918,921,923],{"class":42,"line":142},[40,919,920],{"class":46},"    name",[40,922,63],{"class":50},[40,924,882],{"class":66},[40,926,927,930,932],{"class":42,"line":155},[40,928,929],{"class":46},"  minReplicas",[40,931,63],{"class":50},[40,933,935],{"class":934},"sDLfK","2\n",[40,937,938,941,943],{"class":42,"line":651},[40,939,940],{"class":46},"  maxReplicas",[40,942,63],{"class":50},[40,944,945],{"class":934},"10\n",[40,947,948,951],{"class":42,"line":661},[40,949,950],{"class":46},"  metrics",[40,952,51],{"class":50},[40,954,955,957,960,962,965],{"class":42,"line":670},[40,956,612],{"class":50},[40,958,959],{"class":46},"type",[40,961,63],{"class":50},[40,963,964],{"class":66},"Resource",[40,966,967],{"class":98},"                   # встроенные CPU\u002Fmemory метрики\n",[40,969,971,974],{"class":42,"line":970},14,[40,972,973],{"class":46},"    resource",[40,975,51],{"class":50},[40,977,979,982,984],{"class":42,"line":978},15,[40,980,981],{"class":46},"      name",[40,983,63],{"class":50},[40,985,986],{"class":66},"cpu\n",[40,988,990,993],{"class":42,"line":989},16,[40,991,992],{"class":46},"      target",[40,994,51],{"class":50},[40,996,998,1001,1003],{"class":42,"line":997},17,[40,999,1000],{"class":46},"        type",[40,1002,63],{"class":50},[40,1004,1005],{"class":66},"Utilization\n",[40,1007,1009,1012,1014,1017],{"class":42,"line":1008},18,[40,1010,1011],{"class":46},"        averageUtilization",[40,1013,63],{"class":50},[40,1015,1016],{"class":934},"70",[40,1018,1019],{"class":98},"       # target: 70% от requests\n",[40,1021,1023,1025,1027,1029],{"class":42,"line":1022},19,[40,1024,612],{"class":50},[40,1026,959],{"class":46},[40,1028,63],{"class":50},[40,1030,1031],{"class":66},"Resource\n",[40,1033,1035,1037],{"class":42,"line":1034},20,[40,1036,973],{"class":46},[40,1038,51],{"class":50},[40,1040,1042,1044,1046],{"class":42,"line":1041},21,[40,1043,981],{"class":46},[40,1045,63],{"class":50},[40,1047,1048],{"class":66},"memory\n",[40,1050,1052,1054],{"class":42,"line":1051},22,[40,1053,992],{"class":46},[40,1055,51],{"class":50},[40,1057,1059,1061,1063],{"class":42,"line":1058},23,[40,1060,1000],{"class":46},[40,1062,63],{"class":50},[40,1064,1065],{"class":66},"AverageValue\n",[40,1067,1069,1072,1074],{"class":42,"line":1068},24,[40,1070,1071],{"class":46},"        averageValue",[40,1073,63],{"class":50},[40,1075,1076],{"class":66},"500Mi\n",[40,1078,1080,1083,1086],{"class":42,"line":1079},25,[40,1081,1082],{"class":46},"  behavior",[40,1084,1085],{"class":50},":                          ",[40,1087,1088],{"class":98},"# настройка скорости scaling'а\n",[40,1090,1092,1095],{"class":42,"line":1091},26,[40,1093,1094],{"class":46},"    scaleUp",[40,1096,51],{"class":50},[40,1098,1100,1103,1105],{"class":42,"line":1099},27,[40,1101,1102],{"class":46},"      stabilizationWindowSeconds",[40,1104,63],{"class":50},[40,1106,1107],{"class":934},"60\n",[40,1109,1111,1114],{"class":42,"line":1110},28,[40,1112,1113],{"class":46},"    scaleDown",[40,1115,51],{"class":50},[40,1117,1119,1121,1123,1126],{"class":42,"line":1118},29,[40,1120,1102],{"class":46},[40,1122,63],{"class":50},[40,1124,1125],{"class":934},"300",[40,1127,1128],{"class":98},"  # 5 мин перед scale down\n",[22,1130,1132],{"id":1131},"как-работает-hpa","Как работает HPA",[1134,1135,1136,1143,1149],"ol",{},[202,1137,1138,1139,1142],{},"HPA controller проверяет метрики каждые 15 секунд (",[37,1140,1141],{},"--horizontal-pod-autoscaler-sync-period",")",[202,1144,1145,1146],{},"Вычисляет желаемое количество реплик: ",[37,1147,1148],{},"ceil(current * (currentMetric \u002F targetMetric))",[202,1150,1151],{},"Обновляет replicas в Deployment\u002FStatefulSet",[11,1153,1154],{},[169,1155,1156],{},"Пример расчёта:",[199,1158,1159,1162],{},[202,1160,1161],{},"current replicas: 3, current CPU: 90%, target CPU: 70%",[202,1163,1164],{},"desired = ceil(3 * (90\u002F70)) = ceil(3.86) = 4",[11,1166,1167,1170],{},[169,1168,1169],{},"Без requests HPA не работает"," -- он считает процент от requests. Если requests не заданы, HPA не сможет вычислить utilization.",[22,1172,1174],{"id":1173},"типы-метрик","Типы метрик",[226,1176,1177,1189],{},[229,1178,1179],{},[232,1180,1181,1184,1186],{},[235,1182,1183],{},"Тип",[235,1185,240],{},[235,1187,1188],{},"Источник",[242,1190,1191,1201,1212,1222],{},[232,1192,1193,1195,1198],{},[247,1194,964],{},[247,1196,1197],{},"CPU, memory",[247,1199,1200],{},"Metrics Server",[232,1202,1203,1206,1209],{},[247,1204,1205],{},"Pods",[247,1207,1208],{},"Custom метрики на Pod (requests\u002Fsec, queue length)",[247,1210,1211],{},"Prometheus Adapter",[232,1213,1214,1217,1220],{},[247,1215,1216],{},"Object",[247,1218,1219],{},"Метрики K8s объекта (Ingress requests, Service latency)",[247,1221,1211],{},[232,1223,1224,1227,1230],{},[247,1225,1226],{},"External",[247,1228,1229],{},"Внешние метрики (SQS queue depth, Pub\u002FSub messages)",[247,1231,1232],{},"Custom adapter",[22,1234,1236],{"id":1235},"полезные-команды","Полезные команды",[30,1238,1242],{"className":1239,"code":1240,"language":1241,"meta":35,"style":35},"language-bash shiki shiki-themes github-dark","kubectl get hpa                    # список HPA\nkubectl describe hpa kiada         # детали, текущие метрики, события\nkubectl top pods                   # текущее потребление (нужен Metrics Server)\nkubectl top nodes                  # потребление на нодах\n","bash",[37,1243,1244,1259,1274,1287],{"__ignoreMap":35},[40,1245,1246,1250,1253,1256],{"class":42,"line":43},[40,1247,1249],{"class":1248},"svObZ","kubectl",[40,1251,1252],{"class":66}," get",[40,1254,1255],{"class":66}," hpa",[40,1257,1258],{"class":98},"                    # список HPA\n",[40,1260,1261,1263,1266,1268,1271],{"class":42,"line":54},[40,1262,1249],{"class":1248},[40,1264,1265],{"class":66}," describe",[40,1267,1255],{"class":66},[40,1269,1270],{"class":66}," kiada",[40,1272,1273],{"class":98},"         # детали, текущие метрики, события\n",[40,1275,1276,1278,1281,1284],{"class":42,"line":70},[40,1277,1249],{"class":1248},[40,1279,1280],{"class":66}," top",[40,1282,1283],{"class":66}," pods",[40,1285,1286],{"class":98},"                   # текущее потребление (нужен Metrics Server)\n",[40,1288,1289,1291,1293,1296],{"class":42,"line":81},[40,1290,1249],{"class":1248},[40,1292,1280],{"class":66},[40,1294,1295],{"class":66}," nodes",[40,1297,1298],{"class":98},"                  # потребление на нодах\n",[14,1300],{},[22,1302,1304],{"id":1303},"custom-metrics-для-hpa","Custom Metrics для HPA",[11,1306,1307],{},"Metrics Server предоставляет только CPU и memory. Для масштабирования по бизнес-метрикам нужен metrics adapter.",[22,1309,1211],{"id":1310},"prometheus-adapter",[11,1312,1313,1314,1317],{},"Собирает метрики из Prometheus, регистрирует ",[37,1315,1316],{},"custom.metrics.k8s.io"," API. HPA читает custom метрики через этот API.",[30,1319,1321],{"className":32,"code":1320,"language":34,"meta":35,"style":35},"# Пример HPA с custom metric:\nmetrics:\n- type: Pods\n  pods:\n    metric:\n      name: http_requests_per_second\n    target:\n      type: AverageValue\n      averageValue: \"100\"          # target: 100 rps на Pod\n",[37,1322,1323,1328,1335,1346,1353,1360,1369,1376,1385],{"__ignoreMap":35},[40,1324,1325],{"class":42,"line":43},[40,1326,1327],{"class":98},"# Пример HPA с custom metric:\n",[40,1329,1330,1333],{"class":42,"line":54},[40,1331,1332],{"class":46},"metrics",[40,1334,51],{"class":50},[40,1336,1337,1339,1341,1343],{"class":42,"line":70},[40,1338,57],{"class":50},[40,1340,959],{"class":46},[40,1342,63],{"class":50},[40,1344,1345],{"class":66},"Pods\n",[40,1347,1348,1351],{"class":42,"line":81},[40,1349,1350],{"class":46},"  pods",[40,1352,51],{"class":50},[40,1354,1355,1358],{"class":42,"line":89},[40,1356,1357],{"class":46},"    metric",[40,1359,51],{"class":50},[40,1361,1362,1364,1366],{"class":42,"line":102},[40,1363,981],{"class":46},[40,1365,63],{"class":50},[40,1367,1368],{"class":66},"http_requests_per_second\n",[40,1370,1371,1374],{"class":42,"line":116},[40,1372,1373],{"class":46},"    target",[40,1375,51],{"class":50},[40,1377,1378,1381,1383],{"class":42,"line":130},[40,1379,1380],{"class":46},"      type",[40,1382,63],{"class":50},[40,1384,1065],{"class":66},[40,1386,1387,1390,1392,1395],{"class":42,"line":142},[40,1388,1389],{"class":46},"      averageValue",[40,1391,63],{"class":50},[40,1393,1394],{"class":66},"\"100\"",[40,1396,1397],{"class":98},"          # target: 100 rps на Pod\n",[22,1399,1401],{"id":1400},"keda-kubernetes-event-driven-autoscaling","KEDA (Kubernetes Event-Driven Autoscaling)",[11,1403,1404],{},"KEDA — отдельный controller, который может управлять HPA или работать напрямую. Ключевые преимущества:",[199,1406,1407,1410,1416],{},[202,1408,1409],{},"Поддерживает 50+ event sources из коробки: Kafka, RabbitMQ, Redis, PostgreSQL, AWS SQS, Azure Queue",[202,1411,1412,1413,1142],{},"Умеет scale to zero (HPA не умеет: ",[37,1414,1415],{},"minReplicas >= 1",[202,1417,1418],{},"Декларативная конфигурация через CRD (ScaledObject, ScaledJob)",[11,1420,1421],{},"KEDA особенно полезен для event-driven архитектур, где нагрузка определяется глубиной очередей, а не CPU\u002Fmemory.",[14,1423],{},[22,1425,1427],{"id":1426},"vpa-вертикальное-автомасштабирование","VPA — вертикальное автомасштабирование",[11,1429,1430],{},"VPA (Vertical Pod Autoscaler) автоматически подбирает requests и limits для контейнеров на основе реального потребления. Не встроен в Kubernetes — устанавливается отдельно.",[22,1432,1434],{"id":1433},"три-компонента-vpa","Три компонента VPA",[226,1436,1437,1446],{},[229,1438,1439],{},[232,1440,1441,1444],{},[235,1442,1443],{},"Компонент",[235,1445,240],{},[242,1447,1448,1456,1464],{},[232,1449,1450,1453],{},[247,1451,1452],{},"Recommender",[247,1454,1455],{},"Анализирует потребление, рекомендует requests\u002Flimits",[232,1457,1458,1461],{},[247,1459,1460],{},"Updater",[247,1462,1463],{},"Evict'ит Pod'ы для применения рекомендаций",[232,1465,1466,1469],{},[247,1467,1468],{},"Admission Controller",[247,1470,1471],{},"Устанавливает requests при создании Pod'а",[22,1473,1475],{"id":1474},"режимы-работы","Режимы работы",[226,1477,1478,1487],{},[229,1479,1480],{},[232,1481,1482,1485],{},[235,1483,1484],{},"Режим",[235,1486,240],{},[242,1488,1489,1500,1508,1516],{},[232,1490,1491,1494],{},[247,1492,1493],{},"Off",[247,1495,1496,1497,1142],{},"Только рекомендации (",[37,1498,1499],{},"kubectl describe vpa",[232,1501,1502,1505],{},[247,1503,1504],{},"Initial",[247,1506,1507],{},"Устанавливает requests только при создании Pod'а",[232,1509,1510,1513],{},[247,1511,1512],{},"Recreate",[247,1514,1515],{},"Evict'ит Pod'ы для обновления requests",[232,1517,1518,1521],{},[247,1519,1520],{},"Auto",[247,1522,1523],{},"Сейчас эквивалентен Recreate; в будущем планируется in-place update",[11,1525,1526,1527,1530],{},"Важно: ",[169,1528,1529],{},"VPA + HPA по CPU\u002Fmemory одновременно = конфликт",". Оба будут пытаться влиять на поведение Pod'а, создавая feedback loop. Безопасные комбинации:",[199,1532,1533,1536],{},[202,1534,1535],{},"VPA для requests + HPA по custom metrics (requests\u002Fsec, queue depth)",[202,1537,1538],{},"Multidimensional Pod Autoscaler (MPA) — специализированное решение",[14,1540],{},[22,1542,1544],{"id":1543},"cluster-autoscaler-масштабирование-нод","Cluster Autoscaler — масштабирование нод",[11,1546,1547],{},"Cluster Autoscaler работает на уровне нод, а не Pod'ов.",[22,1549,1551],{"id":1550},"scale-up","Scale UP",[1134,1553,1554,1557,1560,1563],{},[202,1555,1556],{},"Pod в статусе Pending (не хватает ресурсов на существующих нодах)",[202,1558,1559],{},"Cluster Autoscaler видит Pending Pod",[202,1561,1562],{},"Запрашивает новую ноду у cloud provider (ASG, MIG, Node Pool)",[202,1564,1565],{},"Нода создаётся, Pod scheduling выполняется",[22,1567,1569],{"id":1568},"scale-down","Scale DOWN",[1134,1571,1572,1575,1578,1581],{},[202,1573,1574],{},"Нода недозагружена (менее 50% utilization по requests)",[202,1576,1577],{},"Все Pod'ы на ноде могут быть перемещены на другие ноды",[202,1579,1580],{},"Нода помечается для удаления",[202,1582,1583],{},"Pod'ы evict'ятся, нода удаляется",[11,1585,1586,1587,1590],{},"Scale down ",[169,1588,1589],{},"не произойдёт",", если на ноде есть:",[199,1592,1593,1596,1599,1602],{},[202,1594,1595],{},"Pod без controller'а (не Deployment\u002FReplicaSet\u002FStatefulSet)",[202,1597,1598],{},"Pod с local storage (emptyDir с данными)",[202,1600,1601],{},"Pod с PodDisruptionBudget, блокирующим eviction",[202,1603,1604,1605],{},"Pod с аннотацией ",[37,1606,1607],{},"cluster-autoscaler.kubernetes.io\u002Fsafe-to-evict: \"false\"",[11,1609,1610,1611,1614],{},"Cluster Autoscaler работает ",[169,1612,1613],{},"только с cloud providers",": GKE, EKS, AKS. Для bare metal используются Karpenter (AWS) или другие решения.",[22,1616,1618],{"id":1617},"взаимодействие-hpa-cluster-autoscaler","Взаимодействие HPA + Cluster Autoscaler",[30,1620,1625],{"className":1621,"code":1623,"language":1624},[1622],"language-text","                    +------------------+\n  Metrics Server -->|  HPA Controller  |--> изменяет replicas\n                    +------------------+    в Deployment\n                                            |\n                                            v\n                                      Pod Pending?\n                                      (нет ресурсов)\n                                            |\n                    +------------------+    v\n  Cloud Provider \u003C--|Cluster Autoscaler|\u003C-- да -> добавить ноду\n                    +------------------+\n","text",[37,1626,1623],{"__ignoreMap":35},[11,1628,1629],{},"Цепочка событий:",[1134,1631,1632,1635,1638,1641],{},[202,1633,1634],{},"Нагрузка растёт — HPA увеличивает replicas",[202,1636,1637],{},"Новые Pod'ы Pending (нет места на нодах) — Cluster Autoscaler добавляет ноды",[202,1639,1640],{},"Нагрузка падает — HPA уменьшает replicas",[202,1642,1643],{},"Ноды пустеют — Cluster Autoscaler удаляет ноды",[11,1645,1646],{},"VPA работает ортогонально: он меняет requests\u002Flimits, а не replicas.",[22,1648,1200],{"id":1649},"metrics-server",[11,1651,1652],{},"Metrics Server — обязательный компонент для работы HPA и VPA. Собирает метрики CPU и memory с kubelet'ов каждой ноды через Metrics API. Это легковесный in-memory агрегатор, не предназначенный для долгосрочного хранения метрик.",[30,1654,1656],{"className":1239,"code":1655,"language":1241,"meta":35,"style":35},"kubectl top pods                   # текущее потребление Pod'ов\nkubectl top nodes                  # потребление на нодах\n",[37,1657,1658,1669],{"__ignoreMap":35},[40,1659,1660,1662,1664,1666],{"class":42,"line":43},[40,1661,1249],{"class":1248},[40,1663,1280],{"class":66},[40,1665,1283],{"class":66},[40,1667,1668],{"class":98},"                   # текущее потребление Pod'ов\n",[40,1670,1671,1673,1675,1677],{"class":42,"line":54},[40,1672,1249],{"class":1248},[40,1674,1280],{"class":66},[40,1676,1295],{"class":66},[40,1678,1298],{"class":98},[11,1680,1681,1682,1685],{},"Если ",[37,1683,1684],{},"kubectl top"," возвращает ошибку \"Metrics API not available\" — Metrics Server не установлен.",[14,1687],{},[17,1689,1691],{"id":1690},"scheduling","Scheduling",[22,1693,1695],{"id":1694},"nodeselector-и-nodeaffinity","nodeSelector и nodeAffinity",[11,1697,1698],{},"Эти механизмы позволяют Pod'у указать, на каких нодах он хочет запускаться.",[22,1700,1702],{"id":1701},"nodeselector-простой-способ","nodeSelector — простой способ",[30,1704,1706],{"className":32,"code":1705,"language":34,"meta":35,"style":35},"spec:\n  nodeSelector:\n    disktype: ssd                # Pod только на ноды с label disktype=ssd\n    zone: us-east-1a\n",[37,1707,1708,1714,1721,1734],{"__ignoreMap":35},[40,1709,1710,1712],{"class":42,"line":43},[40,1711,599],{"class":46},[40,1713,51],{"class":50},[40,1715,1716,1719],{"class":42,"line":54},[40,1717,1718],{"class":46},"  nodeSelector",[40,1720,51],{"class":50},[40,1722,1723,1726,1728,1731],{"class":42,"line":70},[40,1724,1725],{"class":46},"    disktype",[40,1727,63],{"class":50},[40,1729,1730],{"class":66},"ssd",[40,1732,1733],{"class":98},"                # Pod только на ноды с label disktype=ssd\n",[40,1735,1736,1739,1741],{"class":42,"line":81},[40,1737,1738],{"class":46},"    zone",[40,1740,63],{"class":50},[40,1742,1743],{"class":66},"us-east-1a\n",[11,1745,1746],{},"Если нет ноды с указанными labels — Pod останется Pending.",[22,1748,1750],{"id":1749},"nodeaffinity-расширенный-вариант","nodeAffinity — расширенный вариант",[30,1752,1754],{"className":32,"code":1753,"language":34,"meta":35,"style":35},"spec:\n  affinity:\n    nodeAffinity:\n      requiredDuringSchedulingIgnoredDuringExecution:     # ОБЯЗАТЕЛЬНО (hard)\n        nodeSelectorTerms:\n        - matchExpressions:\n          - key: zone\n            operator: In                 # In, NotIn, Exists, DoesNotExist, Gt, Lt\n            values: [us-east-1a, us-east-1b]\n      preferredDuringSchedulingIgnoredDuringExecution:    # ЖЕЛАТЕЛЬНО (soft)\n      - weight: 80                       # 1-100, чем больше тем важнее\n        preference:\n          matchExpressions:\n          - key: disktype\n            operator: In\n            values: [ssd]\n",[37,1755,1756,1762,1769,1776,1787,1794,1804,1817,1830,1850,1861,1877,1884,1891,1902,1911],{"__ignoreMap":35},[40,1757,1758,1760],{"class":42,"line":43},[40,1759,599],{"class":46},[40,1761,51],{"class":50},[40,1763,1764,1767],{"class":42,"line":54},[40,1765,1766],{"class":46},"  affinity",[40,1768,51],{"class":50},[40,1770,1771,1774],{"class":42,"line":70},[40,1772,1773],{"class":46},"    nodeAffinity",[40,1775,51],{"class":50},[40,1777,1778,1781,1784],{"class":42,"line":81},[40,1779,1780],{"class":46},"      requiredDuringSchedulingIgnoredDuringExecution",[40,1782,1783],{"class":50},":     ",[40,1785,1786],{"class":98},"# ОБЯЗАТЕЛЬНО (hard)\n",[40,1788,1789,1792],{"class":42,"line":89},[40,1790,1791],{"class":46},"        nodeSelectorTerms",[40,1793,51],{"class":50},[40,1795,1796,1799,1802],{"class":42,"line":102},[40,1797,1798],{"class":50},"        - ",[40,1800,1801],{"class":46},"matchExpressions",[40,1803,51],{"class":50},[40,1805,1806,1809,1812,1814],{"class":42,"line":116},[40,1807,1808],{"class":50},"          - ",[40,1810,1811],{"class":46},"key",[40,1813,63],{"class":50},[40,1815,1816],{"class":66},"zone\n",[40,1818,1819,1822,1824,1827],{"class":42,"line":130},[40,1820,1821],{"class":46},"            operator",[40,1823,63],{"class":50},[40,1825,1826],{"class":66},"In",[40,1828,1829],{"class":98},"                 # In, NotIn, Exists, DoesNotExist, Gt, Lt\n",[40,1831,1832,1835,1838,1841,1844,1847],{"class":42,"line":142},[40,1833,1834],{"class":46},"            values",[40,1836,1837],{"class":50},": [",[40,1839,1840],{"class":66},"us-east-1a",[40,1842,1843],{"class":50},", ",[40,1845,1846],{"class":66},"us-east-1b",[40,1848,1849],{"class":50},"]\n",[40,1851,1852,1855,1858],{"class":42,"line":155},[40,1853,1854],{"class":46},"      preferredDuringSchedulingIgnoredDuringExecution",[40,1856,1857],{"class":50},":    ",[40,1859,1860],{"class":98},"# ЖЕЛАТЕЛЬНО (soft)\n",[40,1862,1863,1866,1869,1871,1874],{"class":42,"line":651},[40,1864,1865],{"class":50},"      - ",[40,1867,1868],{"class":46},"weight",[40,1870,63],{"class":50},[40,1872,1873],{"class":934},"80",[40,1875,1876],{"class":98},"                       # 1-100, чем больше тем важнее\n",[40,1878,1879,1882],{"class":42,"line":661},[40,1880,1881],{"class":46},"        preference",[40,1883,51],{"class":50},[40,1885,1886,1889],{"class":42,"line":670},[40,1887,1888],{"class":46},"          matchExpressions",[40,1890,51],{"class":50},[40,1892,1893,1895,1897,1899],{"class":42,"line":970},[40,1894,1808],{"class":50},[40,1896,1811],{"class":46},[40,1898,63],{"class":50},[40,1900,1901],{"class":66},"disktype\n",[40,1903,1904,1906,1908],{"class":42,"line":978},[40,1905,1821],{"class":46},[40,1907,63],{"class":50},[40,1909,1910],{"class":66},"In\n",[40,1912,1913,1915,1917,1919],{"class":42,"line":989},[40,1914,1834],{"class":46},[40,1916,1837],{"class":50},[40,1918,1730],{"class":66},[40,1920,1849],{"class":50},[199,1922,1923,1929],{},[202,1924,1925,1928],{},[169,1926,1927],{},"required"," -- Pod НЕ будет scheduled, если нет matching ноды (аналог nodeSelector, но с set-based операторами)",[202,1930,1931,1934],{},[169,1932,1933],{},"preferred"," -- scheduler попробует, но не гарантирует; weight определяет приоритет",[11,1936,1937,1940],{},[169,1938,1939],{},"IgnoredDuringExecution"," означает: если label на ноде изменится после scheduling, Pod не будет evicted. В будущем планируется RequiredDuringExecution.",[22,1942,1944],{"id":1943},"встроенные-labels-нод","Встроенные labels нод",[226,1946,1947,1956],{},[229,1948,1949],{},[232,1950,1951,1954],{},[235,1952,1953],{},"Label",[235,1955,240],{},[242,1957,1958,1966,1974,1982,1990,1998],{},[232,1959,1960,1963],{},[247,1961,1962],{},"kubernetes.io\u002Fhostname",[247,1964,1965],{},"Имя ноды",[232,1967,1968,1971],{},[247,1969,1970],{},"kubernetes.io\u002Fos",[247,1972,1973],{},"ОС (linux \u002F windows)",[232,1975,1976,1979],{},[247,1977,1978],{},"kubernetes.io\u002Farch",[247,1980,1981],{},"Архитектура (amd64 \u002F arm64)",[232,1983,1984,1987],{},[247,1985,1986],{},"topology.kubernetes.io\u002Fzone",[247,1988,1989],{},"Зона (us-east-1a)",[232,1991,1992,1995],{},[247,1993,1994],{},"topology.kubernetes.io\u002Fregion",[247,1996,1997],{},"Регион (us-east-1)",[232,1999,2000,2003],{},[247,2001,2002],{},"node.kubernetes.io\u002Finstance-type",[247,2004,2005],{},"Тип инстанса (m5.xlarge)",[14,2007],{},[22,2009,2011],{"id":2010},"podaffinity-и-podantiaffinity","podAffinity и podAntiAffinity",[11,2013,2014,2015,2018],{},"Эти механизмы управляют размещением Pod'ов относительно ",[169,2016,2017],{},"других Pod'ов",", а не нод.",[22,2020,2022],{"id":2021},"podaffinity-запусти-рядом","podAffinity — \"запусти рядом\"",[30,2024,2026],{"className":32,"code":2025,"language":34,"meta":35,"style":35},"spec:\n  affinity:\n    podAffinity:\n      requiredDuringSchedulingIgnoredDuringExecution:\n      - labelSelector:\n          matchExpressions:\n          - key: app\n            operator: In\n            values: [cache]\n        topologyKey: kubernetes.io\u002Fhostname    # \"на той же ноде\"\n",[37,2027,2028,2034,2040,2047,2053,2062,2068,2078,2086,2097],{"__ignoreMap":35},[40,2029,2030,2032],{"class":42,"line":43},[40,2031,599],{"class":46},[40,2033,51],{"class":50},[40,2035,2036,2038],{"class":42,"line":54},[40,2037,1766],{"class":46},[40,2039,51],{"class":50},[40,2041,2042,2045],{"class":42,"line":70},[40,2043,2044],{"class":46},"    podAffinity",[40,2046,51],{"class":50},[40,2048,2049,2051],{"class":42,"line":81},[40,2050,1780],{"class":46},[40,2052,51],{"class":50},[40,2054,2055,2057,2060],{"class":42,"line":89},[40,2056,1865],{"class":50},[40,2058,2059],{"class":46},"labelSelector",[40,2061,51],{"class":50},[40,2063,2064,2066],{"class":42,"line":102},[40,2065,1888],{"class":46},[40,2067,51],{"class":50},[40,2069,2070,2072,2074,2076],{"class":42,"line":116},[40,2071,1808],{"class":50},[40,2073,1811],{"class":46},[40,2075,63],{"class":50},[40,2077,67],{"class":66},[40,2079,2080,2082,2084],{"class":42,"line":130},[40,2081,1821],{"class":46},[40,2083,63],{"class":50},[40,2085,1910],{"class":66},[40,2087,2088,2090,2092,2095],{"class":42,"line":142},[40,2089,1834],{"class":46},[40,2091,1837],{"class":50},[40,2093,2094],{"class":66},"cache",[40,2096,1849],{"class":50},[40,2098,2099,2102,2104,2106],{"class":42,"line":155},[40,2100,2101],{"class":46},"        topologyKey",[40,2103,63],{"class":50},[40,2105,1962],{"class":66},[40,2107,2108],{"class":98},"    # \"на той же ноде\"\n",[11,2110,2111],{},"Use cases: web Pod рядом с cache Pod для низкой latency, frontend рядом с backend.",[22,2113,2115],{"id":2114},"podantiaffinity-запусти-подальше","podAntiAffinity — \"запусти подальше\"",[30,2117,2119],{"className":32,"code":2118,"language":34,"meta":35,"style":35},"spec:\n  affinity:\n    podAntiAffinity:\n      requiredDuringSchedulingIgnoredDuringExecution:\n      - labelSelector:\n          matchExpressions:\n          - key: app\n            operator: In\n            values: [kiada]\n        topologyKey: kubernetes.io\u002Fhostname    # \"на РАЗНЫХ нодах\"\n",[37,2120,2121,2127,2133,2140,2146,2154,2160,2170,2178,2189],{"__ignoreMap":35},[40,2122,2123,2125],{"class":42,"line":43},[40,2124,599],{"class":46},[40,2126,51],{"class":50},[40,2128,2129,2131],{"class":42,"line":54},[40,2130,1766],{"class":46},[40,2132,51],{"class":50},[40,2134,2135,2138],{"class":42,"line":70},[40,2136,2137],{"class":46},"    podAntiAffinity",[40,2139,51],{"class":50},[40,2141,2142,2144],{"class":42,"line":81},[40,2143,1780],{"class":46},[40,2145,51],{"class":50},[40,2147,2148,2150,2152],{"class":42,"line":89},[40,2149,1865],{"class":50},[40,2151,2059],{"class":46},[40,2153,51],{"class":50},[40,2155,2156,2158],{"class":42,"line":102},[40,2157,1888],{"class":46},[40,2159,51],{"class":50},[40,2161,2162,2164,2166,2168],{"class":42,"line":116},[40,2163,1808],{"class":50},[40,2165,1811],{"class":46},[40,2167,63],{"class":50},[40,2169,67],{"class":66},[40,2171,2172,2174,2176],{"class":42,"line":130},[40,2173,1821],{"class":46},[40,2175,63],{"class":50},[40,2177,1910],{"class":66},[40,2179,2180,2182,2184,2187],{"class":42,"line":142},[40,2181,1834],{"class":46},[40,2183,1837],{"class":50},[40,2185,2186],{"class":66},"kiada",[40,2188,1849],{"class":50},[40,2190,2191,2193,2195,2197],{"class":42,"line":155},[40,2192,2101],{"class":46},[40,2194,63],{"class":50},[40,2196,1962],{"class":66},[40,2198,2199],{"class":98},"    # \"на РАЗНЫХ нодах\"\n",[11,2201,2202],{},"Use cases: реплики Deployment'а на разных нодах для отказоустойчивости, реплики БД в разных зонах (HA).",[22,2204,2206],{"id":2205},"topologykey","topologyKey",[11,2208,2209],{},"topologyKey определяет, что значит \"рядом\" или \"далеко\":",[226,2211,2212,2220],{},[229,2213,2214],{},[232,2215,2216,2218],{},[235,2217,2206],{},[235,2219,237],{},[242,2221,2222,2229,2236],{},[232,2223,2224,2226],{},[247,2225,1962],{},[247,2227,2228],{},"Та же \u002F разная нода",[232,2230,2231,2233],{},[247,2232,1986],{},[247,2234,2235],{},"Та же \u002F разная зона",[232,2237,2238,2240],{},[247,2239,1994],{},[247,2241,2242],{},"Тот же \u002F разный регион",[22,2244,2246],{"id":2245},"практический-пример-реплики-на-разных-нодах","Практический пример: реплики на разных нодах",[11,2248,2249],{},"Это самый частый вопрос на собеседованиях, связанный с affinity:",[30,2251,2253],{"className":32,"code":2252,"language":34,"meta":35,"style":35},"apiVersion: apps\u002Fv1\nkind: Deployment\nmetadata:\n  name: kiada\nspec:\n  replicas: 3\n  template:\n    metadata:\n      labels:\n        app: kiada\n    spec:\n      affinity:\n        podAntiAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: app\n                operator: In\n                values: [kiada]\n            topologyKey: kubernetes.io\u002Fhostname\n",[37,2254,2255,2263,2271,2277,2285,2291,2301,2308,2315,2322,2331,2338,2345,2352,2359,2367,2374,2385,2394,2405],{"__ignoreMap":35},[40,2256,2257,2259,2261],{"class":42,"line":43},[40,2258,562],{"class":46},[40,2260,63],{"class":50},[40,2262,905],{"class":66},[40,2264,2265,2267,2269],{"class":42,"line":54},[40,2266,572],{"class":46},[40,2268,63],{"class":50},[40,2270,915],{"class":66},[40,2272,2273,2275],{"class":42,"line":70},[40,2274,582],{"class":46},[40,2276,51],{"class":50},[40,2278,2279,2281,2283],{"class":42,"line":81},[40,2280,589],{"class":46},[40,2282,63],{"class":50},[40,2284,882],{"class":66},[40,2286,2287,2289],{"class":42,"line":89},[40,2288,599],{"class":46},[40,2290,51],{"class":50},[40,2292,2293,2296,2298],{"class":42,"line":102},[40,2294,2295],{"class":46},"  replicas",[40,2297,63],{"class":50},[40,2299,2300],{"class":934},"3\n",[40,2302,2303,2306],{"class":42,"line":116},[40,2304,2305],{"class":46},"  template",[40,2307,51],{"class":50},[40,2309,2310,2313],{"class":42,"line":130},[40,2311,2312],{"class":46},"    metadata",[40,2314,51],{"class":50},[40,2316,2317,2320],{"class":42,"line":142},[40,2318,2319],{"class":46},"      labels",[40,2321,51],{"class":50},[40,2323,2324,2327,2329],{"class":42,"line":155},[40,2325,2326],{"class":46},"        app",[40,2328,63],{"class":50},[40,2330,882],{"class":66},[40,2332,2333,2336],{"class":42,"line":651},[40,2334,2335],{"class":46},"    spec",[40,2337,51],{"class":50},[40,2339,2340,2343],{"class":42,"line":661},[40,2341,2342],{"class":46},"      affinity",[40,2344,51],{"class":50},[40,2346,2347,2350],{"class":42,"line":670},[40,2348,2349],{"class":46},"        podAntiAffinity",[40,2351,51],{"class":50},[40,2353,2354,2357],{"class":42,"line":970},[40,2355,2356],{"class":46},"          requiredDuringSchedulingIgnoredDuringExecution",[40,2358,51],{"class":50},[40,2360,2361,2363,2365],{"class":42,"line":978},[40,2362,1808],{"class":50},[40,2364,2059],{"class":46},[40,2366,51],{"class":50},[40,2368,2369,2372],{"class":42,"line":989},[40,2370,2371],{"class":46},"              matchExpressions",[40,2373,51],{"class":50},[40,2375,2376,2379,2381,2383],{"class":42,"line":997},[40,2377,2378],{"class":50},"              - ",[40,2380,1811],{"class":46},[40,2382,63],{"class":50},[40,2384,67],{"class":66},[40,2386,2387,2390,2392],{"class":42,"line":1008},[40,2388,2389],{"class":46},"                operator",[40,2391,63],{"class":50},[40,2393,1910],{"class":66},[40,2395,2396,2399,2401,2403],{"class":42,"line":1022},[40,2397,2398],{"class":46},"                values",[40,2400,1837],{"class":50},[40,2402,2186],{"class":66},[40,2404,1849],{"class":50},[40,2406,2407,2410,2412],{"class":42,"line":1034},[40,2408,2409],{"class":46},"            topologyKey",[40,2411,63],{"class":50},[40,2413,2414],{"class":66},"kubernetes.io\u002Fhostname\n",[11,2416,2417,2418,2420,2421,2423,2424,334],{},"С ",[37,2419,1927],{}," и 3 репликами нужно минимум 3 ноды. Если нод меньше — Pod останется Pending. Для гибкости используйте ",[37,2422,1933],{}," вместо ",[37,2425,1927],{},[14,2427],{},[22,2429,2431],{"id":2430},"taints-и-tolerations","Taints и Tolerations",[11,2433,2434],{},"Если affinity — это \"Pod говорит: хочу туда\", то taints\u002Ftolerations — это \"нода говорит: не пускаю сюда\".",[22,2436,2438],{"id":2437},"taints-на-нодах","Taints на нодах",[30,2440,2442],{"className":1239,"code":2441,"language":1241,"meta":35,"style":35},"# Добавить taint:\nkubectl taint nodes worker-1 dedicated=gpu:NoSchedule\n\n# Убрать taint:\nkubectl taint nodes worker-1 dedicated=gpu:NoSchedule-\n",[37,2443,2444,2449,2464,2470,2475],{"__ignoreMap":35},[40,2445,2446],{"class":42,"line":43},[40,2447,2448],{"class":98},"# Добавить taint:\n",[40,2450,2451,2453,2456,2458,2461],{"class":42,"line":54},[40,2452,1249],{"class":1248},[40,2454,2455],{"class":66}," taint",[40,2457,1295],{"class":66},[40,2459,2460],{"class":66}," worker-1",[40,2462,2463],{"class":66}," dedicated=gpu:NoSchedule\n",[40,2465,2466],{"class":42,"line":70},[40,2467,2469],{"emptyLinePlaceholder":2468},true,"\n",[40,2471,2472],{"class":42,"line":81},[40,2473,2474],{"class":98},"# Убрать taint:\n",[40,2476,2477,2479,2481,2483,2485],{"class":42,"line":89},[40,2478,1249],{"class":1248},[40,2480,2455],{"class":66},[40,2482,1295],{"class":66},[40,2484,2460],{"class":66},[40,2486,2487],{"class":66}," dedicated=gpu:NoSchedule-\n",[22,2489,2491],{"id":2490},"tolerations-на-podах","Tolerations на Pod'ах",[30,2493,2495],{"className":32,"code":2494,"language":34,"meta":35,"style":35},"spec:\n  tolerations:\n  - key: dedicated\n    operator: Equal          # Equal или Exists\n    value: gpu\n    effect: NoSchedule       # NoSchedule, PreferNoSchedule, NoExecute\n",[37,2496,2497,2503,2510,2521,2534,2544],{"__ignoreMap":35},[40,2498,2499,2501],{"class":42,"line":43},[40,2500,599],{"class":46},[40,2502,51],{"class":50},[40,2504,2505,2508],{"class":42,"line":54},[40,2506,2507],{"class":46},"  tolerations",[40,2509,51],{"class":50},[40,2511,2512,2514,2516,2518],{"class":42,"line":70},[40,2513,612],{"class":50},[40,2515,1811],{"class":46},[40,2517,63],{"class":50},[40,2519,2520],{"class":66},"dedicated\n",[40,2522,2523,2526,2528,2531],{"class":42,"line":81},[40,2524,2525],{"class":46},"    operator",[40,2527,63],{"class":50},[40,2529,2530],{"class":66},"Equal",[40,2532,2533],{"class":98},"          # Equal или Exists\n",[40,2535,2536,2539,2541],{"class":42,"line":89},[40,2537,2538],{"class":46},"    value",[40,2540,63],{"class":50},[40,2542,2543],{"class":66},"gpu\n",[40,2545,2546,2549,2551,2554],{"class":42,"line":102},[40,2547,2548],{"class":46},"    effect",[40,2550,63],{"class":50},[40,2552,2553],{"class":66},"NoSchedule",[40,2555,2556],{"class":98},"       # NoSchedule, PreferNoSchedule, NoExecute\n",[22,2558,2560],{"id":2559},"три-эффекта","Три эффекта",[226,2562,2563,2572],{},[229,2564,2565],{},[232,2566,2567,2570],{},[235,2568,2569],{},"Эффект",[235,2571,240],{},[242,2573,2574,2583,2593],{},[232,2575,2576,2580],{},[247,2577,2578],{},[169,2579,2553],{},[247,2581,2582],{},"Новые Pod'ы без toleration НЕ будут scheduled на ноду. Существующие Pod'ы не трогает",[232,2584,2585,2590],{},[247,2586,2587],{},[169,2588,2589],{},"PreferNoSchedule",[247,2591,2592],{},"Scheduler попробует избежать этой ноды, но не гарантирует",[232,2594,2595,2600],{},[247,2596,2597],{},[169,2598,2599],{},"NoExecute",[247,2601,2602],{},"Новые Pod'ы не scheduled + существующие Pod'ы evicted. Можно добавить tolerationSeconds для задержки eviction",[22,2604,2606],{"id":2605},"встроенные-taints","Встроенные taints",[11,2608,2609],{},"Kubernetes автоматически добавляет taints в определённых ситуациях:",[226,2611,2612,2624],{},[229,2613,2614],{},[232,2615,2616,2619,2622],{},[235,2617,2618],{},"Taint",[235,2620,2621],{},"Когда",[235,2623,2569],{},[242,2625,2626,2636,2646,2656,2666,2676],{},[232,2627,2628,2631,2634],{},[247,2629,2630],{},"node.kubernetes.io\u002Fnot-ready",[247,2632,2633],{},"Нода NotReady",[247,2635,2599],{},[232,2637,2638,2641,2644],{},[247,2639,2640],{},"node.kubernetes.io\u002Funreachable",[247,2642,2643],{},"Нода unreachable",[247,2645,2599],{},[232,2647,2648,2651,2654],{},[247,2649,2650],{},"node.kubernetes.io\u002Fmemory-pressure",[247,2652,2653],{},"Мало памяти",[247,2655,2553],{},[232,2657,2658,2661,2664],{},[247,2659,2660],{},"node.kubernetes.io\u002Fdisk-pressure",[247,2662,2663],{},"Мало диска",[247,2665,2553],{},[232,2667,2668,2671,2674],{},[247,2669,2670],{},"node.kubernetes.io\u002Fpid-pressure",[247,2672,2673],{},"Мало PID'ов",[247,2675,2553],{},[232,2677,2678,2681,2684],{},[247,2679,2680],{},"node.kubernetes.io\u002Funschedulable",[247,2682,2683],{},"kubectl cordon",[247,2685,2553],{},[11,2687,2688,2689,2692],{},"Control plane ноды имеют taint ",[37,2690,2691],{},"node-role.kubernetes.io\u002Fcontrol-plane: NoSchedule"," -- поэтому обычные Pod'ы на master не попадают.",[22,2694,2696],{"id":2695},"affinity-vs-taints-сравнение","Affinity vs Taints — сравнение",[226,2698,2699,2711],{},[229,2700,2701],{},[232,2702,2703,2705,2708],{},[235,2704],{},[235,2706,2707],{},"Affinity \u002F Anti-Affinity",[235,2709,2710],{},"Taints \u002F Tolerations",[242,2712,2713,2724,2735,2746,2757],{},[232,2714,2715,2718,2721],{},[247,2716,2717],{},"Кто решает?",[247,2719,2720],{},"Pod выбирает ноду",[247,2722,2723],{},"Нода отталкивает Pod'ы",[232,2725,2726,2729,2732],{},[247,2727,2728],{},"Направление",[247,2730,2731],{},"\"хочу туда\"",[247,2733,2734],{},"\"не пускаю сюда\"",[232,2736,2737,2740,2743],{},[247,2738,2739],{},"Гранулярность",[247,2741,2742],{},"per-pod spec",[247,2744,2745],{},"per-node + per-pod",[232,2747,2748,2751,2754],{},[247,2749,2750],{},"Hard \u002F Soft",[247,2752,2753],{},"required \u002F preferred",[247,2755,2756],{},"NoSchedule \u002F PreferNoSchedule",[232,2758,2759,2762,2765],{},[247,2760,2761],{},"Eviction",[247,2763,2764],{},"Нет (IgnoredDuringExecution)",[247,2766,2767],{},"NoExecute evict'ит Pod'ы",[11,2769,2770,2773],{},[169,2771,2772],{},"Работают вместе:"," taint на GPU-нодах отталкивает \"чужих\", nodeAffinity на GPU Pod'ах притягивает \"своих\".",[14,2775],{},[22,2777,2779],{"id":2778},"topologyspreadconstraints","topologySpreadConstraints",[11,2781,2782],{},"Более гибкая альтернатива podAntiAffinity для равномерного распределения Pod'ов:",[30,2784,2786],{"className":32,"code":2785,"language":34,"meta":35,"style":35},"spec:\n  topologySpreadConstraints:\n  - maxSkew: 1                              # макс. разница между зонами\n    topologyKey: topology.kubernetes.io\u002Fzone\n    whenUnsatisfiable: DoNotSchedule        # или ScheduleAnyway\n    labelSelector:\n      matchLabels:\n        app: kiada\n",[37,2787,2788,2794,2801,2815,2825,2838,2845,2852],{"__ignoreMap":35},[40,2789,2790,2792],{"class":42,"line":43},[40,2791,599],{"class":46},[40,2793,51],{"class":50},[40,2795,2796,2799],{"class":42,"line":54},[40,2797,2798],{"class":46},"  topologySpreadConstraints",[40,2800,51],{"class":50},[40,2802,2803,2805,2808,2810,2812],{"class":42,"line":70},[40,2804,612],{"class":50},[40,2806,2807],{"class":46},"maxSkew",[40,2809,63],{"class":50},[40,2811,249],{"class":934},[40,2813,2814],{"class":98},"                              # макс. разница между зонами\n",[40,2816,2817,2820,2822],{"class":42,"line":81},[40,2818,2819],{"class":46},"    topologyKey",[40,2821,63],{"class":50},[40,2823,2824],{"class":66},"topology.kubernetes.io\u002Fzone\n",[40,2826,2827,2830,2832,2835],{"class":42,"line":89},[40,2828,2829],{"class":46},"    whenUnsatisfiable",[40,2831,63],{"class":50},[40,2833,2834],{"class":66},"DoNotSchedule",[40,2836,2837],{"class":98},"        # или ScheduleAnyway\n",[40,2839,2840,2843],{"class":42,"line":102},[40,2841,2842],{"class":46},"    labelSelector",[40,2844,51],{"class":50},[40,2846,2847,2850],{"class":42,"line":116},[40,2848,2849],{"class":46},"      matchLabels",[40,2851,51],{"class":50},[40,2853,2854,2856,2858],{"class":42,"line":130},[40,2855,2326],{"class":46},[40,2857,63],{"class":50},[40,2859,882],{"class":66},[11,2861,2862],{},"Этот манифест говорит: \"распредели Pod'ы равномерно по зонам, разница между количеством Pod'ов в любых двух зонах не больше 1\". Это более выразительно, чем podAntiAffinity, для multi-zone HA.",[199,2864,2865,2870,2876],{},[202,2866,2867,2869],{},[37,2868,2807],{}," -- максимально допустимая разница в количестве Pod'ов между топологическими доменами",[202,2871,2872,2875],{},[37,2873,2874],{},"whenUnsatisfiable: DoNotSchedule"," -- не размещай, если нарушается maxSkew",[202,2877,2878,2881],{},[37,2879,2880],{},"whenUnsatisfiable: ScheduleAnyway"," -- размести в любом случае, но предпочти домен с меньшим количеством Pod'ов",[14,2883],{},[17,2885,2887],{"id":2886},"admission","Admission",[22,2889,2891],{"id":2890},"admission-controllers","Admission Controllers",[11,2893,2894],{},"Admission Controllers — плагины API server'а, которые перехватывают запросы после аутентификации и авторизации, но до сохранения в etcd. Они могут валидировать, мутировать или отклонять объекты.",[22,2896,2898],{"id":2897},"цепочка-обработки-запроса-в-api-server","Цепочка обработки запроса в API Server",[30,2900,2903],{"className":2901,"code":2902,"language":1624},[1622],"Client (kubectl \u002F controller)\n  |\n  v\nAuthentication -> \"кто ты?\" (x509, token, OIDC)\n  |\n  v\nAuthorization -> \"можешь ли?\" (RBAC, ABAC)\n  |\n  v\nMutating Admission -> изменяют объект (добавить label, inject sidecar)\n  |\n  v\nSchema Validation -> объект валиден по OpenAPI схеме?\n  |\n  v\nValidating Admission -> доп. проверки (политики, constraints)\n  |\n  v\netcd -> объект сохранён\n",[37,2904,2902],{"__ignoreMap":35},[11,2906,2907,2908,2911],{},"Порядок важен: ",[169,2909,2910],{},"Mutating выполняется перед Validating",". Мутирующий webhook добавляет или изменяет поля, валидирующий webhook проверяет финальный объект.",[22,2913,2915],{"id":2914},"встроенные-admission-controllers","Встроенные Admission Controllers",[226,2917,2918,2928],{},[229,2919,2920],{},[232,2921,2922,2925],{},[235,2923,2924],{},"Controller",[235,2926,2927],{},"Назначение",[242,2929,2930,2938,2946,2954,2962,2970,2978,2986],{},[232,2931,2932,2935],{},[247,2933,2934],{},"NamespaceLifecycle",[247,2936,2937],{},"Запрещает создание объектов в удаляемом namespace",[232,2939,2940,2943],{},[247,2941,2942],{},"LimitRanger",[247,2944,2945],{},"Применяет default requests\u002Flimits из LimitRange",[232,2947,2948,2951],{},[247,2949,2950],{},"ServiceAccount",[247,2952,2953],{},"Подставляет default ServiceAccount",[232,2955,2956,2959],{},[247,2957,2958],{},"ResourceQuota",[247,2960,2961],{},"Проверяет лимиты ResourceQuota",[232,2963,2964,2967],{},[247,2965,2966],{},"DefaultStorageClass",[247,2968,2969],{},"Добавляет default StorageClass к PVC",[232,2971,2972,2975],{},[247,2973,2974],{},"PodSecurity",[247,2976,2977],{},"Проверяет Pod Security Standards (заменил PodSecurityPolicy)",[232,2979,2980,2983],{},[247,2981,2982],{},"MutatingAdmissionWebhook",[247,2984,2985],{},"Вызывает пользовательские mutating webhooks",[232,2987,2988,2991],{},[247,2989,2990],{},"ValidatingAdmissionWebhook",[247,2992,2993],{},"Вызывает пользовательские validating webhooks",[11,2995,2996],{},"Порядок выполнения встроенных controllers определён в коде API server'а. Список можно посмотреть:",[30,2998,3000],{"className":1239,"code":2999,"language":1241,"meta":35,"style":35},"kube-apiserver --help | grep enable-admission-plugins\n",[37,3001,3002],{"__ignoreMap":35},[40,3003,3004,3007,3010,3014,3017],{"class":42,"line":43},[40,3005,3006],{"class":1248},"kube-apiserver",[40,3008,3009],{"class":934}," --help",[40,3011,3013],{"class":3012},"snl16"," |",[40,3015,3016],{"class":1248}," grep",[40,3018,3019],{"class":66}," enable-admission-plugins\n",[14,3021],{},[22,3023,3025],{"id":3024},"dynamic-admission-webhooks","Dynamic Admission Webhooks",[11,3027,3028],{},"Dynamic Admission Control позволяет создать свои webhooks без пересборки API server'а. Существует два типа конфигураций: MutatingWebhookConfiguration и ValidatingWebhookConfiguration.",[22,3030,3032],{"id":3031},"mutatingwebhookconfiguration","MutatingWebhookConfiguration",[30,3034,3036],{"className":32,"code":3035,"language":34,"meta":35,"style":35},"apiVersion: admissionregistration.k8s.io\u002Fv1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: inject-sidecar\nwebhooks:\n- name: sidecar.example.com\n  admissionReviewVersions: [\"v1\"]\n  sideEffects: None\n  clientConfig:\n    service:                         # webhook = Service в кластере\n      name: sidecar-injector\n      namespace: system\n      path: \u002Finject\n    caBundle: \u003Cbase64-encoded-CA>    # TLS обязателен\n  rules:\n  - operations: [\"CREATE\"]\n    apiGroups: [\"\"]\n    apiVersions: [\"v1\"]\n    resources: [\"pods\"]\n  namespaceSelector:                 # фильтр по namespace labels\n    matchLabels:\n      inject-sidecar: \"true\"\n  failurePolicy: Fail               # Fail или Ignore\n  timeoutSeconds: 5\n",[37,3037,3038,3047,3056,3062,3071,3078,3089,3101,3111,3118,3129,3138,3148,3158,3171,3178,3192,3204,3215,3227,3238,3245,3255,3268],{"__ignoreMap":35},[40,3039,3040,3042,3044],{"class":42,"line":43},[40,3041,562],{"class":46},[40,3043,63],{"class":50},[40,3045,3046],{"class":66},"admissionregistration.k8s.io\u002Fv1\n",[40,3048,3049,3051,3053],{"class":42,"line":54},[40,3050,572],{"class":46},[40,3052,63],{"class":50},[40,3054,3055],{"class":66},"MutatingWebhookConfiguration\n",[40,3057,3058,3060],{"class":42,"line":70},[40,3059,582],{"class":46},[40,3061,51],{"class":50},[40,3063,3064,3066,3068],{"class":42,"line":81},[40,3065,589],{"class":46},[40,3067,63],{"class":50},[40,3069,3070],{"class":66},"inject-sidecar\n",[40,3072,3073,3076],{"class":42,"line":89},[40,3074,3075],{"class":46},"webhooks",[40,3077,51],{"class":50},[40,3079,3080,3082,3084,3086],{"class":42,"line":102},[40,3081,57],{"class":50},[40,3083,60],{"class":46},[40,3085,63],{"class":50},[40,3087,3088],{"class":66},"sidecar.example.com\n",[40,3090,3091,3094,3096,3099],{"class":42,"line":116},[40,3092,3093],{"class":46},"  admissionReviewVersions",[40,3095,1837],{"class":50},[40,3097,3098],{"class":66},"\"v1\"",[40,3100,1849],{"class":50},[40,3102,3103,3106,3108],{"class":42,"line":130},[40,3104,3105],{"class":46},"  sideEffects",[40,3107,63],{"class":50},[40,3109,3110],{"class":66},"None\n",[40,3112,3113,3116],{"class":42,"line":142},[40,3114,3115],{"class":46},"  clientConfig",[40,3117,51],{"class":50},[40,3119,3120,3123,3126],{"class":42,"line":155},[40,3121,3122],{"class":46},"    service",[40,3124,3125],{"class":50},":                         ",[40,3127,3128],{"class":98},"# webhook = Service в кластере\n",[40,3130,3131,3133,3135],{"class":42,"line":651},[40,3132,981],{"class":46},[40,3134,63],{"class":50},[40,3136,3137],{"class":66},"sidecar-injector\n",[40,3139,3140,3143,3145],{"class":42,"line":661},[40,3141,3142],{"class":46},"      namespace",[40,3144,63],{"class":50},[40,3146,3147],{"class":66},"system\n",[40,3149,3150,3153,3155],{"class":42,"line":670},[40,3151,3152],{"class":46},"      path",[40,3154,63],{"class":50},[40,3156,3157],{"class":66},"\u002Finject\n",[40,3159,3160,3163,3165,3168],{"class":42,"line":970},[40,3161,3162],{"class":46},"    caBundle",[40,3164,63],{"class":50},[40,3166,3167],{"class":66},"\u003Cbase64-encoded-CA>",[40,3169,3170],{"class":98},"    # TLS обязателен\n",[40,3172,3173,3176],{"class":42,"line":978},[40,3174,3175],{"class":46},"  rules",[40,3177,51],{"class":50},[40,3179,3180,3182,3185,3187,3190],{"class":42,"line":989},[40,3181,612],{"class":50},[40,3183,3184],{"class":46},"operations",[40,3186,1837],{"class":50},[40,3188,3189],{"class":66},"\"CREATE\"",[40,3191,1849],{"class":50},[40,3193,3194,3197,3199,3202],{"class":42,"line":997},[40,3195,3196],{"class":46},"    apiGroups",[40,3198,1837],{"class":50},[40,3200,3201],{"class":66},"\"\"",[40,3203,1849],{"class":50},[40,3205,3206,3209,3211,3213],{"class":42,"line":1008},[40,3207,3208],{"class":46},"    apiVersions",[40,3210,1837],{"class":50},[40,3212,3098],{"class":66},[40,3214,1849],{"class":50},[40,3216,3217,3220,3222,3225],{"class":42,"line":1022},[40,3218,3219],{"class":46},"    resources",[40,3221,1837],{"class":50},[40,3223,3224],{"class":66},"\"pods\"",[40,3226,1849],{"class":50},[40,3228,3229,3232,3235],{"class":42,"line":1034},[40,3230,3231],{"class":46},"  namespaceSelector",[40,3233,3234],{"class":50},":                 ",[40,3236,3237],{"class":98},"# фильтр по namespace labels\n",[40,3239,3240,3243],{"class":42,"line":1041},[40,3241,3242],{"class":46},"    matchLabels",[40,3244,51],{"class":50},[40,3246,3247,3250,3252],{"class":42,"line":1051},[40,3248,3249],{"class":46},"      inject-sidecar",[40,3251,63],{"class":50},[40,3253,3254],{"class":66},"\"true\"\n",[40,3256,3257,3260,3262,3265],{"class":42,"line":1058},[40,3258,3259],{"class":46},"  failurePolicy",[40,3261,63],{"class":50},[40,3263,3264],{"class":66},"Fail",[40,3266,3267],{"class":98},"               # Fail или Ignore\n",[40,3269,3270,3273,3275],{"class":42,"line":1068},[40,3271,3272],{"class":46},"  timeoutSeconds",[40,3274,63],{"class":50},[40,3276,3277],{"class":934},"5\n",[22,3279,3281],{"id":3280},"как-работает-webhook","Как работает webhook",[1134,3283,3284,3287,3290,3293,3300],{},[202,3285,3286],{},"Вы создаёте MutatingWebhookConfiguration или ValidatingWebhookConfiguration",[202,3288,3289],{},"API server перехватывает matching запросы (по rules, namespaceSelector)",[202,3291,3292],{},"Отправляет AdmissionReview (JSON) на webhook endpoint",[202,3294,3295,3296,3299],{},"Webhook отвечает: ",[37,3297,3298],{},"allowed: true\u002Ffalse"," + patches (для mutating)",[202,3301,3302],{},"API server применяет patches или отклоняет запрос",[11,3304,3305,3306,3309,3310,3313],{},"Webhook endpoint — обычно Pod + Service в кластере. ",[169,3307,3308],{},"HTTPS (TLS) обязателен."," Webhook должен отвечать быстро (контролируется через ",[37,3311,3312],{},"timeoutSeconds",", default 10s).",[22,3315,3317],{"id":3316},"failurepolicy","failurePolicy",[226,3319,3320,3329],{},[229,3321,3322],{},[232,3323,3324,3326],{},[235,3325,237],{},[235,3327,3328],{},"Поведение",[242,3330,3331,3338],{},[232,3332,3333,3335],{},[247,3334,3264],{},[247,3336,3337],{},"Если webhook недоступен — запрос отклоняется (безопаснее)",[232,3339,3340,3343],{},[247,3341,3342],{},"Ignore",[247,3344,3345],{},"Если webhook недоступен — запрос проходит (доступность важнее)",[22,3347,3349],{"id":3348},"примеры-использования","Примеры использования",[11,3351,3352],{},[169,3353,3354],{},"Mutating webhooks:",[199,3356,3357,3360,3363,3366],{},[202,3358,3359],{},"Istio sidecar injection — добавляет envoy container в Pod",[202,3361,3362],{},"Добавление default labels\u002Fannotations",[202,3364,3365],{},"Inject environment variables",[202,3367,3368],{},"Cert-manager — inject TLS certificates",[11,3370,3371],{},[169,3372,3373],{},"Validating webhooks:",[199,3375,3376,3379,3382],{},[202,3377,3378],{},"OPA\u002FGatekeeper — policy enforcement (\"все images из internal registry\", \"Pod'ы обязаны иметь resource limits\")",[202,3380,3381],{},"Kyverno — альтернативный policy engine",[202,3383,3384],{},"Custom бизнес-правила",[14,3386],{},[22,3388,3390],{"id":3389},"validatingadmissionpolicy-с-cel","ValidatingAdmissionPolicy с CEL",[11,3392,3393],{},"Начиная с Kubernetes 1.30, ValidatingAdmissionPolicy — встроенная альтернатива validating webhooks. Она использует CEL (Common Expression Language) выражения прямо в объекте Kubernetes, без необходимости поднимать внешний webhook server.",[30,3395,3397],{"className":32,"code":3396,"language":34,"meta":35,"style":35},"apiVersion: admissionregistration.k8s.io\u002Fv1\nkind: ValidatingAdmissionPolicy\nmetadata:\n  name: require-labels\nspec:\n  matchConstraints:\n    resourceRules:\n    - apiGroups: [\"apps\"]\n      operations: [\"CREATE\", \"UPDATE\"]\n      resources: [\"deployments\"]\n  validations:\n  - expression: \"has(object.metadata.labels.team)\"\n    message: \"All deployments must have a 'team' label\"\n---\napiVersion: admissionregistration.k8s.io\u002Fv1\nkind: ValidatingAdmissionPolicyBinding\nmetadata:\n  name: require-labels-binding\nspec:\n  policyName: require-labels\n  validationActions: [\"Deny\"]\n",[37,3398,3399,3407,3416,3422,3431,3437,3444,3451,3466,3482,3494,3501,3513,3523,3528,3536,3545,3551,3560,3566,3575],{"__ignoreMap":35},[40,3400,3401,3403,3405],{"class":42,"line":43},[40,3402,562],{"class":46},[40,3404,63],{"class":50},[40,3406,3046],{"class":66},[40,3408,3409,3411,3413],{"class":42,"line":54},[40,3410,572],{"class":46},[40,3412,63],{"class":50},[40,3414,3415],{"class":66},"ValidatingAdmissionPolicy\n",[40,3417,3418,3420],{"class":42,"line":70},[40,3419,582],{"class":46},[40,3421,51],{"class":50},[40,3423,3424,3426,3428],{"class":42,"line":81},[40,3425,589],{"class":46},[40,3427,63],{"class":50},[40,3429,3430],{"class":66},"require-labels\n",[40,3432,3433,3435],{"class":42,"line":89},[40,3434,599],{"class":46},[40,3436,51],{"class":50},[40,3438,3439,3442],{"class":42,"line":102},[40,3440,3441],{"class":46},"  matchConstraints",[40,3443,51],{"class":50},[40,3445,3446,3449],{"class":42,"line":116},[40,3447,3448],{"class":46},"    resourceRules",[40,3450,51],{"class":50},[40,3452,3453,3456,3459,3461,3464],{"class":42,"line":130},[40,3454,3455],{"class":50},"    - ",[40,3457,3458],{"class":46},"apiGroups",[40,3460,1837],{"class":50},[40,3462,3463],{"class":66},"\"apps\"",[40,3465,1849],{"class":50},[40,3467,3468,3471,3473,3475,3477,3480],{"class":42,"line":142},[40,3469,3470],{"class":46},"      operations",[40,3472,1837],{"class":50},[40,3474,3189],{"class":66},[40,3476,1843],{"class":50},[40,3478,3479],{"class":66},"\"UPDATE\"",[40,3481,1849],{"class":50},[40,3483,3484,3487,3489,3492],{"class":42,"line":155},[40,3485,3486],{"class":46},"      resources",[40,3488,1837],{"class":50},[40,3490,3491],{"class":66},"\"deployments\"",[40,3493,1849],{"class":50},[40,3495,3496,3499],{"class":42,"line":651},[40,3497,3498],{"class":46},"  validations",[40,3500,51],{"class":50},[40,3502,3503,3505,3508,3510],{"class":42,"line":661},[40,3504,612],{"class":50},[40,3506,3507],{"class":46},"expression",[40,3509,63],{"class":50},[40,3511,3512],{"class":66},"\"has(object.metadata.labels.team)\"\n",[40,3514,3515,3518,3520],{"class":42,"line":670},[40,3516,3517],{"class":46},"    message",[40,3519,63],{"class":50},[40,3521,3522],{"class":66},"\"All deployments must have a 'team' label\"\n",[40,3524,3525],{"class":42,"line":970},[40,3526,3527],{"class":1248},"---\n",[40,3529,3530,3532,3534],{"class":42,"line":978},[40,3531,562],{"class":46},[40,3533,63],{"class":50},[40,3535,3046],{"class":66},[40,3537,3538,3540,3542],{"class":42,"line":989},[40,3539,572],{"class":46},[40,3541,63],{"class":50},[40,3543,3544],{"class":66},"ValidatingAdmissionPolicyBinding\n",[40,3546,3547,3549],{"class":42,"line":997},[40,3548,582],{"class":46},[40,3550,51],{"class":50},[40,3552,3553,3555,3557],{"class":42,"line":1008},[40,3554,589],{"class":46},[40,3556,63],{"class":50},[40,3558,3559],{"class":66},"require-labels-binding\n",[40,3561,3562,3564],{"class":42,"line":1022},[40,3563,599],{"class":46},[40,3565,51],{"class":50},[40,3567,3568,3571,3573],{"class":42,"line":1034},[40,3569,3570],{"class":46},"  policyName",[40,3572,63],{"class":50},[40,3574,3430],{"class":66},[40,3576,3577,3580,3582,3585],{"class":42,"line":1041},[40,3578,3579],{"class":46},"  validationActions",[40,3581,1837],{"class":50},[40,3583,3584],{"class":66},"\"Deny\"",[40,3586,1849],{"class":50},[11,3588,3589,3592,3593,3596],{},[37,3590,3591],{},"ValidatingAdmissionPolicy"," только описывает правило. Чтобы оно реально применялось, нужен ",[37,3594,3595],{},"ValidatingAdmissionPolicyBinding","; без binding policy останется неактивной.",[11,3598,3599],{},"Преимущества ValidatingAdmissionPolicy:",[199,3601,3602,3605,3608],{},[202,3603,3604],{},"Не нужен внешний webhook server (меньше компонентов, выше надёжность)",[202,3606,3607],{},"CEL выражения выполняются в API server (быстрее, чем HTTP-вызов к webhook)",[202,3609,3610],{},"Декларативная конфигурация, легко аудитить",[14,3612],{},[17,3614,3616],{"id":3615},"вопросы-на-собеседовании","Вопросы на собеседовании",[11,3618,3619,3622],{},[169,3620,3621],{},"Q: Чем requests отличаются от limits? Что произойдёт, если Pod превысит CPU limit? А memory limit?","\nA: Requests — гарантированный минимум ресурсов, используется scheduler'ом для placement. Limits — потолок. CPU — сжимаемый ресурс: при превышении limit происходит throttling (замедление, не kill). Memory — несжимаемый: при превышении limit происходит OOM kill с exit code 137. Если Pod превышает requests, но не limits — он будет убит при давлении на ноде.",[11,3624,3625,3628],{},[169,3626,3627],{},"Q: Какие QoS классы есть в Kubernetes? Как они влияют на eviction?","\nA: Guaranteed (requests == limits для всех контейнеров) — последний на убой. Burstable (есть requests или limits, но они не равны) — средний приоритет, убивается по проценту превышения requests. BestEffort (нет requests и limits) — первый на убой. При OOM на ноде Kubernetes убивает Pod'ы в порядке: BestEffort -> Burstable -> Guaranteed.",[11,3630,3631,3634,3635,3638,3639,3642,3643,3645,3646,3648],{},[169,3632,3633],{},"Q: Как разместить реплики Deployment'а на разных нодах? На разных зонах?","\nA: Через podAntiAffinity с topologyKey. Для разных нод: ",[37,3636,3637],{},"topologyKey: kubernetes.io\u002Fhostname",". Для разных зон: ",[37,3640,3641],{},"topologyKey: topology.kubernetes.io\u002Fzone",". Используйте ",[37,3644,1927],{}," для строгого требования (Pod останется Pending, если нод\u002Fзон недостаточно) или ",[37,3647,1933],{}," для мягкого. Альтернатива — topologySpreadConstraints с maxSkew для равномерного распределения.",[11,3650,3651,3654],{},[169,3652,3653],{},"Q: В чём разница между affinity и taints\u002Ftolerations?","\nA: Affinity — Pod говорит \"хочу на эту ноду\" (притягивает). Taints\u002Ftolerations — нода говорит \"не пускаю к себе\" (отталкивает). Работают вместе: taint на GPU-нодах отталкивает обычные Pod'ы, nodeAffinity на GPU Pod'ах притягивает их на GPU-ноды. Ещё одно отличие: NoExecute taint может evict'ить уже запущенные Pod'ы, а affinity IgnoredDuringExecution не evict'ит.",[11,3656,3657,3660,3661,3664],{},[169,3658,3659],{},"Q: Как HPA вычисляет нужное количество реплик?","\nA: Формула: ",[37,3662,3663],{},"desired = ceil(currentReplicas * (currentMetricValue \u002F targetMetricValue))",". Например, при 3 репликах, текущем CPU 90% и target 70%: ceil(3 * 90\u002F70) = ceil(3.86) = 4. HPA проверяет метрики каждые 15 секунд. Без requests HPA не работает — utilization считается как процент от requests.",[11,3666,3667,3670],{},[169,3668,3669],{},"Q: Можно ли использовать HPA и VPA одновременно?","\nA: По одним и тем же метрикам (CPU\u002Fmemory) — нет, будет конфликт (feedback loop). Безопасная комбинация: VPA подбирает requests\u002Flimits, HPA скейлит по custom metrics (requests per second, queue depth). Также существует Multidimensional Pod Autoscaler (MPA) как специализированное решение.",[11,3672,3673,3676],{},[169,3674,3675],{},"Q: Что такое admission controllers? Зачем нужны webhook'и?","\nA: Admission controllers — плагины API server'а, перехватывающие запросы после AuthN\u002FAuthZ, но до записи в etcd. Бывают mutating (изменяют объект) и validating (проверяют). Webhooks позволяют добавлять свою логику без пересборки API server'а. Примеры: Istio sidecar injection (mutating), OPA\u002FGatekeeper policy enforcement (validating). Начиная с K8s 1.30, ValidatingAdmissionPolicy с CEL позволяет писать политики без внешнего webhook server'а.",[11,3678,3679,3682,3683,3685],{},[169,3680,3681],{},"Q: Почему control plane ноды не запускают обычные Pod'ы?","\nA: На control plane нодах стоит taint ",[37,3684,2691],{},". Scheduler не размещает Pod'ы без matching toleration на такие ноды. Это защита: на master-нодах работают критические компоненты (API server, etcd, controller-manager, scheduler), и пользовательские workload'ы не должны конкурировать с ними за ресурсы.",[14,3687],{},[17,3689,3691],{"id":3690},"задачи","Задачи",[11,3693,3694],{},[169,3695,3696],{},"Задача 1: Настройка ресурсов для Go-сервиса",[11,3698,3699,3702],{},[169,3700,3701],{},"Уровень:"," Средняя",[11,3704,3705,3708],{},[169,3706,3707],{},"Что проверяет:"," понимание requests\u002Flimits, QoS классов и GOMEMLIMIT",[11,3710,3711,3714,3715,3718],{},[169,3712,3713],{},"Условие:"," У вас есть Go-сервис ",[37,3716,3717],{},"order-processor",", который в среднем потребляет 200m CPU и 180Mi памяти, с пиками до 800m CPU и 400Mi памяти. Напишите манифест Deployment с:",[1134,3720,3721,3724,3727],{},[202,3722,3723],{},"Resources, обеспечивающими QoS Guaranteed",[202,3725,3726],{},"Resources, обеспечивающими QoS Burstable",[202,3728,3729],{},"Для Burstable варианта — корректный GOMEMLIMIT",[11,3731,3732],{},"Объясните: какой вариант лучше для production и почему? Что произойдёт с каждым вариантом при пиковой нагрузке?",[11,3734,3735],{},[169,3736,3737],{},"Ожидаемый ответ:",[30,3739,3741],{"className":32,"code":3740,"language":34,"meta":35,"style":35},"# Guaranteed: requests == limits\nresources:\n  requests:\n    cpu: 800m\n    memory: 400Mi\n  limits:\n    cpu: 800m\n    memory: 400Mi\n# GOMEMLIMIT = ~350MiB (88% от 400Mi)\n\n# Burstable: requests \u003C limits\nresources:\n  requests:\n    cpu: 200m\n    memory: 180Mi\n  limits:\n    cpu: 800m\n    memory: 400Mi\n# GOMEMLIMIT = ~350MiB (88% от limit 400Mi)\n",[37,3742,3743,3748,3754,3760,3769,3778,3784,3792,3800,3805,3809,3814,3820,3826,3835,3844,3850,3858,3866],{"__ignoreMap":35},[40,3744,3745],{"class":42,"line":43},[40,3746,3747],{"class":98},"# Guaranteed: requests == limits\n",[40,3749,3750,3752],{"class":42,"line":54},[40,3751,383],{"class":46},[40,3753,51],{"class":50},[40,3755,3756,3758],{"class":42,"line":70},[40,3757,390],{"class":46},[40,3759,51],{"class":50},[40,3761,3762,3764,3766],{"class":42,"line":81},[40,3763,397],{"class":46},[40,3765,63],{"class":50},[40,3767,3768],{"class":66},"800m\n",[40,3770,3771,3773,3775],{"class":42,"line":89},[40,3772,407],{"class":46},[40,3774,63],{"class":50},[40,3776,3777],{"class":66},"400Mi\n",[40,3779,3780,3782],{"class":42,"line":102},[40,3781,417],{"class":46},[40,3783,51],{"class":50},[40,3785,3786,3788,3790],{"class":42,"line":116},[40,3787,397],{"class":46},[40,3789,63],{"class":50},[40,3791,3768],{"class":66},[40,3793,3794,3796,3798],{"class":42,"line":130},[40,3795,407],{"class":46},[40,3797,63],{"class":50},[40,3799,3777],{"class":66},[40,3801,3802],{"class":42,"line":142},[40,3803,3804],{"class":98},"# GOMEMLIMIT = ~350MiB (88% от 400Mi)\n",[40,3806,3807],{"class":42,"line":155},[40,3808,2469],{"emptyLinePlaceholder":2468},[40,3810,3811],{"class":42,"line":651},[40,3812,3813],{"class":98},"# Burstable: requests \u003C limits\n",[40,3815,3816,3818],{"class":42,"line":661},[40,3817,383],{"class":46},[40,3819,51],{"class":50},[40,3821,3822,3824],{"class":42,"line":670},[40,3823,390],{"class":46},[40,3825,51],{"class":50},[40,3827,3828,3830,3832],{"class":42,"line":970},[40,3829,397],{"class":46},[40,3831,63],{"class":50},[40,3833,3834],{"class":66},"200m\n",[40,3836,3837,3839,3841],{"class":42,"line":978},[40,3838,407],{"class":46},[40,3840,63],{"class":50},[40,3842,3843],{"class":66},"180Mi\n",[40,3845,3846,3848],{"class":42,"line":989},[40,3847,417],{"class":46},[40,3849,51],{"class":50},[40,3851,3852,3854,3856],{"class":42,"line":997},[40,3853,397],{"class":46},[40,3855,63],{"class":50},[40,3857,3768],{"class":66},[40,3859,3860,3862,3864],{"class":42,"line":1008},[40,3861,407],{"class":46},[40,3863,63],{"class":50},[40,3865,3777],{"class":66},[40,3867,3868],{"class":42,"line":1022},[40,3869,3870],{"class":98},"# GOMEMLIMIT = ~350MiB (88% от limit 400Mi)\n",[11,3872,3873],{},"Guaranteed безопаснее для production: Pod последний в очереди на OOM kill. Но дороже — резервирует ресурсы по максимуму. При пиковой нагрузке Guaranteed Pod получит все заявленные ресурсы. Burstable Pod при пике может получить throttling по CPU и OOM kill по memory при давлении на ноде.",[14,3875],{},[11,3877,3878],{},[169,3879,3880],{},"Задача 2: HA Deployment с распределением по зонам",[11,3882,3883,3885],{},[169,3884,3701],{}," Сложная",[11,3887,3888,3890],{},[169,3889,3707],{}," podAntiAffinity, topologySpreadConstraints, taints\u002Ftolerations",[11,3892,3893,3895,3896,3899],{},[169,3894,3713],{}," Создайте Deployment ",[37,3897,3898],{},"cache-service"," (6 реплик), который:",[1134,3901,3902,3905,3908,3914],{},[202,3903,3904],{},"Размещает реплики на разных нодах (preferred, не required)",[202,3906,3907],{},"Равномерно распределяет реплики по зонам (maxSkew: 1)",[202,3909,3910,3911],{},"Может запускаться на нодах с taint ",[37,3912,3913],{},"workload=cache:NoSchedule",[202,3915,3916],{},"Имеет HPA от 6 до 30 реплик по CPU (target 60%)",[11,3918,3919],{},"Объясните: почему для этого случая лучше использовать preferred podAntiAffinity вместо required?",[14,3921],{},[11,3923,3924],{},[169,3925,3926],{},"Задача 3: Admission Policy для безопасности",[11,3928,3929,3885],{},[169,3930,3701],{},[11,3932,3933,3935],{},[169,3934,3707],{}," понимание admission controllers и CEL",[11,3937,3938,3940],{},[169,3939,3713],{}," Напишите ValidatingAdmissionPolicy, которая:",[1134,3942,3943,3946,3953],{},[202,3944,3945],{},"Применяется ко всем Deployment'ам при CREATE и UPDATE",[202,3947,3948,3949,3952],{},"Требует наличие label ",[37,3950,3951],{},"team"," на каждом Deployment",[202,3954,3955],{},"Требует, чтобы все контейнеры имели memory limits",[11,3957,3958],{},[169,3959,3737],{},[30,3961,3963],{"className":32,"code":3962,"language":34,"meta":35,"style":35},"apiVersion: admissionregistration.k8s.io\u002Fv1\nkind: ValidatingAdmissionPolicy\nmetadata:\n  name: deployment-requirements\nspec:\n  matchConstraints:\n    resourceRules:\n    - apiGroups: [\"apps\"]\n      operations: [\"CREATE\", \"UPDATE\"]\n      resources: [\"deployments\"]\n  validations:\n  - expression: \"has(object.metadata.labels.team)\"\n    message: \"Deployment must have a 'team' label\"\n  - expression: \"object.spec.template.spec.containers.all(c, has(c.resources) && has(c.resources.limits) && has(c.resources.limits.memory))\"\n    message: \"All containers must have memory limits\"\n---\napiVersion: admissionregistration.k8s.io\u002Fv1\nkind: ValidatingAdmissionPolicyBinding\nmetadata:\n  name: deployment-requirements-binding\nspec:\n  policyName: deployment-requirements\n  validationActions: [\"Deny\"]\n",[37,3964,3965,3973,3981,3987,3996,4002,4008,4014,4026,4040,4050,4056,4066,4075,4086,4095,4099,4107,4115,4121,4130,4136,4144],{"__ignoreMap":35},[40,3966,3967,3969,3971],{"class":42,"line":43},[40,3968,562],{"class":46},[40,3970,63],{"class":50},[40,3972,3046],{"class":66},[40,3974,3975,3977,3979],{"class":42,"line":54},[40,3976,572],{"class":46},[40,3978,63],{"class":50},[40,3980,3415],{"class":66},[40,3982,3983,3985],{"class":42,"line":70},[40,3984,582],{"class":46},[40,3986,51],{"class":50},[40,3988,3989,3991,3993],{"class":42,"line":81},[40,3990,589],{"class":46},[40,3992,63],{"class":50},[40,3994,3995],{"class":66},"deployment-requirements\n",[40,3997,3998,4000],{"class":42,"line":89},[40,3999,599],{"class":46},[40,4001,51],{"class":50},[40,4003,4004,4006],{"class":42,"line":102},[40,4005,3441],{"class":46},[40,4007,51],{"class":50},[40,4009,4010,4012],{"class":42,"line":116},[40,4011,3448],{"class":46},[40,4013,51],{"class":50},[40,4015,4016,4018,4020,4022,4024],{"class":42,"line":130},[40,4017,3455],{"class":50},[40,4019,3458],{"class":46},[40,4021,1837],{"class":50},[40,4023,3463],{"class":66},[40,4025,1849],{"class":50},[40,4027,4028,4030,4032,4034,4036,4038],{"class":42,"line":142},[40,4029,3470],{"class":46},[40,4031,1837],{"class":50},[40,4033,3189],{"class":66},[40,4035,1843],{"class":50},[40,4037,3479],{"class":66},[40,4039,1849],{"class":50},[40,4041,4042,4044,4046,4048],{"class":42,"line":155},[40,4043,3486],{"class":46},[40,4045,1837],{"class":50},[40,4047,3491],{"class":66},[40,4049,1849],{"class":50},[40,4051,4052,4054],{"class":42,"line":651},[40,4053,3498],{"class":46},[40,4055,51],{"class":50},[40,4057,4058,4060,4062,4064],{"class":42,"line":661},[40,4059,612],{"class":50},[40,4061,3507],{"class":46},[40,4063,63],{"class":50},[40,4065,3512],{"class":66},[40,4067,4068,4070,4072],{"class":42,"line":670},[40,4069,3517],{"class":46},[40,4071,63],{"class":50},[40,4073,4074],{"class":66},"\"Deployment must have a 'team' label\"\n",[40,4076,4077,4079,4081,4083],{"class":42,"line":970},[40,4078,612],{"class":50},[40,4080,3507],{"class":46},[40,4082,63],{"class":50},[40,4084,4085],{"class":66},"\"object.spec.template.spec.containers.all(c, has(c.resources) && has(c.resources.limits) && has(c.resources.limits.memory))\"\n",[40,4087,4088,4090,4092],{"class":42,"line":978},[40,4089,3517],{"class":46},[40,4091,63],{"class":50},[40,4093,4094],{"class":66},"\"All containers must have memory limits\"\n",[40,4096,4097],{"class":42,"line":989},[40,4098,3527],{"class":1248},[40,4100,4101,4103,4105],{"class":42,"line":997},[40,4102,562],{"class":46},[40,4104,63],{"class":50},[40,4106,3046],{"class":66},[40,4108,4109,4111,4113],{"class":42,"line":1008},[40,4110,572],{"class":46},[40,4112,63],{"class":50},[40,4114,3544],{"class":66},[40,4116,4117,4119],{"class":42,"line":1022},[40,4118,582],{"class":46},[40,4120,51],{"class":50},[40,4122,4123,4125,4127],{"class":42,"line":1034},[40,4124,589],{"class":46},[40,4126,63],{"class":50},[40,4128,4129],{"class":66},"deployment-requirements-binding\n",[40,4131,4132,4134],{"class":42,"line":1041},[40,4133,599],{"class":46},[40,4135,51],{"class":50},[40,4137,4138,4140,4142],{"class":42,"line":1051},[40,4139,3570],{"class":46},[40,4141,63],{"class":50},[40,4143,3995],{"class":66},[40,4145,4146,4148,4150,4152],{"class":42,"line":1058},[40,4147,3579],{"class":46},[40,4149,1837],{"class":50},[40,4151,3584],{"class":66},[40,4153,1849],{"class":50},[11,4155,4156],{},"Объясните: в чём преимущество ValidatingAdmissionPolicy перед webhook для этой задачи?",[14,4158],{},[17,4160,4162],{"id":4161},"интерактивная-практика","Интерактивная практика",[4164,4165,4169,4172,4189],"quiz",{"answer":4166,"id":4167,"xp":4168},"2","infra-scaling-q1","10",[11,4170,4171],{},"Что делает Horizontal Pod Autoscaler?",[4173,4174,4175],"template",{"v-slot:options":35},[199,4176,4177,4180,4183,4186],{},[202,4178,4179],{},"Меняет размер node pool напрямую",[202,4181,4182],{},"Меняет количество реплик workload на основе метрик",[202,4184,4185],{},"Назначает pod на конкретную ноду вручную",[202,4187,4188],{},"Запрещает запуск pod-ов без labels",[4173,4190,4191],{"v-slot:explanation":35},[11,4192,4193],{},"HPA управляет числом реплик, опираясь на метрики вроде CPU, memory или custom metrics. Размер кластера меняет Cluster Autoscaler, а policy-проверки делают admission controls.",[4195,4196,4200,4203,4392],"predict",{"answer":4197,"id":4198,"xp":4199},"Burstable\\nGuaranteed","infra-scaling-p1","15",[11,4201,4202],{},"Что выведет программа?",[4173,4204,4205],{"v-slot:code":35},[30,4206,4210],{"className":4207,"code":4208,"language":4209,"meta":35,"style":35},"language-go shiki shiki-themes github-dark","package main\n\nimport \"fmt\"\n\nfunc qos(equalReqLimit bool, hasRequests bool) string {\n    if equalReqLimit {\n        return \"Guaranteed\"\n    }\n    if hasRequests {\n        return \"Burstable\"\n    }\n    return \"BestEffort\"\n}\n\nfunc main() {\n    fmt.Println(qos(false, true))\n    fmt.Println(qos(true, true))\n}\n","go",[37,4211,4212,4220,4224,4238,4242,4276,4284,4292,4297,4304,4311,4315,4323,4328,4332,4342,4368,4388],{"__ignoreMap":35},[40,4213,4214,4217],{"class":42,"line":43},[40,4215,4216],{"class":3012},"package",[40,4218,4219],{"class":1248}," main\n",[40,4221,4222],{"class":42,"line":54},[40,4223,2469],{"emptyLinePlaceholder":2468},[40,4225,4226,4229,4232,4235],{"class":42,"line":70},[40,4227,4228],{"class":3012},"import",[40,4230,4231],{"class":66}," \"",[40,4233,4234],{"class":1248},"fmt",[40,4236,4237],{"class":66},"\"\n",[40,4239,4240],{"class":42,"line":81},[40,4241,2469],{"emptyLinePlaceholder":2468},[40,4243,4244,4247,4250,4253,4257,4260,4262,4265,4267,4270,4273],{"class":42,"line":89},[40,4245,4246],{"class":3012},"func",[40,4248,4249],{"class":1248}," qos",[40,4251,4252],{"class":50},"(",[40,4254,4256],{"class":4255},"s9osk","equalReqLimit",[40,4258,4259],{"class":3012}," bool",[40,4261,1843],{"class":50},[40,4263,4264],{"class":4255},"hasRequests",[40,4266,4259],{"class":3012},[40,4268,4269],{"class":50},") ",[40,4271,4272],{"class":3012},"string",[40,4274,4275],{"class":50}," {\n",[40,4277,4278,4281],{"class":42,"line":102},[40,4279,4280],{"class":3012},"    if",[40,4282,4283],{"class":50}," equalReqLimit {\n",[40,4285,4286,4289],{"class":42,"line":116},[40,4287,4288],{"class":3012},"        return",[40,4290,4291],{"class":66}," \"Guaranteed\"\n",[40,4293,4294],{"class":42,"line":130},[40,4295,4296],{"class":50},"    }\n",[40,4298,4299,4301],{"class":42,"line":142},[40,4300,4280],{"class":3012},[40,4302,4303],{"class":50}," hasRequests {\n",[40,4305,4306,4308],{"class":42,"line":155},[40,4307,4288],{"class":3012},[40,4309,4310],{"class":66}," \"Burstable\"\n",[40,4312,4313],{"class":42,"line":651},[40,4314,4296],{"class":50},[40,4316,4317,4320],{"class":42,"line":661},[40,4318,4319],{"class":3012},"    return",[40,4321,4322],{"class":66}," \"BestEffort\"\n",[40,4324,4325],{"class":42,"line":670},[40,4326,4327],{"class":50},"}\n",[40,4329,4330],{"class":42,"line":970},[40,4331,2469],{"emptyLinePlaceholder":2468},[40,4333,4334,4336,4339],{"class":42,"line":978},[40,4335,4246],{"class":3012},[40,4337,4338],{"class":1248}," main",[40,4340,4341],{"class":50},"() {\n",[40,4343,4344,4347,4350,4352,4355,4357,4360,4362,4365],{"class":42,"line":989},[40,4345,4346],{"class":50},"    fmt.",[40,4348,4349],{"class":1248},"Println",[40,4351,4252],{"class":50},[40,4353,4354],{"class":1248},"qos",[40,4356,4252],{"class":50},[40,4358,4359],{"class":934},"false",[40,4361,1843],{"class":50},[40,4363,4364],{"class":934},"true",[40,4366,4367],{"class":50},"))\n",[40,4369,4370,4372,4374,4376,4378,4380,4382,4384,4386],{"class":42,"line":997},[40,4371,4346],{"class":50},[40,4373,4349],{"class":1248},[40,4375,4252],{"class":50},[40,4377,4354],{"class":1248},[40,4379,4252],{"class":50},[40,4381,4364],{"class":934},[40,4383,1843],{"class":50},[40,4385,4364],{"class":934},[40,4387,4367],{"class":50},[40,4389,4390],{"class":42,"line":1008},[40,4391,4327],{"class":50},[4173,4393,4394],{"v-slot:hint":35},[11,4395,4396,4398,4399,334],{},[37,4397,370],{}," требует равных requests и limits для CPU\u002Fmemory. Если requests есть, но guarantees неполные, pod обычно попадает в ",[37,4400,448],{},[4402,4403,4407,4414,4544],"code-task",{"expected":4404,"id":4405,"xp":4406},"scale-up\\nreject\\nschedule","infra-scaling-ct1","20",[11,4408,4409,4410,4413],{},"Реализуй ",[37,4411,4412],{},"SchedulingDecision",": высокая CPU-нагрузка ведёт к scale-up, отсутствие обязательного team-label — к reject, остальные случаи можно планировать.",[4173,4415,4416],{"v-slot:template":35},[30,4417,4419],{"className":4207,"code":4418,"language":4209,"meta":35,"style":35},"package main\n\nimport \"fmt\"\n\nfunc SchedulingDecision(signal string) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(SchedulingDecision(\"high-cpu\"))\n    fmt.Println(SchedulingDecision(\"no-team-label\"))\n    fmt.Println(SchedulingDecision(\"normal\"))\n}\n",[37,4420,4421,4427,4431,4441,4445,4466,4473,4477,4481,4489,4506,4523,4540],{"__ignoreMap":35},[40,4422,4423,4425],{"class":42,"line":43},[40,4424,4216],{"class":3012},[40,4426,4219],{"class":1248},[40,4428,4429],{"class":42,"line":54},[40,4430,2469],{"emptyLinePlaceholder":2468},[40,4432,4433,4435,4437,4439],{"class":42,"line":70},[40,4434,4228],{"class":3012},[40,4436,4231],{"class":66},[40,4438,4234],{"class":1248},[40,4440,4237],{"class":66},[40,4442,4443],{"class":42,"line":81},[40,4444,2469],{"emptyLinePlaceholder":2468},[40,4446,4447,4449,4452,4454,4457,4460,4462,4464],{"class":42,"line":89},[40,4448,4246],{"class":3012},[40,4450,4451],{"class":1248}," SchedulingDecision",[40,4453,4252],{"class":50},[40,4455,4456],{"class":4255},"signal",[40,4458,4459],{"class":3012}," string",[40,4461,4269],{"class":50},[40,4463,4272],{"class":3012},[40,4465,4275],{"class":50},[40,4467,4468,4470],{"class":42,"line":102},[40,4469,4319],{"class":3012},[40,4471,4472],{"class":66}," \"todo\"\n",[40,4474,4475],{"class":42,"line":116},[40,4476,4327],{"class":50},[40,4478,4479],{"class":42,"line":130},[40,4480,2469],{"emptyLinePlaceholder":2468},[40,4482,4483,4485,4487],{"class":42,"line":142},[40,4484,4246],{"class":3012},[40,4486,4338],{"class":1248},[40,4488,4341],{"class":50},[40,4490,4491,4493,4495,4497,4499,4501,4504],{"class":42,"line":155},[40,4492,4346],{"class":50},[40,4494,4349],{"class":1248},[40,4496,4252],{"class":50},[40,4498,4412],{"class":1248},[40,4500,4252],{"class":50},[40,4502,4503],{"class":66},"\"high-cpu\"",[40,4505,4367],{"class":50},[40,4507,4508,4510,4512,4514,4516,4518,4521],{"class":42,"line":651},[40,4509,4346],{"class":50},[40,4511,4349],{"class":1248},[40,4513,4252],{"class":50},[40,4515,4412],{"class":1248},[40,4517,4252],{"class":50},[40,4519,4520],{"class":66},"\"no-team-label\"",[40,4522,4367],{"class":50},[40,4524,4525,4527,4529,4531,4533,4535,4538],{"class":42,"line":661},[40,4526,4346],{"class":50},[40,4528,4349],{"class":1248},[40,4530,4252],{"class":50},[40,4532,4412],{"class":1248},[40,4534,4252],{"class":50},[40,4536,4537],{"class":66},"\"normal\"",[40,4539,4367],{"class":50},[40,4541,4542],{"class":42,"line":670},[40,4543,4327],{"class":50},[4173,4545,4546],{"v-slot:hints":35},[199,4547,4548,4551,4554],{},[202,4549,4550],{},"Метрики нагрузки обычно идут в autoscaling-решения.",[202,4552,4553],{},"Нарушение admission policy нужно отклонять до создания объекта.",[202,4555,4556,4557,334],{},"Нормальный объект без тревожных сигналов можно ",[37,4558,4559],{},"schedule",[4561,4562,4563],"style",{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":35,"searchDepth":54,"depth":54,"links":4565},[4566,4579,4597,4615,4626,4627,4628],{"id":19,"depth":54,"text":20,"children":4567},[4568,4569,4570,4571,4572,4573,4574,4575,4576,4577,4578],{"id":24,"depth":70,"text":25},{"id":218,"depth":70,"text":219},{"id":323,"depth":70,"text":324},{"id":362,"depth":70,"text":363},{"id":369,"depth":70,"text":370},{"id":447,"depth":70,"text":448},{"id":517,"depth":70,"text":518},{"id":544,"depth":70,"text":545},{"id":548,"depth":70,"text":549},{"id":681,"depth":70,"text":682},{"id":794,"depth":70,"text":795},{"id":832,"depth":54,"text":833,"children":4580},[4581,4582,4583,4584,4585,4586,4587,4588,4589,4590,4591,4592,4593,4594,4595,4596],{"id":836,"depth":70,"text":837},{"id":843,"depth":70,"text":844},{"id":1131,"depth":70,"text":1132},{"id":1173,"depth":70,"text":1174},{"id":1235,"depth":70,"text":1236},{"id":1303,"depth":70,"text":1304},{"id":1310,"depth":70,"text":1211},{"id":1400,"depth":70,"text":1401},{"id":1426,"depth":70,"text":1427},{"id":1433,"depth":70,"text":1434},{"id":1474,"depth":70,"text":1475},{"id":1543,"depth":70,"text":1544},{"id":1550,"depth":70,"text":1551},{"id":1568,"depth":70,"text":1569},{"id":1617,"depth":70,"text":1618},{"id":1649,"depth":70,"text":1200},{"id":1690,"depth":54,"text":1691,"children":4598},[4599,4600,4601,4602,4603,4604,4605,4606,4607,4608,4609,4610,4611,4612,4613,4614],{"id":1694,"depth":70,"text":1695},{"id":1701,"depth":70,"text":1702},{"id":1749,"depth":70,"text":1750},{"id":1943,"depth":70,"text":1944},{"id":2010,"depth":70,"text":2011},{"id":2021,"depth":70,"text":2022},{"id":2114,"depth":70,"text":2115},{"id":2205,"depth":70,"text":2206},{"id":2245,"depth":70,"text":2246},{"id":2430,"depth":70,"text":2431},{"id":2437,"depth":70,"text":2438},{"id":2490,"depth":70,"text":2491},{"id":2559,"depth":70,"text":2560},{"id":2605,"depth":70,"text":2606},{"id":2695,"depth":70,"text":2696},{"id":2778,"depth":70,"text":2779},{"id":2886,"depth":54,"text":2887,"children":4616},[4617,4618,4619,4620,4621,4622,4623,4624,4625],{"id":2890,"depth":70,"text":2891},{"id":2897,"depth":70,"text":2898},{"id":2914,"depth":70,"text":2915},{"id":3024,"depth":70,"text":3025},{"id":3031,"depth":70,"text":3032},{"id":3280,"depth":70,"text":3281},{"id":3316,"depth":70,"text":3317},{"id":3348,"depth":70,"text":3349},{"id":3389,"depth":70,"text":3390},{"id":3615,"depth":54,"text":3616},{"id":3690,"depth":54,"text":3691},{"id":4161,"depth":54,"text":4162},1781022066948]