mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-26 05:14:13 +01:00
Compare commits
679 Commits
v0.10.0
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8744928020 | ||
|
|
546d8ef196 | ||
|
|
e5fa6e455a | ||
|
|
3cbd411062 | ||
|
|
9bf473b7e4 | ||
|
|
dcb4899f33 | ||
|
|
55ab0deaf7 | ||
|
|
0a4bcdaea1 | ||
|
|
635e286b58 | ||
|
|
57a1d9f780 | ||
|
|
c09c060042 | ||
|
|
196b7a297d | ||
|
|
eb838bd37a | ||
|
|
7b505383bb | ||
|
|
e0e608722f | ||
|
|
26684300c4 | ||
|
|
2868e83f75 | ||
|
|
a25066fa89 | ||
|
|
eb0be65687 | ||
|
|
64786460cd | ||
|
|
9c110c4004 | ||
|
|
0eddf7f108 | ||
|
|
3c8d6c4d53 | ||
|
|
6e84d0a6ba | ||
|
|
fb1df468ad | ||
|
|
ac4d576df8 | ||
|
|
314ad65b04 | ||
|
|
969a618933 | ||
|
|
028f205e8c | ||
|
|
3eca2782d4 | ||
|
|
16eb9063b6 | ||
|
|
eac3b4b54a | ||
|
|
d08cea731a | ||
|
|
0fc5ba9316 | ||
|
|
ecbd10afe2 | ||
|
|
e5ed0540f2 | ||
|
|
4e972a7602 | ||
|
|
ae20b5b034 | ||
|
|
406e3ed5b3 | ||
|
|
7589aaf00b | ||
|
|
ca90b53913 | ||
|
|
238eebeaca | ||
|
|
cf59d08193 | ||
|
|
2ea65e69dc | ||
|
|
7f6a2a69b0 | ||
|
|
ac3362149b | ||
|
|
0a52af9ab8 | ||
|
|
07bbdc61c4 | ||
|
|
17595fdcfc | ||
|
|
285523f0d9 | ||
|
|
c55a897599 | ||
|
|
52ff50f2d1 | ||
|
|
8ebf3fb323 | ||
|
|
0e0ae8df90 | ||
|
|
bd3daa82d3 | ||
|
|
60a15f0392 | ||
|
|
d98cb84568 | ||
|
|
6ab01eca63 | ||
|
|
584ac2d604 | ||
|
|
448dc4784c | ||
|
|
3ca77e7a3d | ||
|
|
01e7015b97 | ||
|
|
fd5a8c7d78 | ||
|
|
43148ecd0c | ||
|
|
16501978dc | ||
|
|
1b4e48b006 | ||
|
|
da6a3e063f | ||
|
|
5784c0cc04 | ||
|
|
254a3a9ec1 | ||
|
|
328c695141 | ||
|
|
3ab0268c5a | ||
|
|
cd8dbdd1e2 | ||
|
|
54c50c5390 | ||
|
|
a2cbc25397 | ||
|
|
bd81f6436e | ||
|
|
30be19b04e | ||
|
|
3c251fb09d | ||
|
|
224e2b078f | ||
|
|
dd80d60f4f | ||
|
|
e88837a349 | ||
|
|
5901f8af1b | ||
|
|
0d1704a192 | ||
|
|
c5878b18c6 | ||
|
|
ff1954b32e | ||
|
|
4c8040bbaf | ||
|
|
deaa314492 | ||
|
|
9c653a2274 | ||
|
|
8d37557743 | ||
|
|
5081ad84b5 | ||
|
|
c51c066cd1 | ||
|
|
afb1d75ce1 | ||
|
|
90e6174fdd | ||
|
|
8e3ef9a6b3 | ||
|
|
778a18c550 | ||
|
|
1a98a566b3 | ||
|
|
a643c619c9 | ||
|
|
203388ff1a | ||
|
|
2844f80a35 | ||
|
|
901a16ecbc | ||
|
|
e0f086ff85 | ||
|
|
0251935268 | ||
|
|
8752a28025 | ||
|
|
24884c7568 | ||
|
|
175f648045 | ||
|
|
f50a3fa119 | ||
|
|
551eced42a | ||
|
|
3635a8171c | ||
|
|
796f347305 | ||
|
|
13abbe7f09 | ||
|
|
e4df54d2d1 | ||
|
|
c38f617e40 | ||
|
|
e6551564c4 | ||
|
|
3a991dd50c | ||
|
|
77cb406052 | ||
|
|
921a5680ab | ||
|
|
1529180d70 | ||
|
|
2d9143d129 | ||
|
|
e9c0833b6f | ||
|
|
8462cf56d7 | ||
|
|
a60d6a527d | ||
|
|
2b23694704 | ||
|
|
d0a95bee2f | ||
|
|
57a910f5d1 | ||
|
|
ccaedde183 | ||
|
|
2020642b6f | ||
|
|
96ff5d2dd9 | ||
|
|
d8718d7db3 | ||
|
|
1e5165ba9f | ||
|
|
8e74f8bd77 | ||
|
|
2424928019 | ||
|
|
e6314d2c7e | ||
|
|
271ee3c7e3 | ||
|
|
e58686c142 | ||
|
|
0b2c10d6ce | ||
|
|
cac3b9185b | ||
|
|
94888e653c | ||
|
|
936578b238 | ||
|
|
4fa7bf978c | ||
|
|
2f7c496944 | ||
|
|
5fe3ca86ff | ||
|
|
0ff8ecb41e | ||
|
|
08ed129a07 | ||
|
|
49ad197dfc | ||
|
|
82201d0e48 | ||
|
|
2b95332e8c | ||
|
|
e8ed62e540 | ||
|
|
e5725de7bb | ||
|
|
c47e811937 | ||
|
|
e0bac4c371 | ||
|
|
73a7adf572 | ||
|
|
5cf381a817 | ||
|
|
4603182320 | ||
|
|
ad207775ff | ||
|
|
d0f11a41c0 | ||
|
|
c7524705b3 | ||
|
|
50f9513cbb | ||
|
|
6fd80ba29c | ||
|
|
5b557941fa | ||
|
|
c6229934a0 | ||
|
|
ed28eaeccc | ||
|
|
3be910c238 | ||
|
|
d96dd6da2d | ||
|
|
f7c26ef41f | ||
|
|
57ad9cc91b | ||
|
|
926339594d | ||
|
|
1ba53ad68c | ||
|
|
6eb37ce079 | ||
|
|
54d660eee0 | ||
|
|
cf219fbfae | ||
|
|
d1d9ea0c48 | ||
|
|
4448d9c670 | ||
|
|
3909f3acae | ||
|
|
9f1274f3ab | ||
|
|
e6926e11ea | ||
|
|
16228c9dd1 | ||
|
|
57dabbca5c | ||
|
|
04439c6e64 | ||
|
|
0e0e688fe8 | ||
|
|
0603de4353 | ||
|
|
ea911db6dc | ||
|
|
c079c7aaae | ||
|
|
5420988a28 | ||
|
|
b56a1ab80a | ||
|
|
a6b34c1130 | ||
|
|
0de8002b7d | ||
|
|
84d648ff60 | ||
|
|
6ad6f6fce5 | ||
|
|
b7100ad871 | ||
|
|
38c0f1c639 | ||
|
|
64d7901d82 | ||
|
|
ab1015e5fa | ||
|
|
1753bf4422 | ||
|
|
7cb44dca27 | ||
|
|
2ec4b8b674 | ||
|
|
0d0633488d | ||
|
|
f3b3853d9d | ||
|
|
e550e5e22a | ||
|
|
2bf37ff495 | ||
|
|
fa84ec6774 | ||
|
|
e18e0416b1 | ||
|
|
34282162f8 | ||
|
|
7a043d31be | ||
|
|
b0e5d64bd7 | ||
|
|
1c9ac2daee | ||
|
|
c6b67e8a6f | ||
|
|
2e4873d103 | ||
|
|
3e483c4d85 | ||
|
|
032ea70380 | ||
|
|
df84dc4548 | ||
|
|
d4fa83f8bc | ||
|
|
448dbceadd | ||
|
|
b83b064992 | ||
|
|
133a0049e3 | ||
|
|
50b1c1337d | ||
|
|
d5deed44ca | ||
|
|
0f785b9530 | ||
|
|
eb1f0ecc14 | ||
|
|
b59995eeb8 | ||
|
|
d998d82357 | ||
|
|
f51ea72ce0 | ||
|
|
fe8d4c0d21 | ||
|
|
3843a2d5d1 | ||
|
|
839a237d6a | ||
|
|
4cd1e66ef3 | ||
|
|
2f18864fa5 | ||
|
|
6e71068f4f | ||
|
|
e40620effa | ||
|
|
d7dc0abd7b | ||
|
|
012ca2398f | ||
|
|
f07089d7b3 | ||
|
|
a54b59f208 | ||
|
|
bfd5feaf60 | ||
|
|
41d46d0d3b | ||
|
|
646c13ae15 | ||
|
|
3b9d3d9719 | ||
|
|
449383caa3 | ||
|
|
31fd097c0a | ||
|
|
11143d5b2c | ||
|
|
8480e03e9c | ||
|
|
0397425010 | ||
|
|
5396282e3d | ||
|
|
a9ff644de6 | ||
|
|
fe8e17f72c | ||
|
|
a1709e9edd | ||
|
|
24c0ca2ef9 | ||
|
|
9b26abd538 | ||
|
|
fc83c13166 | ||
|
|
9b69962053 | ||
|
|
4edbecc85d | ||
|
|
54f67266bb | ||
|
|
4ba48b018c | ||
|
|
2a3529c543 | ||
|
|
58408d710b | ||
|
|
161f66a12f | ||
|
|
6bde95c9a1 | ||
|
|
724ff8a188 | ||
|
|
feae158a50 | ||
|
|
780ac7a51e | ||
|
|
c4afb6bb30 | ||
|
|
8b5c4e805d | ||
|
|
f4e24a408f | ||
|
|
2781106d49 | ||
|
|
534a30a058 | ||
|
|
bb55741320 | ||
|
|
079bd6157b | ||
|
|
92cb1b23ed | ||
|
|
832facc526 | ||
|
|
c4fa6c472f | ||
|
|
a848dac3cf | ||
|
|
43a2ccf9c4 | ||
|
|
60cf3aeb95 | ||
|
|
84b174e841 | ||
|
|
40337d064d | ||
|
|
9fe585c854 | ||
|
|
4fce2ca2f1 | ||
|
|
4c11de0403 | ||
|
|
a9099efc45 | ||
|
|
6edb644f2e | ||
|
|
c239e1199f | ||
|
|
b713b7852a | ||
|
|
5d07d0c8e2 | ||
|
|
7076ba0760 | ||
|
|
81b816d4a4 | ||
|
|
9ebc909c7f | ||
|
|
af01b675b0 | ||
|
|
ce6ce5a058 | ||
|
|
bd4f6d4fcd | ||
|
|
6a4181158a | ||
|
|
a2746d09e8 | ||
|
|
b5d7219391 | ||
|
|
b09d5d99dc | ||
|
|
dbcc20f37f | ||
|
|
51340b56b8 | ||
|
|
160669817e | ||
|
|
6ca4479892 | ||
|
|
92740a25d4 | ||
|
|
56e4daccaf | ||
|
|
b546832b66 | ||
|
|
39e5b34af3 | ||
|
|
e699e08d13 | ||
|
|
af26b57e5e | ||
|
|
22fe589ae6 | ||
|
|
0a11b5a138 | ||
|
|
363f02710b | ||
|
|
6abfa232e7 | ||
|
|
bbfb12a120 | ||
|
|
5df2a0c516 | ||
|
|
8ecd14289a | ||
|
|
131ed42a4c | ||
|
|
6b8d4cd5a7 | ||
|
|
24a06511a2 | ||
|
|
09c7d1be0a | ||
|
|
f5666882de | ||
|
|
701f22404b | ||
|
|
d5fa60bdd5 | ||
|
|
9e14f733b7 | ||
|
|
29ade13ce7 | ||
|
|
03518badb8 | ||
|
|
24458fb0ca | ||
|
|
1c5b32763b | ||
|
|
3bd031bbb3 | ||
|
|
ea6e9f22b9 | ||
|
|
73309a3948 | ||
|
|
105313a0e3 | ||
|
|
d368cbed32 | ||
|
|
5f4dfbc922 | ||
|
|
967911e4c1 | ||
|
|
726091712d | ||
|
|
854afa7c73 | ||
|
|
2517268b1f | ||
|
|
fea8beabab | ||
|
|
7f9c95fa16 | ||
|
|
6f7e2a271e | ||
|
|
217ebdfa73 | ||
|
|
e014fda58e | ||
|
|
6d0360fd16 | ||
|
|
01a87b6143 | ||
|
|
f3e871492c | ||
|
|
bf8d744686 | ||
|
|
477d12968e | ||
|
|
dcb4136a96 | ||
|
|
2c0afafccf | ||
|
|
e56617253f | ||
|
|
500aaea4dd | ||
|
|
4444811f26 | ||
|
|
b63c6fac27 | ||
|
|
dfc76906d4 | ||
|
|
fbd17d4caf | ||
|
|
9c01589fb9 | ||
|
|
a0942afaa1 | ||
|
|
16fa21a4a6 | ||
|
|
241f1325c9 | ||
|
|
c1a63a557a | ||
|
|
e45e21368a | ||
|
|
f24b367479 | ||
|
|
8ba9cb1df7 | ||
|
|
d502f05910 | ||
|
|
241f47d947 | ||
|
|
19424f4119 | ||
|
|
635a40f305 | ||
|
|
1804d2e3a2 | ||
|
|
fea4870243 | ||
|
|
f54df67d11 | ||
|
|
1a998037f8 | ||
|
|
c481877c03 | ||
|
|
67df39690b | ||
|
|
674f14da78 | ||
|
|
0cfbdf642b | ||
|
|
c86d1c7eb2 | ||
|
|
f67c265533 | ||
|
|
969921640c | ||
|
|
0273fd7597 | ||
|
|
e84d0c5587 | ||
|
|
5267ec407c | ||
|
|
6714d8e0b7 | ||
|
|
b3439eab41 | ||
|
|
509118587a | ||
|
|
f482537dff | ||
|
|
0f95817746 | ||
|
|
70d1fadae7 | ||
|
|
499beb2fd7 | ||
|
|
de24f3854b | ||
|
|
a5e8ba1a70 | ||
|
|
45ad48042f | ||
|
|
550de966c7 | ||
|
|
c94342db31 | ||
|
|
94f1c7dd8d | ||
|
|
bf91e6790e | ||
|
|
251f44e568 | ||
|
|
922c4f6a63 | ||
|
|
2b5ec01381 | ||
|
|
7bcd562ff5 | ||
|
|
03852d0914 | ||
|
|
5f1e9a97c4 | ||
|
|
cd6f2cd4cb | ||
|
|
e679c7fabc | ||
|
|
6f5918d765 | ||
|
|
5a46ba0630 | ||
|
|
c1323719f4 | ||
|
|
8795fe6b90 | ||
|
|
a3f8bb0369 | ||
|
|
cd3c3bf4da | ||
|
|
652ee87bf5 | ||
|
|
5225ec4597 | ||
|
|
b2af720ddb | ||
|
|
94f07996f7 | ||
|
|
4839d5f369 | ||
|
|
022e07c278 | ||
|
|
620d71abdf | ||
|
|
85d00ab457 | ||
|
|
b30bd40860 | ||
|
|
4108362158 | ||
|
|
b27dc5f14e | ||
|
|
3c54378749 | ||
|
|
6240aa68f7 | ||
|
|
301af7fd9c | ||
|
|
41d529ebe2 | ||
|
|
cc6bb633ba | ||
|
|
31cf70c34c | ||
|
|
3399619395 | ||
|
|
cfc4cce08b | ||
|
|
f9e9f0654a | ||
|
|
73af0e84fa | ||
|
|
b33928ac91 | ||
|
|
3ac0c408de | ||
|
|
149f900811 | ||
|
|
9ede04ba9b | ||
|
|
f9cbed8b71 | ||
|
|
fa4da031e4 | ||
|
|
9511f308d0 | ||
|
|
52f43f0fcb | ||
|
|
4bb0ceeed5 | ||
|
|
279f648e9a | ||
|
|
411ec740ff | ||
|
|
6237ba5a43 | ||
|
|
5d65a9ad68 | ||
|
|
28f3f867c3 | ||
|
|
00f79aa28d | ||
|
|
6042d717e9 | ||
|
|
7afa54519f | ||
|
|
8c3a80fbf9 | ||
|
|
11b9829885 | ||
|
|
4798559545 | ||
|
|
8b34d6eb94 | ||
|
|
70700a1c97 | ||
|
|
d7420eb945 | ||
|
|
c9cfeb35c2 | ||
|
|
fda63a816f | ||
|
|
6329b6c27b | ||
|
|
9b4f781c5c | ||
|
|
63039fcfd6 | ||
|
|
d25f3757d6 | ||
|
|
1303fe6eb9 | ||
|
|
1682cc9462 | ||
|
|
605927676f | ||
|
|
dc41e6a41c | ||
|
|
e37c27313e | ||
|
|
e5d9756ebe | ||
|
|
e6f1c6f78a | ||
|
|
fceebded6d | ||
|
|
08b2dffa42 | ||
|
|
745e29959c | ||
|
|
aa1bab2c4a | ||
|
|
19c3e02b44 | ||
|
|
a45057200f | ||
|
|
74d6be3943 | ||
|
|
1fb3445692 | ||
|
|
195082d33b | ||
|
|
03dbc93961 | ||
|
|
d27f64480b | ||
|
|
5645663b71 | ||
|
|
dbc8092282 | ||
|
|
50d2b246d9 | ||
|
|
6220aca03e | ||
|
|
674993d23a | ||
|
|
f4c3f9b18f | ||
|
|
d65a7c4783 | ||
|
|
89541f7545 | ||
|
|
17f769c1c1 | ||
|
|
eb9e62f047 | ||
|
|
6ccd80f2ee | ||
|
|
d8251b9086 | ||
|
|
4cd1f45d90 | ||
|
|
2dc3f53a13 | ||
|
|
f5d8a02f79 | ||
|
|
a7c51ffae0 | ||
|
|
9746fd300f | ||
|
|
b5e17f91cd | ||
|
|
f5524153ba | ||
|
|
6d693d06fb | ||
|
|
4d7a6ee9be | ||
|
|
0fdaac6042 | ||
|
|
bb7ab369d7 | ||
|
|
d96cca2221 | ||
|
|
6ee87d9d7c | ||
|
|
95ce2a4ff7 | ||
|
|
19e1387bf1 | ||
|
|
ec4c5bed5d | ||
|
|
ae38aa63af | ||
|
|
cdcd677aa0 | ||
|
|
a5eb9fc36d | ||
|
|
96efd2312b | ||
|
|
e7699c4f6b | ||
|
|
d0fbebb77c | ||
|
|
46bb5b6f55 | ||
|
|
b799ed074a | ||
|
|
eee41ee111 | ||
|
|
6ac81e0b9c | ||
|
|
5970899029 | ||
|
|
5bb0389538 | ||
|
|
92cb6a378a | ||
|
|
b09932e92a | ||
|
|
63603f38d6 | ||
|
|
42db31683f | ||
|
|
c40a9c397f | ||
|
|
4014ebad92 | ||
|
|
bb7cb05571 | ||
|
|
30b2bd5d9f | ||
|
|
8d5ab05aa0 | ||
|
|
db501da34d | ||
|
|
8d60370612 | ||
|
|
052f011288 | ||
|
|
6e23579bd0 | ||
|
|
7331f4e5de | ||
|
|
11f1333af7 | ||
|
|
74f70fdbc9 | ||
|
|
0006fb039d | ||
|
|
0894f7740c | ||
|
|
c3f07dc366 | ||
|
|
ca8f1051eb | ||
|
|
c7692a2e9f | ||
|
|
53badf7b61 | ||
|
|
f801c5f72f | ||
|
|
327880ba51 | ||
|
|
7bb8b4feda | ||
|
|
36b1e1f061 | ||
|
|
4507a90bb6 | ||
|
|
550f68306c | ||
|
|
2b668566ce | ||
|
|
ee414ea366 | ||
|
|
5761b5d595 | ||
|
|
07f476dfc4 | ||
|
|
f5e9f07321 | ||
|
|
05c69ee26a | ||
|
|
1623e09122 | ||
|
|
696aa7c505 | ||
|
|
c53dce0805 | ||
|
|
cd8b5a0354 | ||
|
|
b51f24eb8e | ||
|
|
ae3b4368ee | ||
|
|
61eef93618 | ||
|
|
fa0a2ec6fe | ||
|
|
7ece10a643 | ||
|
|
71c8eae47e | ||
|
|
267b0837dc | ||
|
|
c713537d56 | ||
|
|
e374229707 | ||
|
|
f834581a8e | ||
|
|
15fcde5229 | ||
|
|
96c5dd3941 | ||
|
|
65a03e76bf | ||
|
|
43525f6493 | ||
|
|
7457626f62 | ||
|
|
08c22e8921 | ||
|
|
4ff533ec17 | ||
|
|
7680e3d079 | ||
|
|
305801dd0e | ||
|
|
9951b85d60 | ||
|
|
ff21ec9432 | ||
|
|
5e15d77bf2 | ||
|
|
d833c73fc4 | ||
|
|
795a80dfb0 | ||
|
|
f5e4acdd8a | ||
|
|
eb4c1bb355 | ||
|
|
6dfa95cc87 | ||
|
|
eb9d974a8b | ||
|
|
d41a1f4a56 | ||
|
|
138ad556a3 | ||
|
|
37124e6e45 | ||
|
|
bd412bf87f | ||
|
|
6f9b31f568 | ||
|
|
c858740c4f | ||
|
|
bfefe634a1 | ||
|
|
7b7b9e1cd7 | ||
|
|
0a4b8b0a25 | ||
|
|
f28183dcbe | ||
|
|
abdf79454f | ||
|
|
46b570b71d | ||
|
|
616a9b5f6b | ||
|
|
003a4cdc2b | ||
|
|
54ea05d8bb | ||
|
|
d0eea0cabb | ||
|
|
f0297dfe03 | ||
|
|
ef1f36f8e4 | ||
|
|
a733c95dcc | ||
|
|
5a81a0661b | ||
|
|
83e04960af | ||
|
|
2a8dc69cbb | ||
|
|
5d82d08af3 | ||
|
|
c265825166 | ||
|
|
ed0126fb63 | ||
|
|
8c7267b379 | ||
|
|
d85ce22975 | ||
|
|
a9091a1e37 | ||
|
|
d3b0ac8e06 | ||
|
|
435674fb44 | ||
|
|
eacbae72fd | ||
|
|
34550d4b7c | ||
|
|
c6ff87dbd6 | ||
|
|
04efe65f90 | ||
|
|
55afde6251 | ||
|
|
7039b6c8aa | ||
|
|
cff984261e | ||
|
|
4819ab9c69 | ||
|
|
25336da708 | ||
|
|
4941f6a16b | ||
|
|
d7e93058d4 | ||
|
|
c20a595370 | ||
|
|
eec1104d6e | ||
|
|
741b35edf5 | ||
|
|
c01cfcf3b6 | ||
|
|
643cd472ef | ||
|
|
668d727fc2 | ||
|
|
423ee35846 | ||
|
|
31c7855212 | ||
|
|
211f3942b6 | ||
|
|
beae282735 | ||
|
|
635348efb9 | ||
|
|
fa335c782f | ||
|
|
b019a58525 | ||
|
|
78eef6c343 | ||
|
|
cbcefb5d2f | ||
|
|
149085fb57 | ||
|
|
991eddb691 | ||
|
|
91de471376 | ||
|
|
c2d7e22749 | ||
|
|
e7c42794a0 | ||
|
|
3a8dfc07ed | ||
|
|
077b7f6505 | ||
|
|
240fa93bc5 | ||
|
|
6c7f846917 | ||
|
|
6db7c3b92c | ||
|
|
030267107a | ||
|
|
1c300a9881 | ||
|
|
0e9b33b822 | ||
|
|
36e3d1e703 | ||
|
|
f53264b613 | ||
|
|
414554ae5e | ||
|
|
150f945592 | ||
|
|
9a84afece1 | ||
|
|
e0c101c5ae | ||
|
|
e3a562aea0 | ||
|
|
4966e8ee08 | ||
|
|
d3542d5892 | ||
|
|
62d04b0fc7 | ||
|
|
6ecbc85448 | ||
|
|
b9b1eae6fb | ||
|
|
e95e42930d | ||
|
|
3cabb69014 | ||
|
|
ad8f90f177 | ||
|
|
8a62cf1699 | ||
|
|
a6b54dae99 | ||
|
|
112684bcb9 | ||
|
|
682e07c3cd | ||
|
|
9593ce16d9 | ||
|
|
2545c8b031 | ||
|
|
c9793e7029 | ||
|
|
4757132452 | ||
|
|
561b3b67b3 | ||
|
|
566f33e6ad | ||
|
|
83a75bac80 | ||
|
|
d8772f5685 | ||
|
|
df510187d6 | ||
|
|
137b9b72e7 | ||
|
|
09b4979673 | ||
|
|
eff8185d7c | ||
|
|
83ee94dd08 | ||
|
|
aae52ac2ee | ||
|
|
a7c4295c58 |
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a bug report to help improve descheduler
|
||||||
|
title: ''
|
||||||
|
labels: 'kind/bug'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Please answer these questions before submitting your bug report. Thanks! -->
|
||||||
|
|
||||||
|
**What version of descheduler are you using?**
|
||||||
|
|
||||||
|
descheduler version:
|
||||||
|
|
||||||
|
|
||||||
|
**Does this issue reproduce with the latest release?**
|
||||||
|
|
||||||
|
|
||||||
|
**Which descheduler CLI options are you using?**
|
||||||
|
|
||||||
|
|
||||||
|
**Please provide a copy of your descheduler policy config file**
|
||||||
|
|
||||||
|
|
||||||
|
**What k8s version are you using (`kubectl version`)?**
|
||||||
|
|
||||||
|
<details><summary><code>kubectl version</code> Output</summary><br><pre>
|
||||||
|
$ kubectl version
|
||||||
|
|
||||||
|
</pre></details>
|
||||||
|
|
||||||
|
|
||||||
|
**What did you do?**
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If possible, provide a recipe for reproducing the error.
|
||||||
|
A detailed sequence of steps describing what to do to observe the issue is good.
|
||||||
|
A complete runnable bash shell script is best.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
**What did you expect to see?**
|
||||||
|
|
||||||
|
|
||||||
|
**What did you see instead?**
|
||||||
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for descheduler
|
||||||
|
title: ''
|
||||||
|
labels: 'kind/feature'
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Please answer these questions before submitting your feature request. Thanks! -->
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
|
**What version of descheduler are you using?**
|
||||||
|
|
||||||
|
descheduler version:
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
18
.github/ISSUE_TEMPLATE/misc_request.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/misc_request.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
name: Miscellaneous
|
||||||
|
about: Not a bug and not a feature
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please do not use this to submit a bug report or feature request. Use the
|
||||||
|
bug report or feature request options instead.
|
||||||
|
|
||||||
|
Also, please consider posting in the Kubernetes Slack #sig-scheduling channel
|
||||||
|
instead of opening an issue if this is a support request.
|
||||||
|
|
||||||
|
Thanks!
|
||||||
|
-->
|
||||||
31
.github/workflows/release.yaml
vendored
Normal file
31
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Release Charts
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- release-*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name "$GITHUB_ACTOR"
|
||||||
|
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v1
|
||||||
|
with:
|
||||||
|
version: v3.4.0
|
||||||
|
|
||||||
|
- name: Run chart-releaser
|
||||||
|
uses: helm/chart-releaser-action@v1.1.0
|
||||||
|
env:
|
||||||
|
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
CR_RELEASE_NAME_TEMPLATE: "descheduler-helm-chart-{{ .Version }}"
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1 +1,7 @@
|
|||||||
_output/
|
_output/
|
||||||
|
_tmp/
|
||||||
|
vendordiff.patch
|
||||||
|
.idea/
|
||||||
|
*.code-workspace
|
||||||
|
.vscode/
|
||||||
|
kind
|
||||||
31
.travis.yml
31
.travis.yml
@@ -1,31 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
|
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.13.x
|
|
||||||
env:
|
|
||||||
- K8S_VERSION=v1.17.0
|
|
||||||
- K8S_VERSION=v1.16.4
|
|
||||||
- K8S_VERSION=v1.15.7
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
before_script:
|
|
||||||
- curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
|
|
||||||
- wget https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-linux-amd64
|
|
||||||
- chmod +x kind-linux-amd64
|
|
||||||
- mv kind-linux-amd64 kind
|
|
||||||
- export PATH=$PATH:$PWD
|
|
||||||
- kind create cluster --image kindest/node:${K8S_VERSION} --config=$TRAVIS_BUILD_DIR/hack/kind_config.yaml
|
|
||||||
- export KUBECONFIG="$(kind get kubeconfig-path)"
|
|
||||||
- docker pull kubernetes/pause
|
|
||||||
- kind load docker-image kubernetes/pause
|
|
||||||
- kind get kubeconfig > /tmp/admin.conf
|
|
||||||
script:
|
|
||||||
- mkdir -p ~/gopath/src/sigs.k8s.io/
|
|
||||||
- mv ~/gopath/src/github.com/kubernetes-sigs/descheduler ~/gopath/src/sigs.k8s.io/.
|
|
||||||
- hack/verify-gofmt.sh
|
|
||||||
- make lint
|
|
||||||
- make build
|
|
||||||
- make test-unit
|
|
||||||
- make test-e2e
|
|
||||||
@@ -10,7 +10,7 @@ We have full documentation on how to get started contributing here:
|
|||||||
|
|
||||||
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
|
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
|
||||||
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
|
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
|
||||||
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
|
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md) - Common resources for existing developers
|
||||||
|
|
||||||
## Mentorship
|
## Mentorship
|
||||||
|
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@@ -11,15 +11,19 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
FROM golang:1.13.6
|
FROM golang:1.18.2
|
||||||
|
|
||||||
WORKDIR /go/src/sigs.k8s.io/descheduler
|
WORKDIR /go/src/sigs.k8s.io/descheduler
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN make
|
ARG ARCH
|
||||||
|
ARG VERSION
|
||||||
|
RUN VERSION=${VERSION} make build.$ARCH
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
||||||
MAINTAINER Avesh Agarwal <avagarwa@redhat.com>
|
MAINTAINER Kubernetes SIG Scheduling <kubernetes-sig-scheduling@googlegroups.com>
|
||||||
|
|
||||||
|
USER 1000
|
||||||
|
|
||||||
COPY --from=0 /go/src/sigs.k8s.io/descheduler/_output/bin/descheduler /bin/descheduler
|
COPY --from=0 /go/src/sigs.k8s.io/descheduler/_output/bin/descheduler /bin/descheduler
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
||||||
MAINTAINER Avesh Agarwal <avagarwa@redhat.com>
|
MAINTAINER Kubernetes SIG Scheduling <kubernetes-sig-scheduling@googlegroups.com>
|
||||||
|
|
||||||
|
USER 1000
|
||||||
|
|
||||||
COPY _output/bin/descheduler /bin/descheduler
|
COPY _output/bin/descheduler /bin/descheduler
|
||||||
|
|
||||||
|
|||||||
94
Makefile
94
Makefile
@@ -14,16 +14,18 @@
|
|||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
|
||||||
# VERSION is currently based on the last commit
|
# VERSION is based on a date stamp plus the last commit
|
||||||
VERSION?=$(shell git describe --tags)
|
VERSION?=v$(shell date +%Y%m%d)-$(shell git describe --tags)
|
||||||
COMMIT=$(shell git rev-parse HEAD)
|
BRANCH?=$(shell git branch --show-current)
|
||||||
|
SHA1?=$(shell git rev-parse HEAD)
|
||||||
BUILD=$(shell date +%FT%T%z)
|
BUILD=$(shell date +%FT%T%z)
|
||||||
LDFLAG_LOCATION=sigs.k8s.io/descheduler/cmd/descheduler/app
|
LDFLAG_LOCATION=sigs.k8s.io/descheduler/pkg/version
|
||||||
|
ARCHS = amd64 arm arm64
|
||||||
|
|
||||||
LDFLAGS=-ldflags "-X ${LDFLAG_LOCATION}.version=${VERSION} -X ${LDFLAG_LOCATION}.buildDate=${BUILD} -X ${LDFLAG_LOCATION}.gitCommit=${COMMIT}"
|
LDFLAGS=-ldflags "-X ${LDFLAG_LOCATION}.version=${VERSION} -X ${LDFLAG_LOCATION}.buildDate=${BUILD} -X ${LDFLAG_LOCATION}.gitbranch=${BRANCH} -X ${LDFLAG_LOCATION}.gitsha1=${SHA1}"
|
||||||
|
|
||||||
GOLANGCI_VERSION := v1.15.0
|
GOLANGCI_VERSION := v1.46.1
|
||||||
HAS_GOLANGCI := $(shell which golangci-lint)
|
HAS_GOLANGCI := $(shell ls _output/bin/golangci-lint 2> /dev/null)
|
||||||
|
|
||||||
# REGISTRY is the container registry to push
|
# REGISTRY is the container registry to push
|
||||||
# into. The default is to push to the staging
|
# into. The default is to push to the staging
|
||||||
@@ -41,26 +43,74 @@ IMAGE_GCLOUD:=$(REGISTRY)/descheduler:$(VERSION)
|
|||||||
# In the future binaries can be uploaded to
|
# In the future binaries can be uploaded to
|
||||||
# GCS bucket gs://k8s-staging-descheduler.
|
# GCS bucket gs://k8s-staging-descheduler.
|
||||||
|
|
||||||
|
HAS_HELM := $(shell which helm 2> /dev/null)
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
CGO_ENABLED=0 go build ${LDFLAGS} -o _output/bin/descheduler sigs.k8s.io/descheduler/cmd/descheduler
|
CGO_ENABLED=0 go build ${LDFLAGS} -o _output/bin/descheduler sigs.k8s.io/descheduler/cmd/descheduler
|
||||||
|
|
||||||
|
build.amd64:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -o _output/bin/descheduler sigs.k8s.io/descheduler/cmd/descheduler
|
||||||
|
|
||||||
|
build.arm:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build ${LDFLAGS} -o _output/bin/descheduler sigs.k8s.io/descheduler/cmd/descheduler
|
||||||
|
|
||||||
|
build.arm64:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build ${LDFLAGS} -o _output/bin/descheduler sigs.k8s.io/descheduler/cmd/descheduler
|
||||||
|
|
||||||
dev-image: build
|
dev-image: build
|
||||||
docker build -f Dockerfile.dev -t $(IMAGE) .
|
docker build -f Dockerfile.dev -t $(IMAGE) .
|
||||||
|
|
||||||
image:
|
image:
|
||||||
docker build -t $(IMAGE) .
|
docker build --build-arg VERSION="$(VERSION)" --build-arg ARCH="amd64" -t $(IMAGE) .
|
||||||
|
|
||||||
push-container-to-gcloud: image
|
image.amd64:
|
||||||
|
docker build --build-arg VERSION="$(VERSION)" --build-arg ARCH="amd64" -t $(IMAGE)-amd64 .
|
||||||
|
|
||||||
|
image.arm:
|
||||||
|
docker build --build-arg VERSION="$(VERSION)" --build-arg ARCH="arm" -t $(IMAGE)-arm .
|
||||||
|
|
||||||
|
image.arm64:
|
||||||
|
docker build --build-arg VERSION="$(VERSION)" --build-arg ARCH="arm64" -t $(IMAGE)-arm64 .
|
||||||
|
|
||||||
|
push: image
|
||||||
gcloud auth configure-docker
|
gcloud auth configure-docker
|
||||||
docker tag $(IMAGE) $(IMAGE_GCLOUD)
|
docker tag $(IMAGE) $(IMAGE_GCLOUD)
|
||||||
docker push $(IMAGE_GCLOUD)
|
docker push $(IMAGE_GCLOUD)
|
||||||
|
|
||||||
push: push-container-to-gcloud
|
push-all: image.amd64 image.arm image.arm64
|
||||||
|
gcloud auth configure-docker
|
||||||
|
for arch in $(ARCHS); do \
|
||||||
|
docker tag $(IMAGE)-$${arch} $(IMAGE_GCLOUD)-$${arch} ;\
|
||||||
|
docker push $(IMAGE_GCLOUD)-$${arch} ;\
|
||||||
|
done
|
||||||
|
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create $(IMAGE_GCLOUD) $(addprefix --amend $(IMAGE_GCLOUD)-, $(ARCHS))
|
||||||
|
for arch in $(ARCHS); do \
|
||||||
|
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate --arch $${arch} $(IMAGE_GCLOUD) $(IMAGE_GCLOUD)-$${arch} ;\
|
||||||
|
done
|
||||||
|
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push $(IMAGE_GCLOUD) ;\
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf _output
|
rm -rf _output
|
||||||
|
rm -rf _tmp
|
||||||
|
|
||||||
|
verify: verify-govet verify-spelling verify-gofmt verify-vendor lint lint-chart verify-toc verify-gen
|
||||||
|
|
||||||
|
verify-govet:
|
||||||
|
./hack/verify-govet.sh
|
||||||
|
|
||||||
|
verify-spelling:
|
||||||
|
./hack/verify-spelling.sh
|
||||||
|
|
||||||
|
verify-gofmt:
|
||||||
|
./hack/verify-gofmt.sh
|
||||||
|
|
||||||
|
verify-vendor:
|
||||||
|
./hack/verify-vendor.sh
|
||||||
|
|
||||||
|
verify-toc:
|
||||||
|
./hack/verify-toc.sh
|
||||||
|
|
||||||
test-unit:
|
test-unit:
|
||||||
./test/run-unit-tests.sh
|
./test/run-unit-tests.sh
|
||||||
@@ -72,10 +122,26 @@ gen:
|
|||||||
./hack/update-generated-conversions.sh
|
./hack/update-generated-conversions.sh
|
||||||
./hack/update-generated-deep-copies.sh
|
./hack/update-generated-deep-copies.sh
|
||||||
./hack/update-generated-defaulters.sh
|
./hack/update-generated-defaulters.sh
|
||||||
#undo go mod changes caused by above.
|
./hack/update-toc.sh
|
||||||
go mod tidy
|
|
||||||
|
verify-gen:
|
||||||
|
./hack/verify-conversions.sh
|
||||||
|
./hack/verify-deep-copies.sh
|
||||||
|
./hack/verify-defaulters.sh
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
ifndef HAS_GOLANGCI
|
ifndef HAS_GOLANGCI
|
||||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin ${GOLANGCI_VERSION}
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ./_output/bin ${GOLANGCI_VERSION}
|
||||||
|
endif
|
||||||
|
./_output/bin/golangci-lint run
|
||||||
|
|
||||||
|
lint-chart: ensure-helm-install
|
||||||
|
helm lint ./charts/descheduler
|
||||||
|
|
||||||
|
test-helm: ensure-helm-install
|
||||||
|
./test/run-helm-tests.sh
|
||||||
|
|
||||||
|
ensure-helm-install:
|
||||||
|
ifndef HAS_HELM
|
||||||
|
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 && chmod 700 ./get_helm.sh && ./get_helm.sh
|
||||||
endif
|
endif
|
||||||
golangci-lint run
|
|
||||||
|
|||||||
14
OWNERS
14
OWNERS
@@ -1,10 +1,18 @@
|
|||||||
approvers:
|
approvers:
|
||||||
- aveshagarwal
|
- damemi
|
||||||
- k82cn
|
- ingvagabund
|
||||||
- ravisantoshgudimetla
|
- seanmalloy
|
||||||
reviewers:
|
reviewers:
|
||||||
- aveshagarwal
|
- aveshagarwal
|
||||||
- k82cn
|
- k82cn
|
||||||
- ravisantoshgudimetla
|
- ravisantoshgudimetla
|
||||||
- damemi
|
- damemi
|
||||||
- seanmalloy
|
- seanmalloy
|
||||||
|
- ingvagabund
|
||||||
|
- lixiang233
|
||||||
|
- a7i
|
||||||
|
- janeliul
|
||||||
|
emeritus_approvers:
|
||||||
|
- aveshagarwal
|
||||||
|
- k82cn
|
||||||
|
- ravisantoshgudimetla
|
||||||
|
|||||||
862
README.md
862
README.md
@@ -1,16 +1,14 @@
|
|||||||
[](https://travis-ci.org/kubernetes-sigs/descheduler)
|
[](https://goreportcard.com/report/sigs.k8s.io/descheduler)
|
||||||
[](https://goreportcard.com/report/sigs.k8s.io/descheduler)
|

|
||||||
|
|
||||||
# Descheduler for Kubernetes
|
# Descheduler for Kubernetes
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
Scheduling in Kubernetes is the process of binding pending pods to nodes, and is performed by
|
Scheduling in Kubernetes is the process of binding pending pods to nodes, and is performed by
|
||||||
a component of Kubernetes called kube-scheduler. The scheduler's decisions, whether or where a
|
a component of Kubernetes called kube-scheduler. The scheduler's decisions, whether or where a
|
||||||
pod can or can not be scheduled, are guided by its configurable policy which comprises of set of
|
pod can or can not be scheduled, are guided by its configurable policy which comprises of set of
|
||||||
rules, called predicates and priorities. The scheduler's decisions are influenced by its view of
|
rules, called predicates and priorities. The scheduler's decisions are influenced by its view of
|
||||||
a Kubernetes cluster at that point of time when a new pod appears first time for scheduling.
|
a Kubernetes cluster at that point of time when a new pod appears for scheduling.
|
||||||
As Kubernetes clusters are very dynamic and their state change over time, there may be desired
|
As Kubernetes clusters are very dynamic and their state changes over time, there may be desire
|
||||||
to move already running pods to some other nodes for various reasons:
|
to move already running pods to some other nodes for various reasons:
|
||||||
|
|
||||||
* Some nodes are under or over utilized.
|
* Some nodes are under or over utilized.
|
||||||
@@ -24,87 +22,179 @@ Descheduler, based on its policy, finds pods that can be moved and evicts them.
|
|||||||
note, in current implementation, descheduler does not schedule replacement of evicted pods
|
note, in current implementation, descheduler does not schedule replacement of evicted pods
|
||||||
but relies on the default scheduler for that.
|
but relies on the default scheduler for that.
|
||||||
|
|
||||||
## Build and Run
|
Table of Contents
|
||||||
|
=================
|
||||||
|
<!-- toc -->
|
||||||
|
- [Quick Start](#quick-start)
|
||||||
|
- [Run As A Job](#run-as-a-job)
|
||||||
|
- [Run As A CronJob](#run-as-a-cronjob)
|
||||||
|
- [Run As A Deployment](#run-as-a-deployment)
|
||||||
|
- [Install Using Helm](#install-using-helm)
|
||||||
|
- [Install Using Kustomize](#install-using-kustomize)
|
||||||
|
- [User Guide](#user-guide)
|
||||||
|
- [Policy and Strategies](#policy-and-strategies)
|
||||||
|
- [RemoveDuplicates](#removeduplicates)
|
||||||
|
- [LowNodeUtilization](#lownodeutilization)
|
||||||
|
- [HighNodeUtilization](#highnodeutilization)
|
||||||
|
- [RemovePodsViolatingInterPodAntiAffinity](#removepodsviolatinginterpodantiaffinity)
|
||||||
|
- [RemovePodsViolatingNodeAffinity](#removepodsviolatingnodeaffinity)
|
||||||
|
- [RemovePodsViolatingNodeTaints](#removepodsviolatingnodetaints)
|
||||||
|
- [RemovePodsViolatingTopologySpreadConstraint](#removepodsviolatingtopologyspreadconstraint)
|
||||||
|
- [RemovePodsHavingTooManyRestarts](#removepodshavingtoomanyrestarts)
|
||||||
|
- [PodLifeTime](#podlifetime)
|
||||||
|
- [RemoveFailedPods](#removefailedpods)
|
||||||
|
- [Filter Pods](#filter-pods)
|
||||||
|
- [Namespace filtering](#namespace-filtering)
|
||||||
|
- [Priority filtering](#priority-filtering)
|
||||||
|
- [Label filtering](#label-filtering)
|
||||||
|
- [Node Fit filtering](#node-fit-filtering)
|
||||||
|
- [Pod Evictions](#pod-evictions)
|
||||||
|
- [Pod Disruption Budget (PDB)](#pod-disruption-budget-pdb)
|
||||||
|
- [High Availability](#high-availability)
|
||||||
|
- [Configure HA Mode](#configure-ha-mode)
|
||||||
|
- [Metrics](#metrics)
|
||||||
|
- [Compatibility Matrix](#compatibility-matrix)
|
||||||
|
- [Getting Involved and Contributing](#getting-involved-and-contributing)
|
||||||
|
- [Communicating With Contributors](#communicating-with-contributors)
|
||||||
|
- [Roadmap](#roadmap)
|
||||||
|
- [Code of conduct](#code-of-conduct)
|
||||||
|
<!-- /toc -->
|
||||||
|
|
||||||
- Checkout the repo into your $GOPATH directory under src/sigs.k8s.io/descheduler
|
## Quick Start
|
||||||
|
|
||||||
Build descheduler:
|
The descheduler can be run as a `Job`, `CronJob`, or `Deployment` inside of a k8s cluster. It has the
|
||||||
|
advantage of being able to be run multiple times without needing user intervention.
|
||||||
|
The descheduler pod is run as a critical pod in the `kube-system` namespace to avoid
|
||||||
|
being evicted by itself or by the kubelet.
|
||||||
|
|
||||||
```sh
|
### Run As A Job
|
||||||
$ make
|
|
||||||
```
|
|
||||||
|
|
||||||
and run descheduler:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ ./_output/bin/descheduler --kubeconfig <path to kubeconfig> --policy-config-file <path-to-policy-file>
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want more information about what descheduler is doing add `-v 1` to the command line
|
|
||||||
|
|
||||||
For more information about available options run:
|
|
||||||
```
|
|
||||||
$ ./_output/bin/descheduler --help
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running Descheduler as a Job or CronJob
|
|
||||||
|
|
||||||
The descheduler can be run as a job or cronjob inside of a pod. It has the advantage of
|
|
||||||
being able to be run multiple times without needing user intervention.
|
|
||||||
The descheduler pod is run as a critical pod to avoid being evicted by itself,
|
|
||||||
or by the kubelet due to an eviction event. Since critical pods are created in the
|
|
||||||
`kube-system` namespace, the descheduler job and its pod will also be created
|
|
||||||
in `kube-system` namespace.
|
|
||||||
|
|
||||||
### Setup RBAC
|
|
||||||
|
|
||||||
To give necessary permissions for the descheduler to work in a pod.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ kubectl create -f kubernetes/rbac.yaml
|
kubectl create -f kubernetes/base/rbac.yaml
|
||||||
|
kubectl create -f kubernetes/base/configmap.yaml
|
||||||
|
kubectl create -f kubernetes/job/job.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create a configmap to store descheduler policy
|
### Run As A CronJob
|
||||||
|
|
||||||
```
|
```
|
||||||
$ kubectl create -f kubernetes/configmap.yaml
|
kubectl create -f kubernetes/base/rbac.yaml
|
||||||
|
kubectl create -f kubernetes/base/configmap.yaml
|
||||||
|
kubectl create -f kubernetes/cronjob/cronjob.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Create a Job or CronJob
|
### Run As A Deployment
|
||||||
|
|
||||||
As a Job.
|
|
||||||
```
|
```
|
||||||
$ kubectl create -f kubernetes/job.yaml
|
kubectl create -f kubernetes/base/rbac.yaml
|
||||||
|
kubectl create -f kubernetes/base/configmap.yaml
|
||||||
|
kubectl create -f kubernetes/deployment/deployment.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Or as a CronJob.
|
### Install Using Helm
|
||||||
|
|
||||||
|
Starting with release v0.18.0 there is an official helm chart that can be used to install the
|
||||||
|
descheduler. See the [helm chart README](https://github.com/kubernetes-sigs/descheduler/blob/master/charts/descheduler/README.md) for detailed instructions.
|
||||||
|
|
||||||
|
The descheduler helm chart is also listed on the [artifact hub](https://artifacthub.io/packages/helm/descheduler/descheduler).
|
||||||
|
|
||||||
|
### Install Using Kustomize
|
||||||
|
|
||||||
|
You can use kustomize to install descheduler.
|
||||||
|
See the [resources | Kustomize](https://kubectl.docs.kubernetes.io/references/kustomize/resource/) for detailed instructions.
|
||||||
|
|
||||||
|
Run As A Job
|
||||||
```
|
```
|
||||||
$ kubectl create -f kubernetes/cronjob.yaml
|
kustomize build 'github.com/kubernetes-sigs/descheduler/kubernetes/job?ref=v0.24.1' | kubectl apply -f -
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Run As A CronJob
|
||||||
|
```
|
||||||
|
kustomize build 'github.com/kubernetes-sigs/descheduler/kubernetes/cronjob?ref=v0.24.1' | kubectl apply -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
Run As A Deployment
|
||||||
|
```
|
||||||
|
kustomize build 'github.com/kubernetes-sigs/descheduler/kubernetes/deployment?ref=v0.24.1' | kubectl apply -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
## User Guide
|
||||||
|
|
||||||
|
See the [user guide](docs/user-guide.md) in the `/docs` directory.
|
||||||
|
|
||||||
## Policy and Strategies
|
## Policy and Strategies
|
||||||
|
|
||||||
Descheduler's policy is configurable and includes strategies to be enabled or disabled.
|
Descheduler's policy is configurable and includes strategies that can be enabled or disabled. By default, all strategies are enabled.
|
||||||
Five strategies, `RemoveDuplicates`, `LowNodeUtilization`, `RemovePodsViolatingInterPodAntiAffinity`, `RemovePodsViolatingNodeAffinity` , `RemovePodsViolatingNodeTaints` are currently implemented.
|
|
||||||
As part of the policy, the parameters associated with the strategies can be configured too.
|
The policy includes a common configuration that applies to all the strategies:
|
||||||
By default, all strategies are enabled.
|
| Name | Default Value | Description |
|
||||||
|
|------|---------------|-------------|
|
||||||
|
| `nodeSelector` | `nil` | limiting the nodes which are processed |
|
||||||
|
| `evictLocalStoragePods` | `false` | allows eviction of pods with local storage |
|
||||||
|
| `evictSystemCriticalPods` | `false` | [Warning: Will evict Kubernetes system pods] allows eviction of pods with any priority, including system pods like kube-dns |
|
||||||
|
| `ignorePvcPods` | `false` | set whether PVC pods should be evicted or ignored |
|
||||||
|
| `maxNoOfPodsToEvictPerNode` | `nil` | maximum number of pods evicted from each node (summed through all strategies) |
|
||||||
|
| `maxNoOfPodsToEvictPerNamespace` | `nil` | maximum number of pods evicted from each namespace (summed through all strategies) |
|
||||||
|
| `evictFailedBarePods` | `false` | allow eviction of pods without owner references and in failed phase |
|
||||||
|
|
||||||
|
As part of the policy, the parameters associated with each strategy can be configured.
|
||||||
|
See each strategy for details on available parameters.
|
||||||
|
|
||||||
|
**Policy:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
nodeSelector: prod=dev
|
||||||
|
evictFailedBarePods: false
|
||||||
|
evictLocalStoragePods: true
|
||||||
|
evictSystemCriticalPods: true
|
||||||
|
maxNoOfPodsToEvictPerNode: 40
|
||||||
|
ignorePvcPods: false
|
||||||
|
strategies:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The following diagram provides a visualization of most of the strategies to help
|
||||||
|
categorize how strategies fit together.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### RemoveDuplicates
|
### RemoveDuplicates
|
||||||
|
|
||||||
This strategy makes sure that there is only one pod associated with a Replica Set (RS),
|
This strategy makes sure that there is only one pod associated with a ReplicaSet (RS),
|
||||||
Replication Controller (RC), Deployment, or Job running on same node. If there are more,
|
ReplicationController (RC), StatefulSet, or Job running on the same node. If there are more,
|
||||||
those duplicate pods are evicted for better spreading of pods in a cluster. This issue could happen
|
those duplicate pods are evicted for better spreading of pods in a cluster. This issue could happen
|
||||||
if some nodes went down due to whatever reasons, and pods on them were moved to other nodes leading to
|
if some nodes went down due to whatever reasons, and pods on them were moved to other nodes leading to
|
||||||
more than one pod associated with RS or RC, for example, running on same node. Once the failed nodes
|
more than one pod associated with a RS or RC, for example, running on the same node. Once the failed nodes
|
||||||
are ready again, this strategy could be enabled to evict those duplicate pods. Currently, there are no
|
are ready again, this strategy could be enabled to evict those duplicate pods.
|
||||||
parameters associated with this strategy. To disable this strategy, the policy should look like:
|
|
||||||
|
|
||||||
```
|
It provides one optional parameter, `excludeOwnerKinds`, which is a list of OwnerRef `Kind`s. If a pod
|
||||||
|
has any of these `Kind`s listed as an `OwnerRef`, that pod will not be considered for eviction. Note that
|
||||||
|
pods created by Deployments are considered for eviction by this strategy. The `excludeOwnerKinds` parameter
|
||||||
|
should include `ReplicaSet` to have pods created by Deployments excluded.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`excludeOwnerKinds`|list(string)|
|
||||||
|
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```yaml
|
||||||
apiVersion: "descheduler/v1alpha1"
|
apiVersion: "descheduler/v1alpha1"
|
||||||
kind: "DeschedulerPolicy"
|
kind: "DeschedulerPolicy"
|
||||||
strategies:
|
strategies:
|
||||||
"RemoveDuplicates":
|
"RemoveDuplicates":
|
||||||
enabled: false
|
enabled: true
|
||||||
|
params:
|
||||||
|
removeDuplicates:
|
||||||
|
excludeOwnerKinds:
|
||||||
|
- "ReplicaSet"
|
||||||
```
|
```
|
||||||
|
|
||||||
### LowNodeUtilization
|
### LowNodeUtilization
|
||||||
@@ -113,20 +203,50 @@ This strategy finds nodes that are under utilized and evicts pods, if possible,
|
|||||||
in the hope that recreation of evicted pods will be scheduled on these underutilized nodes. The
|
in the hope that recreation of evicted pods will be scheduled on these underutilized nodes. The
|
||||||
parameters of this strategy are configured under `nodeResourceUtilizationThresholds`.
|
parameters of this strategy are configured under `nodeResourceUtilizationThresholds`.
|
||||||
|
|
||||||
The under utilization of nodes is determined by a configurable threshold, `thresholds`. The threshold
|
The under utilization of nodes is determined by a configurable threshold `thresholds`. The threshold
|
||||||
`thresholds` can be configured for cpu, memory, and number of pods in terms of percentage. If a node's
|
`thresholds` can be configured for cpu, memory, number of pods, and extended resources in terms of percentage (the percentage is
|
||||||
usage is below threshold for all (cpu, memory, and number of pods), the node is considered underutilized.
|
calculated as the current resources requested on the node vs [total allocatable](https://kubernetes.io/docs/concepts/architecture/nodes/#capacity).
|
||||||
Currently, pods' request resource requirements are considered for computing node resource utilization.
|
For pods, this means the number of pods on the node as a fraction of the pod capacity set for that node).
|
||||||
|
|
||||||
|
If a node's usage is below threshold for all (cpu, memory, number of pods and extended resources), the node is considered underutilized.
|
||||||
|
Currently, pods request resource requirements are considered for computing node resource utilization.
|
||||||
|
|
||||||
There is another configurable threshold, `targetThresholds`, that is used to compute those potential nodes
|
There is another configurable threshold, `targetThresholds`, that is used to compute those potential nodes
|
||||||
from where pods could be evicted. Any node, between the thresholds, `thresholds` and `targetThresholds` is
|
from where pods could be evicted. If a node's usage is above targetThreshold for any (cpu, memory, number of pods, or extended resources),
|
||||||
|
the node is considered over utilized. Any node between the thresholds, `thresholds` and `targetThresholds` is
|
||||||
considered appropriately utilized and is not considered for eviction. The threshold, `targetThresholds`,
|
considered appropriately utilized and is not considered for eviction. The threshold, `targetThresholds`,
|
||||||
can be configured for cpu, memory, and number of pods too in terms of percentage.
|
can be configured for cpu, memory, and number of pods too in terms of percentage.
|
||||||
|
|
||||||
These thresholds, `thresholds` and `targetThresholds`, could be tuned as per your cluster requirements.
|
These thresholds, `thresholds` and `targetThresholds`, could be tuned as per your cluster requirements. Note that this
|
||||||
An example of the policy for this strategy would look like:
|
strategy evicts pods from `overutilized nodes` (those with usage above `targetThresholds`) to `underutilized nodes`
|
||||||
|
(those with usage below `thresholds`), it will abort if any number of `underutilized nodes` or `overutilized nodes` is zero.
|
||||||
|
|
||||||
```
|
Additionally, the strategy accepts a `useDeviationThresholds` parameter.
|
||||||
|
If that parameter is set to `true`, the thresholds are considered as percentage deviations from mean resource usage.
|
||||||
|
`thresholds` will be deducted from the mean among all nodes and `targetThresholds` will be added to the mean.
|
||||||
|
A resource consumption above (resp. below) this window is considered as overutilization (resp. underutilization).
|
||||||
|
|
||||||
|
**NOTE:** Node resource consumption is determined by the requests and limits of pods, not actual usage.
|
||||||
|
This approach is chosen in order to maintain consistency with the kube-scheduler, which follows the same
|
||||||
|
design for scheduling pods onto nodes. This means that resource usage as reported by Kubelet (or commands
|
||||||
|
like `kubectl top`) may differ from the calculated consumption, due to these components reporting
|
||||||
|
actual usage metrics. Implementing metrics-based descheduling is currently TODO for the project.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`thresholds`|map(string:int)|
|
||||||
|
|`targetThresholds`|map(string:int)|
|
||||||
|
|`numberOfNodes`|int|
|
||||||
|
|`useDeviationThresholds`|bool|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
apiVersion: "descheduler/v1alpha1"
|
apiVersion: "descheduler/v1alpha1"
|
||||||
kind: "DeschedulerPolicy"
|
kind: "DeschedulerPolicy"
|
||||||
strategies:
|
strategies:
|
||||||
@@ -144,28 +264,145 @@ strategies:
|
|||||||
"pods": 50
|
"pods": 50
|
||||||
```
|
```
|
||||||
|
|
||||||
There is another parameter associated with `LowNodeUtilization` strategy, called `numberOfNodes`.
|
Policy should pass the following validation checks:
|
||||||
This parameter can be configured to activate the strategy only when number of under utilized nodes
|
* Three basic native types of resources are supported: `cpu`, `memory` and `pods`.
|
||||||
|
If any of these resource types is not specified, all its thresholds default to 100% to avoid nodes going from underutilized to overutilized.
|
||||||
|
* Extended resources are supported. For example, resource type `nvidia.com/gpu` is specified for GPU node utilization. Extended resources are optional,
|
||||||
|
and will not be used to compute node's usage if it's not specified in `thresholds` and `targetThresholds` explicitly.
|
||||||
|
* `thresholds` or `targetThresholds` can not be nil and they must configure exactly the same types of resources.
|
||||||
|
* The valid range of the resource's percentage value is \[0, 100\]
|
||||||
|
* Percentage value of `thresholds` can not be greater than `targetThresholds` for the same resource.
|
||||||
|
|
||||||
|
There is another parameter associated with the `LowNodeUtilization` strategy, called `numberOfNodes`.
|
||||||
|
This parameter can be configured to activate the strategy only when the number of under utilized nodes
|
||||||
are above the configured value. This could be helpful in large clusters where a few nodes could go
|
are above the configured value. This could be helpful in large clusters where a few nodes could go
|
||||||
under utilized frequently or for a short period of time. By default, `numberOfNodes` is set to zero.
|
under utilized frequently or for a short period of time. By default, `numberOfNodes` is set to zero.
|
||||||
|
|
||||||
|
### HighNodeUtilization
|
||||||
|
|
||||||
|
This strategy finds nodes that are under utilized and evicts pods from the nodes in the hope that these pods will be
|
||||||
|
scheduled compactly into fewer nodes. Used in conjunction with node auto-scaling, this strategy is intended to help
|
||||||
|
trigger down scaling of under utilized nodes.
|
||||||
|
This strategy **must** be used with the scheduler scoring strategy `MostAllocated`. The parameters of this strategy are
|
||||||
|
configured under `nodeResourceUtilizationThresholds`.
|
||||||
|
|
||||||
|
The under utilization of nodes is determined by a configurable threshold `thresholds`. The threshold
|
||||||
|
`thresholds` can be configured for cpu, memory, number of pods, and extended resources in terms of percentage. The percentage is
|
||||||
|
calculated as the current resources requested on the node vs [total allocatable](https://kubernetes.io/docs/concepts/architecture/nodes/#capacity).
|
||||||
|
For pods, this means the number of pods on the node as a fraction of the pod capacity set for that node.
|
||||||
|
|
||||||
|
If a node's usage is below threshold for all (cpu, memory, number of pods and extended resources), the node is considered underutilized.
|
||||||
|
Currently, pods request resource requirements are considered for computing node resource utilization.
|
||||||
|
Any node above `thresholds` is considered appropriately utilized and is not considered for eviction.
|
||||||
|
|
||||||
|
The `thresholds` param could be tuned as per your cluster requirements. Note that this
|
||||||
|
strategy evicts pods from `underutilized nodes` (those with usage below `thresholds`)
|
||||||
|
so that they can be recreated in appropriately utilized nodes.
|
||||||
|
The strategy will abort if any number of `underutilized nodes` or `appropriately utilized nodes` is zero.
|
||||||
|
|
||||||
|
**NOTE:** Node resource consumption is determined by the requests and limits of pods, not actual usage.
|
||||||
|
This approach is chosen in order to maintain consistency with the kube-scheduler, which follows the same
|
||||||
|
design for scheduling pods onto nodes. This means that resource usage as reported by Kubelet (or commands
|
||||||
|
like `kubectl top`) may differ from the calculated consumption, due to these components reporting
|
||||||
|
actual usage metrics. Implementing metrics-based descheduling is currently TODO for the project.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`thresholds`|map(string:int)|
|
||||||
|
|`numberOfNodes`|int|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"HighNodeUtilization":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
nodeResourceUtilizationThresholds:
|
||||||
|
thresholds:
|
||||||
|
"cpu" : 20
|
||||||
|
"memory": 20
|
||||||
|
"pods": 20
|
||||||
|
```
|
||||||
|
|
||||||
|
Policy should pass the following validation checks:
|
||||||
|
* Three basic native types of resources are supported: `cpu`, `memory` and `pods`. If any of these resource types is not specified, all its thresholds default to 100%.
|
||||||
|
* Extended resources are supported. For example, resource type `nvidia.com/gpu` is specified for GPU node utilization. Extended resources are optional, and will not be used to compute node's usage if it's not specified in `thresholds` explicitly.
|
||||||
|
* `thresholds` can not be nil.
|
||||||
|
* The valid range of the resource's percentage value is \[0, 100\]
|
||||||
|
|
||||||
|
There is another parameter associated with the `HighNodeUtilization` strategy, called `numberOfNodes`.
|
||||||
|
This parameter can be configured to activate the strategy only when the number of under utilized nodes
|
||||||
|
is above the configured value. This could be helpful in large clusters where a few nodes could go
|
||||||
|
under utilized frequently or for a short period of time. By default, `numberOfNodes` is set to zero.
|
||||||
|
|
||||||
### RemovePodsViolatingInterPodAntiAffinity
|
### RemovePodsViolatingInterPodAntiAffinity
|
||||||
|
|
||||||
This strategy makes sure that pods violating interpod anti-affinity are removed from nodes. For example, if there is podA on node and podB and podC(running on same node) have antiaffinity rules which prohibit them to run on the same node, then podA will be evicted from the node so that podB and podC could run. This issue could happen, when the anti-affinity rules for pods B,C are created when they are already running on node. Currently, there are no parameters associated with this strategy. To disable this strategy, the policy should look like:
|
This strategy makes sure that pods violating interpod anti-affinity are removed from nodes. For example,
|
||||||
|
if there is podA on a node and podB and podC (running on the same node) have anti-affinity rules which prohibit
|
||||||
|
them to run on the same node, then podA will be evicted from the node so that podB and podC could run. This
|
||||||
|
issue could happen, when the anti-affinity rules for podB and podC are created when they are already running on
|
||||||
|
node.
|
||||||
|
|
||||||
```
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
||||||
|
|`labelSelector`|(see [label filtering](#label-filtering))|
|
||||||
|
|`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
apiVersion: "descheduler/v1alpha1"
|
apiVersion: "descheduler/v1alpha1"
|
||||||
kind: "DeschedulerPolicy"
|
kind: "DeschedulerPolicy"
|
||||||
strategies:
|
strategies:
|
||||||
"RemovePodsViolatingInterPodAntiAffinity":
|
"RemovePodsViolatingInterPodAntiAffinity":
|
||||||
enabled: false
|
enabled: true
|
||||||
```
|
```
|
||||||
|
|
||||||
### RemovePodsViolatingNodeAffinity
|
### RemovePodsViolatingNodeAffinity
|
||||||
|
|
||||||
This strategy makes sure that pods violating node affinity are removed from nodes. For example, there is podA that was scheduled on nodeA which satisfied the node affinity rule `requiredDuringSchedulingIgnoredDuringExecution` at the time of scheduling, but over time nodeA no longer satisfies the rule, then if another node nodeB is available that satisfies the node affinity rule, then podA will be evicted from nodeA. The policy file should like this -
|
This strategy makes sure all pods violating
|
||||||
|
[node affinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#node-affinity)
|
||||||
|
are eventually removed from nodes. Node affinity rules allow a pod to specify
|
||||||
|
`requiredDuringSchedulingIgnoredDuringExecution` type, which tells the scheduler
|
||||||
|
to respect node affinity when scheduling the pod but kubelet to ignore
|
||||||
|
in case node changes over time and no longer respects the affinity.
|
||||||
|
When enabled, the strategy serves as a temporary implementation
|
||||||
|
of `requiredDuringSchedulingRequiredDuringExecution` and evicts pod for kubelet
|
||||||
|
that no longer respects node affinity.
|
||||||
|
|
||||||
```
|
For example, there is podA scheduled on nodeA which satisfies the node
|
||||||
|
affinity rule `requiredDuringSchedulingIgnoredDuringExecution` at the time
|
||||||
|
of scheduling. Over time nodeA stops to satisfy the rule. When the strategy gets
|
||||||
|
executed and there is another node available that satisfies the node affinity rule,
|
||||||
|
podA gets evicted from nodeA.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`nodeAffinityType`|list(string)|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
||||||
|
|`labelSelector`|(see [label filtering](#label-filtering))|
|
||||||
|
|`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
apiVersion: "descheduler/v1alpha1"
|
apiVersion: "descheduler/v1alpha1"
|
||||||
kind: "DeschedulerPolicy"
|
kind: "DeschedulerPolicy"
|
||||||
strategies:
|
strategies:
|
||||||
@@ -175,63 +412,482 @@ strategies:
|
|||||||
nodeAffinityType:
|
nodeAffinityType:
|
||||||
- "requiredDuringSchedulingIgnoredDuringExecution"
|
- "requiredDuringSchedulingIgnoredDuringExecution"
|
||||||
```
|
```
|
||||||
|
|
||||||
### RemovePodsViolatingNodeTaints
|
### RemovePodsViolatingNodeTaints
|
||||||
|
|
||||||
This strategy makes sure that pods violating NoSchedule taints on nodes are removed. For example: there is a pod "podA" with toleration to tolerate a taint ``key=value:NoSchedule`` scheduled and running on the tainted node. If the node's taint is subsequently updated/removed, taint is no longer satisfied by its pods' tolerations and will be evicted. The policy file should look like:
|
This strategy makes sure that pods violating NoSchedule taints on nodes are removed. For example there is a
|
||||||
|
pod "podA" with a toleration to tolerate a taint ``key=value:NoSchedule`` scheduled and running on the tainted
|
||||||
|
node. If the node's taint is subsequently updated/removed, taint is no longer satisfied by its pods' tolerations
|
||||||
|
and will be evicted.
|
||||||
|
|
||||||
````
|
Node taints can be excluded from consideration by specifying a list of excludedTaints. If a node taint key **or**
|
||||||
|
key=value matches an excludedTaints entry, the taint will be ignored.
|
||||||
|
|
||||||
|
For example, excludedTaints entry "dedicated" would match all taints with key "dedicated", regardless of value.
|
||||||
|
excludedTaints entry "dedicated=special-user" would match taints with key "dedicated" and value "special-user".
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`excludedTaints`|list(string)|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
||||||
|
|`labelSelector`|(see [label filtering](#label-filtering))|
|
||||||
|
|`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
````yaml
|
||||||
apiVersion: "descheduler/v1alpha1"
|
apiVersion: "descheduler/v1alpha1"
|
||||||
kind: "DeschedulerPolicy"
|
kind: "DeschedulerPolicy"
|
||||||
strategies:
|
strategies:
|
||||||
"RemovePodsViolatingNodeTaints":
|
"RemovePodsViolatingNodeTaints":
|
||||||
enabled: true
|
enabled: true
|
||||||
|
params:
|
||||||
|
excludedTaints:
|
||||||
|
- dedicated=special-user # exclude taints with key "dedicated" and value "special-user"
|
||||||
|
- reserved # exclude all taints with key "reserved"
|
||||||
````
|
````
|
||||||
|
|
||||||
|
### RemovePodsViolatingTopologySpreadConstraint
|
||||||
|
|
||||||
|
This strategy makes sure that pods violating [topology spread constraints](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/)
|
||||||
|
are evicted from nodes. Specifically, it tries to evict the minimum number of pods required to balance topology domains to within each constraint's `maxSkew`.
|
||||||
|
This strategy requires k8s version 1.18 at a minimum.
|
||||||
|
|
||||||
|
By default, this strategy only deals with hard constraints, setting parameter `includeSoftConstraints` to `true` will
|
||||||
|
include soft constraints.
|
||||||
|
|
||||||
|
Strategy parameter `labelSelector` is not utilized when balancing topology domains and is only applied during eviction to determine if the pod can be evicted.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`includeSoftConstraints`|bool|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
||||||
|
|`labelSelector`|(see [label filtering](#label-filtering))|
|
||||||
|
|`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"RemovePodsViolatingTopologySpreadConstraint":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
includeSoftConstraints: false
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### RemovePodsHavingTooManyRestarts
|
||||||
|
|
||||||
|
This strategy makes sure that pods having too many restarts are removed from nodes. For example a pod with EBS/PD that
|
||||||
|
can't get the volume/disk attached to the instance, then the pod should be re-scheduled to other nodes. Its parameters
|
||||||
|
include `podRestartThreshold`, which is the number of restarts (summed over all eligible containers) at which a pod
|
||||||
|
should be evicted, and `includingInitContainers`, which determines whether init container restarts should be factored
|
||||||
|
into that calculation.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`podRestartThreshold`|int|
|
||||||
|
|`includingInitContainers`|bool|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
||||||
|
|`labelSelector`|(see [label filtering](#label-filtering))|
|
||||||
|
|`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"RemovePodsHavingTooManyRestarts":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podsHavingTooManyRestarts:
|
||||||
|
podRestartThreshold: 100
|
||||||
|
includingInitContainers: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### PodLifeTime
|
||||||
|
|
||||||
|
This strategy evicts pods that are older than `maxPodLifeTimeSeconds`.
|
||||||
|
|
||||||
|
You can also specify `podStatusPhases` to `only` evict pods with specific `StatusPhases`, currently this parameter is limited
|
||||||
|
to `Running` and `Pending`.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`maxPodLifeTimeSeconds`|int|
|
||||||
|
|`podStatusPhases`|list(string)|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
||||||
|
|`labelSelector`|(see [label filtering](#label-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"PodLifeTime":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podLifeTime:
|
||||||
|
maxPodLifeTimeSeconds: 86400
|
||||||
|
podStatusPhases:
|
||||||
|
- "Pending"
|
||||||
|
```
|
||||||
|
|
||||||
|
### RemoveFailedPods
|
||||||
|
|
||||||
|
This strategy evicts pods that are in failed status phase.
|
||||||
|
You can provide an optional parameter to filter by failed `reasons`.
|
||||||
|
`reasons` can be expanded to include reasons of InitContainers as well by setting the optional parameter `includingInitContainers` to `true`.
|
||||||
|
You can specify an optional parameter `minPodLifetimeSeconds` to evict pods that are older than specified seconds.
|
||||||
|
Lastly, you can specify the optional parameter `excludeOwnerKinds` and if a pod
|
||||||
|
has any of these `Kind`s listed as an `OwnerRef`, that pod will not be considered for eviction.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
|
||||||
|
|Name|Type|
|
||||||
|
|---|---|
|
||||||
|
|`minPodLifetimeSeconds`|uint|
|
||||||
|
|`excludeOwnerKinds`|list(string)|
|
||||||
|
|`reasons`|list(string)|
|
||||||
|
|`includingInitContainers`|bool|
|
||||||
|
|`thresholdPriority`|int (see [priority filtering](#priority-filtering))|
|
||||||
|
|`thresholdPriorityClassName`|string (see [priority filtering](#priority-filtering))|
|
||||||
|
|`namespaces`|(see [namespace filtering](#namespace-filtering))|
|
||||||
|
|`labelSelector`|(see [label filtering](#label-filtering))|
|
||||||
|
|`nodeFit`|bool (see [node fit filtering](#node-fit-filtering))|
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"RemoveFailedPods":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
failedPods:
|
||||||
|
reasons:
|
||||||
|
- "NodeAffinity"
|
||||||
|
includingInitContainers: true
|
||||||
|
excludeOwnerKinds:
|
||||||
|
- "Job"
|
||||||
|
minPodLifetimeSeconds: 3600
|
||||||
|
```
|
||||||
|
|
||||||
|
## Filter Pods
|
||||||
|
|
||||||
|
### Namespace filtering
|
||||||
|
|
||||||
|
The following strategies accept a `namespaces` parameter which allows to specify a list of including, resp. excluding namespaces:
|
||||||
|
* `PodLifeTime`
|
||||||
|
* `RemovePodsHavingTooManyRestarts`
|
||||||
|
* `RemovePodsViolatingNodeTaints`
|
||||||
|
* `RemovePodsViolatingNodeAffinity`
|
||||||
|
* `RemovePodsViolatingInterPodAntiAffinity`
|
||||||
|
* `RemoveDuplicates`
|
||||||
|
* `RemovePodsViolatingTopologySpreadConstraint`
|
||||||
|
* `RemoveFailedPods`
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"PodLifeTime":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podLifeTime:
|
||||||
|
maxPodLifeTimeSeconds: 86400
|
||||||
|
namespaces:
|
||||||
|
include:
|
||||||
|
- "namespace1"
|
||||||
|
- "namespace2"
|
||||||
|
```
|
||||||
|
|
||||||
|
In the examples `PodLifeTime` gets executed only over `namespace1` and `namespace2`.
|
||||||
|
The similar holds for `exclude` field:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"PodLifeTime":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podLifeTime:
|
||||||
|
maxPodLifeTimeSeconds: 86400
|
||||||
|
namespaces:
|
||||||
|
exclude:
|
||||||
|
- "namespace1"
|
||||||
|
- "namespace2"
|
||||||
|
```
|
||||||
|
|
||||||
|
The strategy gets executed over all namespaces but `namespace1` and `namespace2`.
|
||||||
|
|
||||||
|
It's not allowed to compute `include` with `exclude` field.
|
||||||
|
|
||||||
|
### Priority filtering
|
||||||
|
|
||||||
|
All strategies are able to configure a priority threshold, only pods under the threshold can be evicted. You can
|
||||||
|
specify this threshold by setting `thresholdPriorityClassName`(setting the threshold to the value of the given
|
||||||
|
priority class) or `thresholdPriority`(directly setting the threshold) parameters. By default, this threshold
|
||||||
|
is set to the value of `system-cluster-critical` priority class.
|
||||||
|
|
||||||
|
Note: Setting `evictSystemCriticalPods` to true disables priority filtering entirely.
|
||||||
|
|
||||||
|
E.g.
|
||||||
|
|
||||||
|
Setting `thresholdPriority`
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"PodLifeTime":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podLifeTime:
|
||||||
|
maxPodLifeTimeSeconds: 86400
|
||||||
|
thresholdPriority: 10000
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting `thresholdPriorityClassName`
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"PodLifeTime":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podLifeTime:
|
||||||
|
maxPodLifeTimeSeconds: 86400
|
||||||
|
thresholdPriorityClassName: "priorityclass1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that you can't configure both `thresholdPriority` and `thresholdPriorityClassName`, if the given priority class
|
||||||
|
does not exist, descheduler won't create it and will throw an error.
|
||||||
|
|
||||||
|
### Label filtering
|
||||||
|
|
||||||
|
The following strategies can configure a [standard kubernetes labelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#labelselector-v1-meta)
|
||||||
|
to filter pods by their labels:
|
||||||
|
|
||||||
|
* `PodLifeTime`
|
||||||
|
* `RemovePodsHavingTooManyRestarts`
|
||||||
|
* `RemovePodsViolatingNodeTaints`
|
||||||
|
* `RemovePodsViolatingNodeAffinity`
|
||||||
|
* `RemovePodsViolatingInterPodAntiAffinity`
|
||||||
|
* `RemovePodsViolatingTopologySpreadConstraint`
|
||||||
|
* `RemoveFailedPods`
|
||||||
|
|
||||||
|
This allows running strategies among pods the descheduler is interested in.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"PodLifeTime":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podLifeTime:
|
||||||
|
maxPodLifeTimeSeconds: 86400
|
||||||
|
labelSelector:
|
||||||
|
matchLabels:
|
||||||
|
component: redis
|
||||||
|
matchExpressions:
|
||||||
|
- {key: tier, operator: In, values: [cache]}
|
||||||
|
- {key: environment, operator: NotIn, values: [dev]}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Node Fit filtering
|
||||||
|
|
||||||
|
The following strategies accept a `nodeFit` boolean parameter which can optimize descheduling:
|
||||||
|
* `RemoveDuplicates`
|
||||||
|
* `LowNodeUtilization`
|
||||||
|
* `HighNodeUtilization`
|
||||||
|
* `RemovePodsViolatingInterPodAntiAffinity`
|
||||||
|
* `RemovePodsViolatingNodeAffinity`
|
||||||
|
* `RemovePodsViolatingNodeTaints`
|
||||||
|
* `RemovePodsViolatingTopologySpreadConstraint`
|
||||||
|
* `RemovePodsHavingTooManyRestarts`
|
||||||
|
* `RemoveFailedPods`
|
||||||
|
|
||||||
|
If set to `true` the descheduler will consider whether or not the pods that meet eviction criteria will fit on other nodes before evicting them. If a pod cannot be rescheduled to another node, it will not be evicted. Currently the following criteria are considered when setting `nodeFit` to `true`:
|
||||||
|
- A `nodeSelector` on the pod
|
||||||
|
- Any `tolerations` on the pod and any `taints` on the other nodes
|
||||||
|
- `nodeAffinity` on the pod
|
||||||
|
- Resource `requests` made by the pod and the resources available on other nodes
|
||||||
|
- Whether any of the other nodes are marked as `unschedulable`
|
||||||
|
|
||||||
|
E.g.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"LowNodeUtilization":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
nodeResourceUtilizationThresholds:
|
||||||
|
thresholds:
|
||||||
|
"cpu": 20
|
||||||
|
"memory": 20
|
||||||
|
"pods": 20
|
||||||
|
targetThresholds:
|
||||||
|
"cpu": 50
|
||||||
|
"memory": 50
|
||||||
|
"pods": 50
|
||||||
|
nodeFit: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that node fit filtering references the current pod spec, and not that of it's owner.
|
||||||
|
Thus, if the pod is owned by a ReplicationController (and that ReplicationController was modified recently),
|
||||||
|
the pod may be running with an outdated spec, which the descheduler will reference when determining node fit.
|
||||||
|
This is expected behavior as the descheduler is a "best-effort" mechanism.
|
||||||
|
|
||||||
|
Using Deployments instead of ReplicationControllers provides an automated rollout of pod spec changes, therefore ensuring that the descheduler has an up-to-date view of the cluster state.
|
||||||
|
|
||||||
## Pod Evictions
|
## Pod Evictions
|
||||||
|
|
||||||
When the descheduler decides to evict pods from a node, it employs following general mechanism:
|
When the descheduler decides to evict pods from a node, it employs the following general mechanism:
|
||||||
|
|
||||||
* [Critical pods](https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/) (with priorityClassName set to system-cluster-critical or system-node-critical) are never evicted.
|
* [Critical pods](https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/) (with priorityClassName set to system-cluster-critical or system-node-critical) are never evicted (unless `evictSystemCriticalPods: true` is set).
|
||||||
* Pods (static or mirrored pods or stand alone pods) not part of an RC, RS, Deployment or Jobs are
|
* Pods (static or mirrored pods or standalone pods) not part of an ReplicationController, ReplicaSet(Deployment), StatefulSet, or Job are
|
||||||
never evicted because these pods won't be recreated.
|
never evicted because these pods won't be recreated. (Standalone pods in failed status phase can be evicted by setting `evictFailedBarePods: true`)
|
||||||
* Pods associated with DaemonSets are never evicted.
|
* Pods associated with DaemonSets are never evicted.
|
||||||
* Pods with local storage are never evicted.
|
* Pods with local storage are never evicted (unless `evictLocalStoragePods: true` is set).
|
||||||
* Best efforts pods are evicted before Burstable and Guaranteed pods.
|
* Pods with PVCs are evicted (unless `ignorePvcPods: true` is set).
|
||||||
* All types of pods with annotation descheduler.alpha.kubernetes.io/evict are evicted. This
|
* In `LowNodeUtilization` and `RemovePodsViolatingInterPodAntiAffinity`, pods are evicted by their priority from low to high, and if they have same priority,
|
||||||
annotation is used to override checks which prevent eviction and user can select which pod is evicted.
|
best effort pods are evicted before burstable and guaranteed pods.
|
||||||
User should know how and if the pod will be recreated.
|
* All types of pods with the annotation `descheduler.alpha.kubernetes.io/evict` are eligible for eviction. This
|
||||||
|
annotation is used to override checks which prevent eviction and users can select which pod is evicted.
|
||||||
|
Users should know how and if the pod will be recreated.
|
||||||
|
* Pods with a non-nil DeletionTimestamp are not evicted by default.
|
||||||
|
|
||||||
### Pod disruption Budget (PDB)
|
Setting `--v=4` or greater on the Descheduler will log all reasons why any pod is not evictable.
|
||||||
Pods subject to Pod Disruption Budget (PDB) are not evicted if descheduling violates its pod
|
|
||||||
disruption budget (PDB). The pods are evicted by using eviction subresource to handle PDB.
|
### Pod Disruption Budget (PDB)
|
||||||
|
|
||||||
|
Pods subject to a Pod Disruption Budget(PDB) are not evicted if descheduling violates its PDB. The pods
|
||||||
|
are evicted by using the eviction subresource to handle PDB.
|
||||||
|
|
||||||
|
## High Availability
|
||||||
|
|
||||||
|
In High Availability mode, Descheduler starts [leader election](https://github.com/kubernetes/client-go/tree/master/tools/leaderelection) process in Kubernetes. You can activate HA mode
|
||||||
|
if you choose to deploy your application as Deployment.
|
||||||
|
|
||||||
|
Deployment starts with 1 replica by default. If you want to use more than 1 replica, you must consider
|
||||||
|
enable High Availability mode since we don't want to run descheduler pods simultaneously.
|
||||||
|
|
||||||
|
### Configure HA Mode
|
||||||
|
|
||||||
|
The leader election process can be enabled by setting `--leader-elect` in the CLI. You can also set
|
||||||
|
`--set=leaderElection.enabled=true` flag if you are using Helm.
|
||||||
|
|
||||||
|
To get best results from HA mode some additional configurations might require:
|
||||||
|
* Configure a [podAntiAffinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node) rule if you want to schedule onto a node only if that node is in the same zone as at least one already-running descheduler
|
||||||
|
* Set the replica count greater than 1
|
||||||
|
|
||||||
|
## Metrics
|
||||||
|
|
||||||
|
| name | type | description |
|
||||||
|
|-------|-------|----------------|
|
||||||
|
| build_info | gauge | constant 1 |
|
||||||
|
| pods_evicted | CounterVec | total number of pods evicted |
|
||||||
|
|
||||||
|
The metrics are served through https://localhost:10258/metrics by default.
|
||||||
|
The address and port can be changed by setting `--binding-address` and `--secure-port` flags.
|
||||||
|
|
||||||
|
## Compatibility Matrix
|
||||||
|
The below compatibility matrix shows the k8s client package(client-go, apimachinery, etc) versions that descheduler
|
||||||
|
is compiled with. At this time descheduler does not have a hard dependency to a specific k8s release. However a
|
||||||
|
particular descheduler release is only tested against the three latest k8s minor versions. For example descheduler
|
||||||
|
v0.18 should work with k8s v1.18, v1.17, and v1.16.
|
||||||
|
|
||||||
|
Starting with descheduler release v0.18 the minor version of descheduler matches the minor version of the k8s client
|
||||||
|
packages that it is compiled with.
|
||||||
|
|
||||||
|
| Descheduler | Supported Kubernetes Version |
|
||||||
|
|-------------|------------------------------|
|
||||||
|
| v0.24 | v1.24 |
|
||||||
|
| v0.23 | v1.23 |
|
||||||
|
| v0.22 | v1.22 |
|
||||||
|
| v0.21 | v1.21 |
|
||||||
|
| v0.20 | v1.20 |
|
||||||
|
| v0.19 | v1.19 |
|
||||||
|
| v0.18 | v1.18 |
|
||||||
|
| v0.10 | v1.17 |
|
||||||
|
| v0.4-v0.9 | v1.9+ |
|
||||||
|
| v0.1-v0.3 | v1.7-v1.8 |
|
||||||
|
|
||||||
|
## Getting Involved and Contributing
|
||||||
|
|
||||||
|
Are you interested in contributing to descheduler? We, the
|
||||||
|
maintainers and community, would love your suggestions, contributions, and help!
|
||||||
|
Also, the maintainers can be contacted at any time to learn more about how to get
|
||||||
|
involved.
|
||||||
|
|
||||||
|
To get started writing code see the [contributor guide](docs/contributor-guide.md) in the `/docs` directory.
|
||||||
|
|
||||||
|
In the interest of getting more new people involved we tag issues with
|
||||||
|
[`good first issue`][good_first_issue].
|
||||||
|
These are typically issues that have smaller scope but are good ways to start
|
||||||
|
to get acquainted with the codebase.
|
||||||
|
|
||||||
|
We also encourage ALL active community participants to act as if they are
|
||||||
|
maintainers, even if you don't have "official" write permissions. This is a
|
||||||
|
community effort, we are here to serve the Kubernetes community. If you have an
|
||||||
|
active interest and you want to get involved, you have real power! Don't assume
|
||||||
|
that the only people who can get things done around here are the "maintainers".
|
||||||
|
|
||||||
|
We also would love to add more "official" maintainers, so show us what you can
|
||||||
|
do!
|
||||||
|
|
||||||
|
This repository uses the Kubernetes bots. See a full list of the commands [here][prow].
|
||||||
|
|
||||||
|
### Communicating With Contributors
|
||||||
|
|
||||||
|
You can reach the contributors of this project at:
|
||||||
|
|
||||||
|
- [Slack channel](https://kubernetes.slack.com/messages/sig-scheduling)
|
||||||
|
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-scheduling)
|
||||||
|
|
||||||
|
Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
This roadmap is not in any particular order.
|
This roadmap is not in any particular order.
|
||||||
|
|
||||||
* Consideration of pod affinity
|
* Consideration of pod affinity
|
||||||
* Strategy to consider pod life time
|
|
||||||
* Strategy to consider number of pending pods
|
* Strategy to consider number of pending pods
|
||||||
* Integration with cluster autoscaler
|
* Integration with cluster autoscaler
|
||||||
* Integration with metrics providers for obtaining real load metrics
|
* Integration with metrics providers for obtaining real load metrics
|
||||||
* Consideration of Kubernetes's scheduler's predicates
|
* Consideration of Kubernetes's scheduler's predicates
|
||||||
|
|
||||||
|
|
||||||
## Compatibility matrix
|
|
||||||
|
|
||||||
Descheduler | supported Kubernetes version
|
|
||||||
-------------|-----------------------------
|
|
||||||
0.4+ | 1.9+
|
|
||||||
0.1-0.3 | 1.7-1.8
|
|
||||||
|
|
||||||
## Community, discussion, contribution, and support
|
|
||||||
|
|
||||||
Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
|
|
||||||
|
|
||||||
You can reach the maintainers of this project at:
|
|
||||||
|
|
||||||
- [Slack channel](https://kubernetes.slack.com/messages/sig-scheduling)
|
|
||||||
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-scheduling)
|
|
||||||
|
|
||||||
### Code of conduct
|
### Code of conduct
|
||||||
|
|
||||||
Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).
|
Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).
|
||||||
|
|||||||
22
charts/descheduler/.helmignore
Normal file
22
charts/descheduler/.helmignore
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
||||||
16
charts/descheduler/Chart.yaml
Normal file
16
charts/descheduler/Chart.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
name: descheduler
|
||||||
|
version: 0.24.1
|
||||||
|
appVersion: 0.24.1
|
||||||
|
description: Descheduler for Kubernetes is used to rebalance clusters by evicting pods that can potentially be scheduled on better nodes. In the current implementation, descheduler does not schedule replacement of evicted pods but relies on the default scheduler for that.
|
||||||
|
keywords:
|
||||||
|
- kubernetes
|
||||||
|
- descheduler
|
||||||
|
- kube-scheduler
|
||||||
|
home: https://github.com/kubernetes-sigs/descheduler
|
||||||
|
icon: https://kubernetes.io/images/favicon.png
|
||||||
|
sources:
|
||||||
|
- https://github.com/kubernetes-sigs/descheduler
|
||||||
|
maintainers:
|
||||||
|
- name: Kubernetes SIG Scheduling
|
||||||
|
email: kubernetes-sig-scheduling@googlegroups.com
|
||||||
88
charts/descheduler/README.md
Normal file
88
charts/descheduler/README.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Descheduler for Kubernetes
|
||||||
|
|
||||||
|
[Descheduler](https://github.com/kubernetes-sigs/descheduler/) for Kubernetes is used to rebalance clusters by evicting pods that can potentially be scheduled on better nodes. In the current implementation, descheduler does not schedule replacement of evicted pods but relies on the default scheduler for that.
|
||||||
|
|
||||||
|
## TL;DR:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
|
||||||
|
helm install my-release --namespace kube-system descheduler/descheduler
|
||||||
|
```
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This chart bootstraps a [descheduler](https://github.com/kubernetes-sigs/descheduler/) cron job on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Kubernetes 1.14+
|
||||||
|
|
||||||
|
## Installing the Chart
|
||||||
|
|
||||||
|
To install the chart with the release name `my-release`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
helm install --namespace kube-system my-release descheduler/descheduler
|
||||||
|
```
|
||||||
|
|
||||||
|
The command deploys _descheduler_ on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
|
||||||
|
|
||||||
|
> **Tip**: List all releases using `helm list`
|
||||||
|
|
||||||
|
## Uninstalling the Chart
|
||||||
|
|
||||||
|
To uninstall/delete the `my-release` deployment:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
helm delete my-release
|
||||||
|
```
|
||||||
|
|
||||||
|
The command removes all the Kubernetes components associated with the chart and deletes the release.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The following table lists the configurable parameters of the _descheduler_ chart and their default values.
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------|
|
||||||
|
| `kind` | Use as CronJob or Deployment | `CronJob` |
|
||||||
|
| `image.repository` | Docker repository to use | `k8s.gcr.io/descheduler/descheduler` |
|
||||||
|
| `image.tag` | Docker tag to use | `v[chart appVersion]` |
|
||||||
|
| `image.pullPolicy` | Docker image pull policy | `IfNotPresent` |
|
||||||
|
| `imagePullSecrets` | Docker repository secrets | `[]` |
|
||||||
|
| `nameOverride` | String to partially override `descheduler.fullname` template (will prepend the release name) | `""` |
|
||||||
|
| `fullnameOverride` | String to fully override `descheduler.fullname` template | `""` |
|
||||||
|
| `cronJobApiVersion` | CronJob API Group Version | `"batch/v1"` |
|
||||||
|
| `schedule` | The cron schedule to run the _descheduler_ job on | `"*/2 * * * *"` |
|
||||||
|
| `startingDeadlineSeconds` | If set, configure `startingDeadlineSeconds` for the _descheduler_ job | `nil` |
|
||||||
|
| `successfulJobsHistoryLimit` | If set, configure `successfulJobsHistoryLimit` for the _descheduler_ job | `nil` |
|
||||||
|
| `failedJobsHistoryLimit` | If set, configure `failedJobsHistoryLimit` for the _descheduler_ job | `nil` |
|
||||||
|
| `deschedulingInterval` | If using kind:Deployment, sets time between consecutive descheduler executions. | `5m` |
|
||||||
|
| `replicas` | The replica count for Deployment | `1` |
|
||||||
|
| `leaderElection` | The options for high availability when running replicated components | _see values.yaml_ |
|
||||||
|
| `cmdOptions` | The options to pass to the _descheduler_ command | _see values.yaml_ |
|
||||||
|
| `deschedulerPolicy.strategies` | The _descheduler_ strategies to apply | _see values.yaml_ |
|
||||||
|
| `priorityClassName` | The name of the priority class to add to pods | `system-cluster-critical` |
|
||||||
|
| `rbac.create` | If `true`, create & use RBAC resources | `true` |
|
||||||
|
| `podSecurityPolicy.create` | If `true`, create PodSecurityPolicy | `true` |
|
||||||
|
| `resources` | Descheduler container CPU and memory requests/limits | _see values.yaml_ |
|
||||||
|
| `serviceAccount.create` | If `true`, create a service account for the cron job | `true` |
|
||||||
|
| `serviceAccount.name` | The name of the service account to use, if not set and create is true a name is generated using the fullname template | `nil` |
|
||||||
|
| `serviceAccount.annotations` | Specifies custom annotations for the serviceAccount | `{}` |
|
||||||
|
| `podAnnotations` | Annotations to add to the descheduler Pods | `{}` |
|
||||||
|
| `podLabels` | Labels to add to the descheduler Pods | `{}` |
|
||||||
|
| `nodeSelector` | Node selectors to run the descheduler cronjob/deployment on specific nodes | `nil` |
|
||||||
|
| `service.enabled` | If `true`, create a service for deployment | `false` |
|
||||||
|
| `serviceMonitor.enabled` | If `true`, create a ServiceMonitor for deployment | `false` |
|
||||||
|
| `serviceMonitor.namespace` | The namespace where Prometheus expects to find service monitors | `nil` |
|
||||||
|
| `serviceMonitor.interval` | The scrape interval. If not set, the Prometheus default scrape interval is used | `nil` |
|
||||||
|
| `serviceMonitor.honorLabels` | Keeps the scraped data's labels when labels are on collisions with target labels. | `true` |
|
||||||
|
| `serviceMonitor.insecureSkipVerify` | Skip TLS certificate validation when scraping | `true` |
|
||||||
|
| `serviceMonitor.serverName` | Name of the server to use when validating TLS certificate | `nil` |
|
||||||
|
| `serviceMonitor.metricRelabelings` | MetricRelabelConfigs to apply to samples after scraping, but before ingestion | `[]` |
|
||||||
|
| `serviceMonitor.relabelings` | RelabelConfigs to apply to samples before scraping | `[]` |
|
||||||
|
| `affinity` | Node affinity to run the descheduler cronjob/deployment on specific nodes | `nil` |
|
||||||
|
| `tolerations` | tolerations to run the descheduler cronjob/deployment on specific nodes | `nil` |
|
||||||
|
| `suspend` | Set spec.suspend in descheduler cronjob | `false` |
|
||||||
|
| `commonLabels` | Labels to apply to all resources | `{}` |
|
||||||
|
| `livenessProbe` | Liveness probe configuration for the descheduler container | _see values.yaml_ |
|
||||||
7
charts/descheduler/templates/NOTES.txt
Normal file
7
charts/descheduler/templates/NOTES.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Descheduler installed as a {{ .Values.kind }}.
|
||||||
|
|
||||||
|
{{- if eq .Values.kind "Deployment" }}
|
||||||
|
{{- if eq .Values.replicas 1.0}}
|
||||||
|
WARNING: You set replica count as 1 and workload kind as Deployment however leaderElection is not enabled. Consider enabling Leader Election for HA mode.
|
||||||
|
{{- end}}
|
||||||
|
{{- end}}
|
||||||
94
charts/descheduler/templates/_helpers.tpl
Normal file
94
charts/descheduler/templates/_helpers.tpl
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
{{/* vim: set filetype=mustache: */}}
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "descheduler.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
If release name contains chart name it will be used as a full name.
|
||||||
|
*/}}
|
||||||
|
{{- define "descheduler.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride -}}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||||
|
{{- if contains $name .Release.Name -}}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "descheduler.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "descheduler.labels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "descheduler.name" . }}
|
||||||
|
helm.sh/chart: {{ include "descheduler.chart" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- if .Values.commonLabels}}
|
||||||
|
{{ toYaml .Values.commonLabels }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "descheduler.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "descheduler.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "descheduler.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create -}}
|
||||||
|
{{ default (include "descheduler.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else -}}
|
||||||
|
{{ default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Leader Election
|
||||||
|
*/}}
|
||||||
|
{{- define "descheduler.leaderElection"}}
|
||||||
|
{{- if .Values.leaderElection -}}
|
||||||
|
- --leader-elect={{ .Values.leaderElection.enabled }}
|
||||||
|
{{- if .Values.leaderElection.leaseDuration }}
|
||||||
|
- --leader-elect-lease-duration={{ .Values.leaderElection.leaseDuration }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.leaderElection.renewDeadline }}
|
||||||
|
- --leader-elect-renew-deadline={{ .Values.leaderElection.renewDeadline }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.leaderElection.retryPeriod }}
|
||||||
|
- --leader-elect-retry-period={{ .Values.leaderElection.retryPeriod }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.leaderElection.resourceLock }}
|
||||||
|
- --leader-elect-resource-lock={{ .Values.leaderElection.resourceLock }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.leaderElection.resourceName }}
|
||||||
|
- --leader-elect-resource-name={{ .Values.leaderElection.resourceName }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.leaderElection.resourceNamescape }}
|
||||||
|
- --leader-elect-resource-namespace={{ .Values.leaderElection.resourceNamescape }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
43
charts/descheduler/templates/clusterrole.yaml
Normal file
43
charts/descheduler/templates/clusterrole.yaml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{{- if .Values.rbac.create -}}
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.labels" . | nindent 4 }}
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["events"]
|
||||||
|
verbs: ["create", "update"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["nodes"]
|
||||||
|
verbs: ["get", "watch", "list"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["namespaces"]
|
||||||
|
verbs: ["get", "watch", "list"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods"]
|
||||||
|
verbs: ["get", "watch", "list", "delete"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods/eviction"]
|
||||||
|
verbs: ["create"]
|
||||||
|
- apiGroups: ["scheduling.k8s.io"]
|
||||||
|
resources: ["priorityclasses"]
|
||||||
|
verbs: ["get", "watch", "list"]
|
||||||
|
{{- if .Values.leaderElection.enabled }}
|
||||||
|
- apiGroups: ["coordination.k8s.io"]
|
||||||
|
resources: ["leases"]
|
||||||
|
verbs: ["create", "update"]
|
||||||
|
- apiGroups: ["coordination.k8s.io"]
|
||||||
|
resources: ["leases"]
|
||||||
|
resourceNames: ["descheduler"]
|
||||||
|
verbs: ["get", "patch", "delete"]
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.podSecurityPolicy.create }}
|
||||||
|
- apiGroups: ['policy']
|
||||||
|
resources: ['podsecuritypolicies']
|
||||||
|
verbs: ['use']
|
||||||
|
resourceNames:
|
||||||
|
- {{ template "descheduler.fullname" . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
16
charts/descheduler/templates/clusterrolebinding.yaml
Normal file
16
charts/descheduler/templates/clusterrolebinding.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{{- if .Values.rbac.create -}}
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.labels" . | nindent 4 }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: {{ template "descheduler.serviceAccountName" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
{{- end -}}
|
||||||
12
charts/descheduler/templates/configmap.yaml
Normal file
12
charts/descheduler/templates/configmap.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.labels" . | nindent 4 }}
|
||||||
|
data:
|
||||||
|
policy.yaml: |
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
{{ toYaml .Values.deschedulerPolicy | trim | indent 4 }}
|
||||||
95
charts/descheduler/templates/cronjob.yaml
Normal file
95
charts/descheduler/templates/cronjob.yaml
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
{{- if eq .Values.kind "CronJob" }}
|
||||||
|
apiVersion: {{ .Values.cronJobApiVersion | default "batch/v1" }}
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
schedule: {{ .Values.schedule | quote }}
|
||||||
|
{{- if .Values.suspend }}
|
||||||
|
suspend: {{ .Values.suspend }}
|
||||||
|
{{- end }}
|
||||||
|
concurrencyPolicy: "Forbid"
|
||||||
|
{{- if .Values.startingDeadlineSeconds }}
|
||||||
|
startingDeadlineSeconds: {{ .Values.startingDeadlineSeconds }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.successfulJobsHistoryLimit }}
|
||||||
|
successfulJobsHistoryLimit: {{ .Values.successfulJobsHistoryLimit }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.failedJobsHistoryLimit }}
|
||||||
|
failedJobsHistoryLimit: {{ .Values.failedJobsHistoryLimit }}
|
||||||
|
{{- end }}
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
annotations:
|
||||||
|
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||||
|
{{- if .Values.podAnnotations }}
|
||||||
|
{{- .Values.podAnnotations | toYaml | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.selectorLabels" . | nindent 12 }}
|
||||||
|
{{- if .Values.podLabels }}
|
||||||
|
{{- .Values.podLabels | toYaml | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.priorityClassName }}
|
||||||
|
priorityClassName: {{ .Values.priorityClassName }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ template "descheduler.serviceAccountName" . }}
|
||||||
|
restartPolicy: "Never"
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "v%s" .Chart.AppVersion) }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
command:
|
||||||
|
- "/bin/descheduler"
|
||||||
|
args:
|
||||||
|
- "--policy-config-file"
|
||||||
|
- "/policy-dir/policy.yaml"
|
||||||
|
{{- range $key, $value := .Values.cmdOptions }}
|
||||||
|
- {{ printf "--%s" $key | quote }}
|
||||||
|
{{- if $value }}
|
||||||
|
- {{ $value | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
livenessProbe:
|
||||||
|
{{- toYaml .Values.livenessProbe | nindent 16 }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 16 }}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
privileged: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /policy-dir
|
||||||
|
name: policy-volume
|
||||||
|
volumes:
|
||||||
|
- name: policy-volume
|
||||||
|
configMap:
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
{{- end }}
|
||||||
94
charts/descheduler/templates/deployment.yaml
Normal file
94
charts/descheduler/templates/deployment.yaml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
{{- if eq .Values.kind "Deployment" }}
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
{{- if gt .Values.replicas 1.0}}
|
||||||
|
{{- if not .Values.leaderElection.enabled }}
|
||||||
|
{{- fail "You must set leaderElection to use more than 1 replica"}}
|
||||||
|
{{- end}}
|
||||||
|
replicas: {{ required "leaderElection required for running more than one replica" .Values.replicas }}
|
||||||
|
{{- else }}
|
||||||
|
replicas: 1
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "descheduler.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.selectorLabels" . | nindent 8 }}
|
||||||
|
{{- if .Values.podLabels }}
|
||||||
|
{{- .Values.podLabels | toYaml | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
annotations:
|
||||||
|
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||||
|
{{- if .Values.podAnnotations }}
|
||||||
|
{{- .Values.podAnnotations | toYaml | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if .Values.priorityClassName }}
|
||||||
|
priorityClassName: {{ .Values.priorityClassName }}
|
||||||
|
{{- end }}
|
||||||
|
serviceAccountName: {{ template "descheduler.serviceAccountName" . }}
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "v%s" .Chart.AppVersion) }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
command:
|
||||||
|
- "/bin/descheduler"
|
||||||
|
args:
|
||||||
|
- "--policy-config-file"
|
||||||
|
- "/policy-dir/policy.yaml"
|
||||||
|
- "--descheduling-interval"
|
||||||
|
- {{ required "deschedulingInterval required for running as Deployment" .Values.deschedulingInterval }}
|
||||||
|
{{- range $key, $value := .Values.cmdOptions }}
|
||||||
|
- {{ printf "--%s" $key | quote }}
|
||||||
|
{{- if $value }}
|
||||||
|
- {{ $value | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- include "descheduler.leaderElection" . | nindent 12 }}
|
||||||
|
ports:
|
||||||
|
- containerPort: 10258
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
{{- toYaml .Values.livenessProbe | nindent 12 }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
privileged: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /policy-dir
|
||||||
|
name: policy-volume
|
||||||
|
volumes:
|
||||||
|
- name: policy-volume
|
||||||
|
configMap:
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
39
charts/descheduler/templates/podsecuritypolicy.yaml
Normal file
39
charts/descheduler/templates/podsecuritypolicy.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{{- if .Values.podSecurityPolicy.create -}}
|
||||||
|
apiVersion: policy/v1beta1
|
||||||
|
kind: PodSecurityPolicy
|
||||||
|
metadata:
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
annotations:
|
||||||
|
seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default,runtime/default'
|
||||||
|
seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default'
|
||||||
|
spec:
|
||||||
|
privileged: false
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
requiredDropCapabilities:
|
||||||
|
- ALL
|
||||||
|
volumes:
|
||||||
|
- 'configMap'
|
||||||
|
- 'secret'
|
||||||
|
hostNetwork: false
|
||||||
|
hostIPC: false
|
||||||
|
hostPID: false
|
||||||
|
runAsUser:
|
||||||
|
rule: 'MustRunAs'
|
||||||
|
ranges:
|
||||||
|
- min: 1
|
||||||
|
max: 65535
|
||||||
|
seLinux:
|
||||||
|
rule: 'RunAsAny'
|
||||||
|
supplementalGroups:
|
||||||
|
rule: 'MustRunAs'
|
||||||
|
ranges:
|
||||||
|
- min: 1
|
||||||
|
max: 65535
|
||||||
|
fsGroup:
|
||||||
|
rule: 'MustRunAs'
|
||||||
|
ranges:
|
||||||
|
- min: 1
|
||||||
|
max: 65535
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
{{- end -}}
|
||||||
21
charts/descheduler/templates/service.yaml
Normal file
21
charts/descheduler/templates/service.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{{- if eq .Values.kind "Deployment" }}
|
||||||
|
{{- if eq .Values.service.enabled true }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.labels" . | nindent 4 }}
|
||||||
|
name: {{ template "descheduler.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
spec:
|
||||||
|
clusterIP: None
|
||||||
|
ports:
|
||||||
|
- name: http-metrics
|
||||||
|
port: 10258
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 10258
|
||||||
|
selector:
|
||||||
|
{{- include "descheduler.selectorLabels" . | nindent 4 }}
|
||||||
|
type: ClusterIP
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
12
charts/descheduler/templates/serviceaccount.yaml
Normal file
12
charts/descheduler/templates/serviceaccount.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{{- if .Values.serviceAccount.create -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ template "descheduler.serviceAccountName" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.labels" . | nindent 4 }}
|
||||||
|
{{- if .Values.serviceAccount.annotations }}
|
||||||
|
annotations: {{ toYaml .Values.serviceAccount.annotations | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end -}}
|
||||||
41
charts/descheduler/templates/servicemonitor.yaml
Normal file
41
charts/descheduler/templates/servicemonitor.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{{- if eq .Values.kind "Deployment" }}
|
||||||
|
{{- if eq .Values.serviceMonitor.enabled true }}
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: {{ template "descheduler.fullname" . }}-servicemonitor
|
||||||
|
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "descheduler.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
jobLabel: jobLabel
|
||||||
|
namespaceSelector:
|
||||||
|
matchNames:
|
||||||
|
- {{ .Release.Namespace }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "descheduler.selectorLabels" . | nindent 6 }}
|
||||||
|
endpoints:
|
||||||
|
- honorLabels: {{ .Values.serviceMonitor.honorLabels | default true }}
|
||||||
|
port: http-metrics
|
||||||
|
{{- if .Values.serviceMonitor.interval }}
|
||||||
|
interval: {{ .Values.serviceMonitor.interval }}
|
||||||
|
{{- end }}
|
||||||
|
scheme: https
|
||||||
|
tlsConfig:
|
||||||
|
{{- if eq .Values.serviceMonitor.insecureSkipVerify true }}
|
||||||
|
insecureSkipVerify: true
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.serviceMonitor.serverName }}
|
||||||
|
serverName: {{ .Values.serviceMonitor.serverName }}
|
||||||
|
{{- end}}
|
||||||
|
{{- if .Values.serviceMonitor.metricRelabelings }}
|
||||||
|
metricRelabelings:
|
||||||
|
{{ tpl (toYaml .Values.serviceMonitor.metricRelabelings | indent 4) . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.serviceMonitor.relabelings }}
|
||||||
|
relabelings:
|
||||||
|
{{ tpl (toYaml .Values.serviceMonitor.relabelings | indent 4) . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
30
charts/descheduler/templates/tests/test-descheduler-pod.yaml
Normal file
30
charts/descheduler/templates/tests/test-descheduler-pod.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: descheduler-test-pod
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
annotations:
|
||||||
|
"helm.sh/hook": test
|
||||||
|
spec:
|
||||||
|
restartPolicy: Never
|
||||||
|
serviceAccountName: descheduler-ci
|
||||||
|
containers:
|
||||||
|
- name: descheduler-test-container
|
||||||
|
image: alpine:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- All
|
||||||
|
privileged: false
|
||||||
|
runAsNonRoot: false
|
||||||
|
command: ["/bin/ash"]
|
||||||
|
args:
|
||||||
|
- -c
|
||||||
|
- >-
|
||||||
|
apk --no-cache add curl &&
|
||||||
|
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl &&
|
||||||
|
chmod +x ./kubectl &&
|
||||||
|
mv ./kubectl /usr/local/bin/kubectl &&
|
||||||
|
/usr/local/bin/kubectl get pods --namespace {{ .Release.Namespace }} --token "$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" | grep "descheduler" | grep "Completed"
|
||||||
173
charts/descheduler/values.yaml
Normal file
173
charts/descheduler/values.yaml
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Default values for descheduler.
|
||||||
|
# This is a YAML-formatted file.
|
||||||
|
# Declare variables to be passed into your templates.
|
||||||
|
|
||||||
|
# CronJob or Deployment
|
||||||
|
kind: CronJob
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: k8s.gcr.io/descheduler/descheduler
|
||||||
|
# Overrides the image tag whose default is the chart version
|
||||||
|
tag: ""
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
imagePullSecrets:
|
||||||
|
# - name: container-registry-secret
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 256Mi
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
# labels that'll be applied to all resources
|
||||||
|
commonLabels: {}
|
||||||
|
|
||||||
|
cronJobApiVersion: "batch/v1" # Use "batch/v1beta1" for k8s version < 1.21.0. TODO(@7i) remove with 1.23 release
|
||||||
|
schedule: "*/2 * * * *"
|
||||||
|
suspend: false
|
||||||
|
#startingDeadlineSeconds: 200
|
||||||
|
#successfulJobsHistoryLimit: 1
|
||||||
|
#failedJobsHistoryLimit: 1
|
||||||
|
|
||||||
|
# Required when running as a Deployment
|
||||||
|
deschedulingInterval: 5m
|
||||||
|
|
||||||
|
# Specifies the replica count for Deployment
|
||||||
|
# Set leaderElection if you want to use more than 1 replica
|
||||||
|
# Set affinity.podAntiAffinity rule if you want to schedule onto a node
|
||||||
|
# only if that node is in the same zone as at least one already-running descheduler
|
||||||
|
replicas: 1
|
||||||
|
|
||||||
|
# Specifies whether Leader Election resources should be created
|
||||||
|
# Required when running as a Deployment
|
||||||
|
leaderElection: {}
|
||||||
|
# enabled: true
|
||||||
|
# leaseDuration: 15s
|
||||||
|
# renewDeadline: 10s
|
||||||
|
# retryPeriod: 2s
|
||||||
|
# resourceLock: "leases"
|
||||||
|
# resourceName: "descheduler"
|
||||||
|
# resourceNamescape: "kube-system"
|
||||||
|
|
||||||
|
cmdOptions:
|
||||||
|
v: 3
|
||||||
|
|
||||||
|
deschedulerPolicy:
|
||||||
|
# nodeSelector: "key1=value1,key2=value2"
|
||||||
|
# maxNoOfPodsToEvictPerNode: 10
|
||||||
|
# maxNoOfPodsToEvictPerNamespace: 10
|
||||||
|
# ignorePvcPods: true
|
||||||
|
# evictLocalStoragePods: true
|
||||||
|
strategies:
|
||||||
|
RemoveDuplicates:
|
||||||
|
enabled: true
|
||||||
|
RemovePodsViolatingNodeTaints:
|
||||||
|
enabled: true
|
||||||
|
RemovePodsViolatingNodeAffinity:
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
nodeAffinityType:
|
||||||
|
- requiredDuringSchedulingIgnoredDuringExecution
|
||||||
|
RemovePodsViolatingInterPodAntiAffinity:
|
||||||
|
enabled: true
|
||||||
|
LowNodeUtilization:
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
nodeResourceUtilizationThresholds:
|
||||||
|
thresholds:
|
||||||
|
cpu: 20
|
||||||
|
memory: 20
|
||||||
|
pods: 20
|
||||||
|
targetThresholds:
|
||||||
|
cpu: 50
|
||||||
|
memory: 50
|
||||||
|
pods: 50
|
||||||
|
|
||||||
|
priorityClassName: system-cluster-critical
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
# foo: bar
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
|
# nodeAffinity:
|
||||||
|
# requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
# nodeSelectorTerms:
|
||||||
|
# - matchExpressions:
|
||||||
|
# - key: kubernetes.io/e2e-az-name
|
||||||
|
# operator: In
|
||||||
|
# values:
|
||||||
|
# - e2e-az1
|
||||||
|
# - e2e-az2
|
||||||
|
# podAntiAffinity:
|
||||||
|
# requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
# - labelSelector:
|
||||||
|
# matchExpressions:
|
||||||
|
# - key: app.kubernetes.io/name
|
||||||
|
# operator: In
|
||||||
|
# values:
|
||||||
|
# - descheduler
|
||||||
|
# topologyKey: "kubernetes.io/hostname"
|
||||||
|
tolerations: []
|
||||||
|
# - key: 'management'
|
||||||
|
# operator: 'Equal'
|
||||||
|
# value: 'tool'
|
||||||
|
# effect: 'NoSchedule'
|
||||||
|
|
||||||
|
rbac:
|
||||||
|
# Specifies whether RBAC resources should be created
|
||||||
|
create: true
|
||||||
|
|
||||||
|
podSecurityPolicy:
|
||||||
|
# Specifies whether PodSecurityPolicy should be created.
|
||||||
|
create: true
|
||||||
|
|
||||||
|
serviceAccount:
|
||||||
|
# Specifies whether a ServiceAccount should be created
|
||||||
|
create: true
|
||||||
|
# The name of the ServiceAccount to use.
|
||||||
|
# If not set and create is true, a name is generated using the fullname template
|
||||||
|
name:
|
||||||
|
# Specifies custom annotations for the serviceAccount
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
podLabels: {}
|
||||||
|
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 10258
|
||||||
|
scheme: HTTPS
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
|
||||||
|
service:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: false
|
||||||
|
# The namespace where Prometheus expects to find service monitors.
|
||||||
|
# namespace: ""
|
||||||
|
interval: ""
|
||||||
|
# honorLabels: true
|
||||||
|
insecureSkipVerify: true
|
||||||
|
serverName: null
|
||||||
|
metricRelabelings: []
|
||||||
|
# - action: keep
|
||||||
|
# regex: 'descheduler_(build_info|pods_evicted)'
|
||||||
|
# sourceLabels: [__name__]
|
||||||
|
relabelings: []
|
||||||
|
# - sourceLabels: [__meta_kubernetes_pod_node_name]
|
||||||
|
# separator: ;
|
||||||
|
# regex: ^(.*)$
|
||||||
|
# targetLabel: nodename
|
||||||
|
# replacement: $1
|
||||||
|
# action: replace
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
# See https://cloud.google.com/cloud-build/docs/build-config
|
# See https://cloud.google.com/cloud-build/docs/build-config
|
||||||
|
|
||||||
# this must be specified in seconds. If omitted, defaults to 600s (10 mins)
|
# this must be specified in seconds. If omitted, defaults to 600s (10 mins)
|
||||||
timeout: 1200s
|
timeout: 1500s
|
||||||
# this prevents errors if you don't use both _GIT_TAG and _PULL_BASE_REF,
|
# this prevents errors if you don't use both _GIT_TAG and _PULL_BASE_REF,
|
||||||
# or any new substitutions added in the future.
|
# or any new substitutions added in the future.
|
||||||
options:
|
options:
|
||||||
substitution_option: ALLOW_LOOSE
|
substitution_option: ALLOW_LOOSE
|
||||||
steps:
|
steps:
|
||||||
- name: 'gcr.io/k8s-testimages/gcb-docker-gcloud:v20190906-745fed4'
|
- name: 'gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90'
|
||||||
entrypoint: make
|
entrypoint: make
|
||||||
env:
|
env:
|
||||||
- DOCKER_CLI_EXPERIMENTAL=enabled
|
- DOCKER_CLI_EXPERIMENTAL=enabled
|
||||||
- VERSION=$_GIT_TAG
|
- VERSION=$_GIT_TAG
|
||||||
- BASE_REF=$_PULL_BASE_REF
|
- BASE_REF=$_PULL_BASE_REF
|
||||||
args:
|
args:
|
||||||
- push
|
- push-all
|
||||||
substitutions:
|
substitutions:
|
||||||
# _GIT_TAG will be filled with a git-based tag for the image, of the form vYYYYMMDD-hash, and
|
# _GIT_TAG will be filled with a git-based tag for the image, of the form vYYYYMMDD-hash, and
|
||||||
# can be used as a substitution
|
# can be used as a substitution
|
||||||
|
|||||||
@@ -18,44 +18,77 @@ limitations under the License.
|
|||||||
package options
|
package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
apiserveroptions "k8s.io/apiserver/pkg/server/options"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
componentbaseconfig "k8s.io/component-base/config"
|
||||||
// install the componentconfig api so we get its defaulting and conversion functions
|
componentbaseoptions "k8s.io/component-base/config/options"
|
||||||
"sigs.k8s.io/descheduler/pkg/apis/componentconfig"
|
"sigs.k8s.io/descheduler/pkg/apis/componentconfig"
|
||||||
"sigs.k8s.io/descheduler/pkg/apis/componentconfig/v1alpha1"
|
"sigs.k8s.io/descheduler/pkg/apis/componentconfig/v1alpha1"
|
||||||
deschedulerscheme "sigs.k8s.io/descheduler/pkg/descheduler/scheme"
|
deschedulerscheme "sigs.k8s.io/descheduler/pkg/descheduler/scheme"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
const (
|
||||||
|
DefaultDeschedulerPort = 10258
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeschedulerServer configuration
|
// DeschedulerServer configuration
|
||||||
type DeschedulerServer struct {
|
type DeschedulerServer struct {
|
||||||
componentconfig.DeschedulerConfiguration
|
componentconfig.DeschedulerConfiguration
|
||||||
Client clientset.Interface
|
|
||||||
|
Client clientset.Interface
|
||||||
|
SecureServing *apiserveroptions.SecureServingOptionsWithLoopback
|
||||||
|
DisableMetrics bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDeschedulerServer creates a new DeschedulerServer with default parameters
|
// NewDeschedulerServer creates a new DeschedulerServer with default parameters
|
||||||
func NewDeschedulerServer() *DeschedulerServer {
|
func NewDeschedulerServer() (*DeschedulerServer, error) {
|
||||||
versioned := v1alpha1.DeschedulerConfiguration{}
|
cfg, err := newDefaultComponentConfig()
|
||||||
deschedulerscheme.Scheme.Default(&versioned)
|
if err != nil {
|
||||||
cfg := componentconfig.DeschedulerConfiguration{}
|
return nil, err
|
||||||
deschedulerscheme.Scheme.Convert(versioned, &cfg, nil)
|
|
||||||
s := DeschedulerServer{
|
|
||||||
DeschedulerConfiguration: cfg,
|
|
||||||
}
|
}
|
||||||
return &s
|
|
||||||
|
secureServing := apiserveroptions.NewSecureServingOptions().WithLoopback()
|
||||||
|
secureServing.BindPort = DefaultDeschedulerPort
|
||||||
|
|
||||||
|
return &DeschedulerServer{
|
||||||
|
DeschedulerConfiguration: *cfg,
|
||||||
|
SecureServing: secureServing,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDefaultComponentConfig() (*componentconfig.DeschedulerConfiguration, error) {
|
||||||
|
versionedCfg := v1alpha1.DeschedulerConfiguration{
|
||||||
|
LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
|
||||||
|
LeaderElect: false,
|
||||||
|
LeaseDuration: metav1.Duration{Duration: 137 * time.Second},
|
||||||
|
RenewDeadline: metav1.Duration{Duration: 107 * time.Second},
|
||||||
|
RetryPeriod: metav1.Duration{Duration: 26 * time.Second},
|
||||||
|
ResourceLock: "leases",
|
||||||
|
ResourceName: "descheduler",
|
||||||
|
ResourceNamespace: "kube-system",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
deschedulerscheme.Scheme.Default(&versionedCfg)
|
||||||
|
cfg := componentconfig.DeschedulerConfiguration{}
|
||||||
|
if err := deschedulerscheme.Scheme.Convert(&versionedCfg, &cfg, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddFlags adds flags for a specific SchedulerServer to the specified FlagSet
|
// AddFlags adds flags for a specific SchedulerServer to the specified FlagSet
|
||||||
func (rs *DeschedulerServer) AddFlags(fs *pflag.FlagSet) {
|
func (rs *DeschedulerServer) AddFlags(fs *pflag.FlagSet) {
|
||||||
|
fs.StringVar(&rs.Logging.Format, "logging-format", "text", `Sets the log format. Permitted formats: "text", "json". Non-default formats don't honor these flags: --add-dir-header, --alsologtostderr, --log-backtrace-at, --log-dir, --log-file, --log-file-max-size, --logtostderr, --skip-headers, --skip-log-headers, --stderrthreshold, --log-flush-frequency.\nNon-default choices are currently alpha and subject to change without warning.`)
|
||||||
fs.DurationVar(&rs.DeschedulingInterval, "descheduling-interval", rs.DeschedulingInterval, "Time interval between two consecutive descheduler executions. Setting this value instructs the descheduler to run in a continuous loop at the interval specified.")
|
fs.DurationVar(&rs.DeschedulingInterval, "descheduling-interval", rs.DeschedulingInterval, "Time interval between two consecutive descheduler executions. Setting this value instructs the descheduler to run in a continuous loop at the interval specified.")
|
||||||
fs.StringVar(&rs.KubeconfigFile, "kubeconfig", rs.KubeconfigFile, "File with kube configuration.")
|
fs.StringVar(&rs.KubeconfigFile, "kubeconfig", rs.KubeconfigFile, "File with kube configuration.")
|
||||||
fs.StringVar(&rs.PolicyConfigFile, "policy-config-file", rs.PolicyConfigFile, "File with descheduler policy configuration.")
|
fs.StringVar(&rs.PolicyConfigFile, "policy-config-file", rs.PolicyConfigFile, "File with descheduler policy configuration.")
|
||||||
fs.BoolVar(&rs.DryRun, "dry-run", rs.DryRun, "execute descheduler in dry run mode.")
|
fs.BoolVar(&rs.DryRun, "dry-run", rs.DryRun, "execute descheduler in dry run mode.")
|
||||||
// node-selector query causes descheduler to run only on nodes that matches the node labels in the query
|
fs.BoolVar(&rs.DisableMetrics, "disable-metrics", rs.DisableMetrics, "Disables metrics. The metrics are by default served through https://localhost:10258/metrics. Secure address, resp. port can be changed through --bind-address, resp. --secure-port flags.")
|
||||||
fs.StringVar(&rs.NodeSelector, "node-selector", rs.NodeSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
|
|
||||||
// max-no-pods-to-evict limits the maximum number of pods to be evicted per node by descheduler.
|
componentbaseoptions.BindLeaderElectionFlags(&rs.LeaderElection, fs)
|
||||||
fs.IntVar(&rs.MaxNoOfPodsToEvictPerNode, "max-pods-to-evict-per-node", rs.MaxNoOfPodsToEvictPerNode, "Limits the maximum number of pods to be evicted per node by descheduler")
|
|
||||||
// evict-local-storage-pods allows eviction of pods that are using local storage. This is false by default.
|
rs.SecureServing.AddFlags(fs)
|
||||||
fs.BoolVar(&rs.EvictLocalStoragePods, "evict-local-storage-pods", rs.EvictLocalStoragePods, "Enables evicting pods using local storage by descheduler")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,44 +18,91 @@ limitations under the License.
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/cmd/descheduler/app/options"
|
"sigs.k8s.io/descheduler/cmd/descheduler/app/options"
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler"
|
"sigs.k8s.io/descheduler/pkg/descheduler"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
aflag "k8s.io/component-base/cli/flag"
|
apiserver "k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/component-base/logs"
|
"k8s.io/apiserver/pkg/server/mux"
|
||||||
"k8s.io/klog"
|
restclient "k8s.io/client-go/rest"
|
||||||
|
"k8s.io/component-base/config"
|
||||||
|
_ "k8s.io/component-base/logs/json/register"
|
||||||
|
"k8s.io/component-base/logs/registry"
|
||||||
|
"k8s.io/component-base/metrics/legacyregistry"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewDeschedulerCommand creates a *cobra.Command object with default parameters
|
// NewDeschedulerCommand creates a *cobra.Command object with default parameters
|
||||||
func NewDeschedulerCommand(out io.Writer) *cobra.Command {
|
func NewDeschedulerCommand(out io.Writer) *cobra.Command {
|
||||||
s := options.NewDeschedulerServer()
|
s, err := options.NewDeschedulerServer()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "unable to initialize server")
|
||||||
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "descheduler",
|
Use: "descheduler",
|
||||||
Short: "descheduler",
|
Short: "descheduler",
|
||||||
Long: `The descheduler evicts pods which may be bound to less desired nodes`,
|
Long: `The descheduler evicts pods which may be bound to less desired nodes`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
logs.InitLogs()
|
// s.Logs.Config.Format = s.Logging.Format
|
||||||
defer logs.FlushLogs()
|
|
||||||
err := Run(s)
|
// LoopbackClientConfig is a config for a privileged loopback connection
|
||||||
if err != nil {
|
var LoopbackClientConfig *restclient.Config
|
||||||
klog.Errorf("%v", err)
|
var SecureServing *apiserver.SecureServingInfo
|
||||||
|
if err := s.SecureServing.ApplyTo(&SecureServing, &LoopbackClientConfig); err != nil {
|
||||||
|
klog.ErrorS(err, "failed to apply secure server configuration")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory, _ := registry.LogRegistry.Get(s.Logging.Format)
|
||||||
|
if factory == nil {
|
||||||
|
klog.ClearLogger()
|
||||||
|
} else {
|
||||||
|
log, logrFlush := factory.Create(config.LoggingConfiguration{})
|
||||||
|
defer logrFlush()
|
||||||
|
klog.SetLogger(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, done := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
pathRecorderMux := mux.NewPathRecorderMux("descheduler")
|
||||||
|
if !s.DisableMetrics {
|
||||||
|
pathRecorderMux.Handle("/metrics", legacyregistry.HandlerWithReset())
|
||||||
|
}
|
||||||
|
|
||||||
|
healthz.InstallHandler(pathRecorderMux, healthz.NamedCheck("Descheduler", healthz.PingHealthz.Check))
|
||||||
|
|
||||||
|
stoppedCh, _, err := SecureServing.Serve(pathRecorderMux, 0, ctx.Done())
|
||||||
|
if err != nil {
|
||||||
|
klog.Fatalf("failed to start secure server: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Run(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "descheduler server")
|
||||||
|
}
|
||||||
|
|
||||||
|
done()
|
||||||
|
// wait for metrics server to close
|
||||||
|
<-stoppedCh
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.SetOutput(out)
|
cmd.SetOut(out)
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.SetNormalizeFunc(aflag.WordSepNormalizeFunc)
|
|
||||||
flags.AddGoFlagSet(flag.CommandLine)
|
|
||||||
s.AddFlags(flags)
|
s.AddFlags(flags)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run(rs *options.DeschedulerServer) error {
|
func Run(ctx context.Context, rs *options.DeschedulerServer) error {
|
||||||
return descheduler.Run(rs)
|
return descheduler.Run(ctx, rs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,70 +18,19 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// gitCommit is a constant representing the source version that
|
|
||||||
// generated this build. It should be set during build via -ldflags.
|
|
||||||
gitCommit string
|
|
||||||
// version is a constant representing the version tag that
|
|
||||||
// generated this build. It should be set during build via -ldflags.
|
|
||||||
version string
|
|
||||||
// buildDate in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
|
||||||
//It should be set during build via -ldflags.
|
|
||||||
buildDate string
|
|
||||||
)
|
|
||||||
|
|
||||||
// Info holds the information related to descheduler app version.
|
|
||||||
type Info struct {
|
|
||||||
Major string `json:"major"`
|
|
||||||
Minor string `json:"minor"`
|
|
||||||
GitCommit string `json:"gitCommit"`
|
|
||||||
GitVersion string `json:"gitVersion"`
|
|
||||||
BuildDate string `json:"buildDate"`
|
|
||||||
GoVersion string `json:"goVersion"`
|
|
||||||
Compiler string `json:"compiler"`
|
|
||||||
Platform string `json:"platform"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the overall codebase version. It's for detecting
|
|
||||||
// what code a binary was built from.
|
|
||||||
func Get() Info {
|
|
||||||
majorVersion, minorVersion := splitVersion(version)
|
|
||||||
return Info{
|
|
||||||
Major: majorVersion,
|
|
||||||
Minor: minorVersion,
|
|
||||||
GitCommit: gitCommit,
|
|
||||||
GitVersion: version,
|
|
||||||
BuildDate: buildDate,
|
|
||||||
GoVersion: runtime.Version(),
|
|
||||||
Compiler: runtime.Compiler,
|
|
||||||
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVersionCommand() *cobra.Command {
|
func NewVersionCommand() *cobra.Command {
|
||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Version of descheduler",
|
Short: "Version of descheduler",
|
||||||
Long: `Prints the version of descheduler.`,
|
Long: `Prints the version of descheduler.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Printf("Descheduler version %+v\n", Get())
|
fmt.Printf("Descheduler version %+v\n", version.Get())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return versionCmd
|
return versionCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitVersion splits the git version to generate major and minor versions needed.
|
|
||||||
func splitVersion(version string) (string, string) {
|
|
||||||
if version == "" {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
// A sample version would be of form v0.1.0-7-ge884046, so split at first '.' and
|
|
||||||
// then return 0 and 1+(+ appended to follow semver convention) for major and minor versions.
|
|
||||||
return strings.Trim(strings.Split(version, ".")[0], "v"), strings.Split(version, ".")[1] + "+"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,20 +17,23 @@ limitations under the License.
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"k8s.io/component-base/cli"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
"sigs.k8s.io/descheduler/cmd/descheduler/app"
|
"sigs.k8s.io/descheduler/cmd/descheduler/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
klog.SetOutput(os.Stdout)
|
||||||
|
klog.InitFlags(nil)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
out := os.Stdout
|
out := os.Stdout
|
||||||
cmd := app.NewDeschedulerCommand(out)
|
cmd := app.NewDeschedulerCommand(out)
|
||||||
cmd.AddCommand(app.NewVersionCommand())
|
cmd.AddCommand(app.NewVersionCommand())
|
||||||
flag.CommandLine.Parse([]string{})
|
|
||||||
if err := cmd.Execute(); err != nil {
|
code := cli.Run(cmd)
|
||||||
fmt.Println(err)
|
os.Exit(code)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
5
docs/README.md
Normal file
5
docs/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Documentation Index
|
||||||
|
|
||||||
|
- [Contributor Guide](contributor-guide.md)
|
||||||
|
- [Release Guide](release-guide.md)
|
||||||
|
- [User Guide](user-guide.md)
|
||||||
56
docs/contributor-guide.md
Normal file
56
docs/contributor-guide.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Contributor Guide
|
||||||
|
|
||||||
|
## Required Tools
|
||||||
|
|
||||||
|
- [Git](https://git-scm.com/downloads)
|
||||||
|
- [Go 1.16+](https://golang.org/dl/)
|
||||||
|
- [Docker](https://docs.docker.com/install/)
|
||||||
|
- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl)
|
||||||
|
- [kind v0.10.0+](https://kind.sigs.k8s.io/)
|
||||||
|
|
||||||
|
## Build and Run
|
||||||
|
|
||||||
|
Build descheduler.
|
||||||
|
```sh
|
||||||
|
cd $GOPATH/src/sigs.k8s.io
|
||||||
|
git clone https://github.com/kubernetes-sigs/descheduler.git
|
||||||
|
cd descheduler
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Run descheduler.
|
||||||
|
```sh
|
||||||
|
./_output/bin/descheduler --kubeconfig <path to kubeconfig> --policy-config-file <path-to-policy-file> --v 1
|
||||||
|
```
|
||||||
|
|
||||||
|
View all CLI options.
|
||||||
|
```
|
||||||
|
./_output/bin/descheduler --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run Tests
|
||||||
|
```
|
||||||
|
GOOS=linux make dev-image
|
||||||
|
kind create cluster --config hack/kind_config.yaml
|
||||||
|
kind load docker-image <image name>
|
||||||
|
kind get kubeconfig > /tmp/admin.conf
|
||||||
|
export KUBECONFIG=/tmp/admin.conf
|
||||||
|
make test-unit
|
||||||
|
make test-e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run Helm Tests
|
||||||
|
Run the helm test for a particular descheduler release by setting below variables,
|
||||||
|
```
|
||||||
|
HELM_IMAGE_REPO="descheduler"
|
||||||
|
HELM_IMAGE_TAG="helm-test"
|
||||||
|
HELM_CHART_LOCATION="./charts/descheduler"
|
||||||
|
```
|
||||||
|
The helm tests runs as part of descheduler CI. But, to run it manually from the descheduler root,
|
||||||
|
|
||||||
|
```
|
||||||
|
make test-helm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Miscellaneous
|
||||||
|
See the [hack directory](https://github.com/kubernetes-sigs/descheduler/tree/master/hack) for additional tools and scripts used for developing the descheduler.
|
||||||
16
docs/proposals.md
Normal file
16
docs/proposals.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Proposals
|
||||||
|
This document walk you through about all the enhancements proposals for descheduler.
|
||||||
|
|
||||||
|
## Descheduler v1alpha2 Design Proposal
|
||||||
|
```yaml
|
||||||
|
title: Descheduler v1alpha2 Design Proposal
|
||||||
|
authors:
|
||||||
|
- "@damemi"
|
||||||
|
link:
|
||||||
|
- https://docs.google.com/document/d/1S1JCh-0F-QCJvBBG-kbmXiHAJFF8doArhDIAKbOj93I/edit#heading=h.imbp1ctnc8lx
|
||||||
|
- https://github.com/kubernetes-sigs/descheduler/issues/679
|
||||||
|
owning-sig: sig-scheduling
|
||||||
|
creation-date: 2021-05-01
|
||||||
|
status: implementable
|
||||||
|
```
|
||||||
|
|
||||||
74
docs/release-guide.md
Normal file
74
docs/release-guide.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Release Guide
|
||||||
|
|
||||||
|
## Container Image
|
||||||
|
|
||||||
|
### Semi-automatic
|
||||||
|
|
||||||
|
1. Make sure your repo is clean by git's standards
|
||||||
|
2. Create a release branch `git checkout -b release-1.18` (not required for patch releases)
|
||||||
|
3. Push the release branch to the descheuler repo and ensure branch protection is enabled (not required for patch releases)
|
||||||
|
4. Tag the repository from the `master` branch (from the `release-1.18` branch for a patch release) and push the tag `VERSION=v0.18.0 git tag -m $VERSION $VERSION; git push origin $VERSION`
|
||||||
|
5. Publish a draft release using the tag you just created
|
||||||
|
6. Perform the [image promotion process](https://github.com/kubernetes/k8s.io/tree/main/k8s.gcr.io#image-promoter)
|
||||||
|
7. Publish release
|
||||||
|
8. Email `kubernetes-sig-scheduling@googlegroups.com` to announce the release
|
||||||
|
|
||||||
|
### Manual
|
||||||
|
|
||||||
|
1. Make sure your repo is clean by git's standards
|
||||||
|
2. Create a release branch `git checkout -b release-1.18` (not required for patch releases)
|
||||||
|
3. Push the release branch to the descheuler repo and ensure branch protection is enabled (not required for patch releases)
|
||||||
|
4. Tag the repository from the `master` branch (from the `release-1.18` branch for a patch release) and push the tag `VERSION=v0.18.0 git tag -m $VERSION $VERSION; git push origin $VERSION`
|
||||||
|
5. Checkout the tag you just created and make sure your repo is clean by git's standards `git checkout $VERSION`
|
||||||
|
6. Build and push the container image to the staging registry `VERSION=$VERSION make push-all`
|
||||||
|
7. Publish a draft release using the tag you just created
|
||||||
|
8. Perform the [image promotion process](https://github.com/kubernetes/k8s.io/tree/main/k8s.gcr.io#image-promoter)
|
||||||
|
9. Publish release
|
||||||
|
10. Email `kubernetes-sig-scheduling@googlegroups.com` to announce the release
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
It's important to create the tag on the master branch after creating the release-* branch so that the [Helm releaser-action](#helm-chart) can work.
|
||||||
|
It compares the changes in the action-triggering branch to the latest tag on that branch, so if you tag before creating the new branch there
|
||||||
|
will be nothing to compare and it will fail (creating a new release branch usually involves no code changes). For this same reason, you should
|
||||||
|
also tag patch releases (on the release-* branch) *after* pushing changes (if those changes involve bumping the Helm chart version).
|
||||||
|
|
||||||
|
See [post-descheduler-push-images dashboard](https://testgrid.k8s.io/sig-scheduling#post-descheduler-push-images) for staging registry image build job status.
|
||||||
|
|
||||||
|
View the descheduler staging registry using [this URL](https://console.cloud.google.com/gcr/images/k8s-staging-descheduler/GLOBAL/descheduler) in a web browser
|
||||||
|
or use the below `gcloud` commands.
|
||||||
|
|
||||||
|
List images in staging registry.
|
||||||
|
```
|
||||||
|
gcloud container images list --repository gcr.io/k8s-staging-descheduler
|
||||||
|
```
|
||||||
|
|
||||||
|
List descheduler image tags in the staging registry.
|
||||||
|
```
|
||||||
|
gcloud container images list-tags gcr.io/k8s-staging-descheduler/descheduler
|
||||||
|
```
|
||||||
|
|
||||||
|
Get SHA256 hash for a specific image in the staging registry.
|
||||||
|
```
|
||||||
|
gcloud container images describe gcr.io/k8s-staging-descheduler/descheduler:v20200206-0.9.0-94-ge2a23f284
|
||||||
|
```
|
||||||
|
|
||||||
|
Pull image from the staging registry.
|
||||||
|
```
|
||||||
|
docker pull gcr.io/k8s-staging-descheduler/descheduler:v20200206-0.9.0-94-ge2a23f284
|
||||||
|
```
|
||||||
|
|
||||||
|
## Helm Chart
|
||||||
|
Helm chart releases are managed by a separate set of git tags that are prefixed with `descheduler-helm-chart-*`. Example git tag name is `descheduler-helm-chart-0.18.0`.
|
||||||
|
Released versions of the helm charts are stored in the `gh-pages` branch of this repo. The [chart-releaser-action GitHub Action](https://github.com/helm/chart-releaser-action)
|
||||||
|
is setup to build and push the helm charts to the `gh-pages` branch when changes are pushed to a `release-*` branch.
|
||||||
|
|
||||||
|
The major and minor version of the chart matches the descheduler major and minor versions. For example descheduler helm chart version helm-descheduler-chart-0.18.0 corresponds
|
||||||
|
to descheduler version v0.18.0. The patch version of the descheduler helm chart and the patcher version of the descheduler will not necessarily match. The patch
|
||||||
|
version of the descheduler helm chart is used to version changes specific to the helm chart.
|
||||||
|
|
||||||
|
1. Merge all helm chart changes into the master branch before the release is tagged/cut
|
||||||
|
1. Ensure that `appVersion` in file `charts/descheduler/Chart.yaml` matches the descheduler version(no `v` prefix)
|
||||||
|
2. Ensure that `version` in file `charts/descheduler/Chart.yaml` has been incremented. This is the chart version.
|
||||||
|
2. Make sure your repo is clean by git's standards
|
||||||
|
3. Follow the release-branch or patch release tagging pattern from the above section.
|
||||||
|
4. Verify the new helm artifact has been successfully pushed to the `gh-pages` branch
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Release process
|
|
||||||
|
|
||||||
## Semi-automatic
|
|
||||||
|
|
||||||
1. Make sure your repo is clean by git's standards
|
|
||||||
2. Tag the repository and push the tag `VERSION=v0.10.0 git tag -m $VERSION $VERSION; git push origin $VERSION`
|
|
||||||
3. Publish a draft release using the tag you just created
|
|
||||||
4. Perform the [image promotion process](https://github.com/kubernetes/k8s.io/tree/master/k8s.gcr.io#image-promoter)
|
|
||||||
5. Publish release
|
|
||||||
6. Email `kubernetes-sig-scheduling@googlegroups.com` to announce the release
|
|
||||||
|
|
||||||
## Manual
|
|
||||||
|
|
||||||
1. Make sure your repo is clean by git's standards
|
|
||||||
2. Tag the repository and push the tag `VERSION=v0.10.0 git tag -m $VERSION $VERSION; git push origin $VERSION`
|
|
||||||
3. Checkout the tag you just created and make sure your repo is clean by git's standards `git checkout $VERSION`
|
|
||||||
4. Build and push the container image to the staging registry `VERSION=$VERSION make push`
|
|
||||||
5. Publish a draft release using the tag you just created
|
|
||||||
6. Perform the [image promotion process](https://github.com/kubernetes/k8s.io/tree/master/k8s.gcr.io#image-promoter)
|
|
||||||
7. Publish release
|
|
||||||
8. Email `kubernetes-sig-scheduling@googlegroups.com` to announce the release
|
|
||||||
174
docs/user-guide.md
Normal file
174
docs/user-guide.md
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# User Guide
|
||||||
|
|
||||||
|
Starting with descheduler release v0.10.0 container images are available in the official k8s container registry.
|
||||||
|
|
||||||
|
Descheduler Version | Container Image | Architectures |
|
||||||
|
------------------- |--------------------------------------------|-------------------------|
|
||||||
|
v0.24.1 | k8s.gcr.io/descheduler/descheduler:v0.24.0 | AMD64<br>ARM64<br>ARMv7 |
|
||||||
|
v0.24.0 | k8s.gcr.io/descheduler/descheduler:v0.24.0 | AMD64<br>ARM64<br>ARMv7 |
|
||||||
|
v0.23.1 | k8s.gcr.io/descheduler/descheduler:v0.23.1 | AMD64<br>ARM64<br>ARMv7 |
|
||||||
|
v0.22.0 | k8s.gcr.io/descheduler/descheduler:v0.22.0 | AMD64<br>ARM64<br>ARMv7 |
|
||||||
|
v0.21.0 | k8s.gcr.io/descheduler/descheduler:v0.21.0 | AMD64<br>ARM64<br>ARMv7 |
|
||||||
|
v0.20.0 | k8s.gcr.io/descheduler/descheduler:v0.20.0 | AMD64<br>ARM64 |
|
||||||
|
v0.19.0 | k8s.gcr.io/descheduler/descheduler:v0.19.0 | AMD64 |
|
||||||
|
v0.18.0 | k8s.gcr.io/descheduler/descheduler:v0.18.0 | AMD64 |
|
||||||
|
v0.10.0 | k8s.gcr.io/descheduler/descheduler:v0.10.0 | AMD64 |
|
||||||
|
|
||||||
|
Note that multi-arch container images cannot be pulled by [kind](https://kind.sigs.k8s.io) from a registry. Therefore
|
||||||
|
starting with descheduler release v0.20.0 use the below process to download the official descheduler
|
||||||
|
image into a kind cluster.
|
||||||
|
```
|
||||||
|
kind create cluster
|
||||||
|
docker pull k8s.gcr.io/descheduler/descheduler:v0.20.0
|
||||||
|
kind load docker-image k8s.gcr.io/descheduler/descheduler:v0.20.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Policy Configuration Examples
|
||||||
|
The [examples](https://github.com/kubernetes-sigs/descheduler/tree/master/examples) directory has descheduler policy configuration examples.
|
||||||
|
|
||||||
|
## CLI Options
|
||||||
|
The descheduler has many CLI options that can be used to override its default behavior.
|
||||||
|
```
|
||||||
|
descheduler --help
|
||||||
|
The descheduler evicts pods which may be bound to less desired nodes
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
descheduler [flags]
|
||||||
|
descheduler [command]
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
completion generate the autocompletion script for the specified shell
|
||||||
|
help Help about any command
|
||||||
|
version Version of descheduler
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--add-dir-header If true, adds the file directory to the header of the log messages (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--alsologtostderr log to standard error as well as files (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--bind-address ip The IP address on which to listen for the --secure-port port. The associated interface(s) must be reachable by the rest of the cluster, and by CLI/web clients. If blank or an unspecified address (0.0.0.0 or ::), all interfaces will be used. (default 0.0.0.0)
|
||||||
|
--cert-dir string The directory where the TLS certs are located. If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored. (default "apiserver.local.config/certificates")
|
||||||
|
--descheduling-interval duration Time interval between two consecutive descheduler executions. Setting this value instructs the descheduler to run in a continuous loop at the interval specified.
|
||||||
|
--disable-metrics Disables metrics. The metrics are by default served through https://localhost:10258/metrics. Secure address, resp. port can be changed through --bind-address, resp. --secure-port flags.
|
||||||
|
--dry-run execute descheduler in dry run mode.
|
||||||
|
-h, --help help for descheduler
|
||||||
|
--http2-max-streams-per-connection int The limit that the server gives to clients for the maximum number of streams in an HTTP/2 connection. Zero means to use golang's default.
|
||||||
|
--kubeconfig string File with kube configuration.
|
||||||
|
--leader-elect Start a leader election client and gain leadership before executing the main loop. Enable this when running replicated components for high availability.
|
||||||
|
--leader-elect-lease-duration duration The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled. (default 15s)
|
||||||
|
--leader-elect-renew-deadline duration The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than or equal to the lease duration. This is only applicable if leader election is enabled. (default 10s)
|
||||||
|
--leader-elect-resource-lock string The type of resource object that is used for locking during leader election. Supported options are 'endpoints', 'configmaps', 'leases', 'endpointsleases' and 'configmapsleases'. (default "leases")
|
||||||
|
--leader-elect-resource-name string The name of resource object that is used for locking during leader election. (default "descheduler")
|
||||||
|
--leader-elect-resource-namespace string The namespace of resource object that is used for locking during leader election. (default "kube-system")
|
||||||
|
--leader-elect-retry-period duration The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled. (default 2s)
|
||||||
|
--log-backtrace-at traceLocation when logging hits line file:N, emit a stack trace (default :0) (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--log-dir string If non-empty, write log files in this directory (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--log-file string If non-empty, use this log file (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--log-file-max-size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--log-flush-frequency duration Maximum number of seconds between log flushes (default 5s)
|
||||||
|
--logging-format string Sets the log format. Permitted formats: "text", "json". Non-default formats don't honor these flags: --add-dir-header, --alsologtostderr, --log-backtrace-at, --log-dir, --log-file, --log-file-max-size, --logtostderr, --skip-headers, --skip-log-headers, --stderrthreshold, --log-flush-frequency.\nNon-default choices are currently alpha and subject to change without warning. (default "text")
|
||||||
|
--logtostderr log to standard error instead of files (default true) (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--one-output If true, only write logs to their native severity level (vs also writing to each lower severity level) (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--permit-address-sharing If true, SO_REUSEADDR will be used when binding the port. This allows binding to wildcard IPs like 0.0.0.0 and specific IPs in parallel, and it avoids waiting for the kernel to release sockets in TIME_WAIT state. [default=false]
|
||||||
|
--permit-port-sharing If true, SO_REUSEPORT will be used when binding the port, which allows more than one instance to bind on the same address and port. [default=false]
|
||||||
|
--policy-config-file string File with descheduler policy configuration.
|
||||||
|
--secure-port int The port on which to serve HTTPS with authentication and authorization. If 0, don't serve HTTPS at all. (default 10258)
|
||||||
|
--skip-headers If true, avoid header prefixes in the log messages (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--skip-log-headers If true, avoid headers when opening log files (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--stderrthreshold severity logs at or above this threshold go to stderr (default 2) (DEPRECATED: will be removed in a future release, see https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/2845-deprecate-klog-specific-flags-in-k8s-components)
|
||||||
|
--tls-cert-file string File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to the directory specified by --cert-dir.
|
||||||
|
--tls-cipher-suites strings Comma-separated list of cipher suites for the server. If omitted, the default Go cipher suites will be used.
|
||||||
|
Preferred values: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384.
|
||||||
|
Insecure values: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_RC4_128_SHA.
|
||||||
|
--tls-min-version string Minimum TLS version supported. Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13
|
||||||
|
--tls-private-key-file string File containing the default x509 private key matching --tls-cert-file.
|
||||||
|
--tls-sni-cert-key namedCertKey A pair of x509 certificate and private key file paths, optionally suffixed with a list of domain patterns which are fully qualified domain names, possibly with prefixed wildcard segments. The domain patterns also allow IP addresses, but IPs should only be used if the apiserver has visibility to the IP address requested by a client. If no domain patterns are provided, the names of the certificate are extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns trump over extracted names. For multiple key/certificate pairs, use the --tls-sni-cert-key multiple times. Examples: "example.crt,example.key" or "foo.crt,foo.key:*.foo.com,foo.com". (default [])
|
||||||
|
-v, --v Level number for the log level verbosity
|
||||||
|
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
||||||
|
|
||||||
|
Use "descheduler [command] --help" for more information about a command.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Use Cases
|
||||||
|
This section contains descriptions of real world production use cases.
|
||||||
|
|
||||||
|
### Balance Cluster By Pod Age
|
||||||
|
When initially migrating applications from a static virtual machine infrastructure to a cloud native k8s
|
||||||
|
infrastructure there can be a tendency to treat application pods like static virtual machines. One approach
|
||||||
|
to help prevent developers and operators from treating pods like virtual machines is to ensure that pods
|
||||||
|
only run for a fixed amount
|
||||||
|
of time.
|
||||||
|
|
||||||
|
The `PodLifeTime` strategy can be used to ensure that old pods are evicted. It is recommended to create a
|
||||||
|
[pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for each
|
||||||
|
application to ensure application availability.
|
||||||
|
```
|
||||||
|
descheduler -v=3 --evict-local-storage-pods --policy-config-file=pod-life-time.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
This policy configuration file ensures that pods created more than 7 days ago are evicted.
|
||||||
|
```
|
||||||
|
---
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"PodLifeTime":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podLifeTime:
|
||||||
|
maxPodLifeTimeSeconds: 604800 # pods run for a maximum of 7 days
|
||||||
|
```
|
||||||
|
|
||||||
|
### Balance Cluster By Node Memory Utilization
|
||||||
|
If your cluster has been running for a long period of time, you may find that the resource utilization is not very
|
||||||
|
balanced. The following two strategies can be used to rebalance your cluster based on `cpu`, `memory`
|
||||||
|
or `number of pods`.
|
||||||
|
|
||||||
|
#### Balance high utilization nodes
|
||||||
|
Using `LowNodeUtilization`, descheduler will rebalance the cluster based on memory by evicting pods
|
||||||
|
from nodes with memory utilization over 70% to nodes with memory utilization below 20%.
|
||||||
|
|
||||||
|
```
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"LowNodeUtilization":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
nodeResourceUtilizationThresholds:
|
||||||
|
thresholds:
|
||||||
|
"memory": 20
|
||||||
|
targetThresholds:
|
||||||
|
"memory": 70
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Balance low utilization nodes
|
||||||
|
Using `HighNodeUtilization`, descheduler will rebalance the cluster based on memory by evicting pods
|
||||||
|
from nodes with memory utilization lower than 20%. This should be use `NodeResourcesFit` with the `MostAllocated` scoring strategy based on these [doc](https://kubernetes.io/docs/reference/scheduling/config/#scheduling-plugins).
|
||||||
|
The evicted pods will be compacted into minimal set of nodes.
|
||||||
|
|
||||||
|
```
|
||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"HighNodeUtilization":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
nodeResourceUtilizationThresholds:
|
||||||
|
thresholds:
|
||||||
|
"memory": 20
|
||||||
|
```
|
||||||
|
|
||||||
|
### Autoheal Node Problems
|
||||||
|
Descheduler's `RemovePodsViolatingNodeTaints` strategy can be combined with
|
||||||
|
[Node Problem Detector](https://github.com/kubernetes/node-problem-detector/) and
|
||||||
|
[Cluster Autoscaler](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler) to automatically remove
|
||||||
|
Nodes which have problems. Node Problem Detector can detect specific Node problems and report them to the API server.
|
||||||
|
There is a feature called TaintNodeByCondition of the node controller that takes some conditions and turns them into taints. Currently, this only works for the default node conditions: PIDPressure, MemoryPressure, DiskPressure, Ready, and some cloud provider specific conditions.
|
||||||
|
The Descheduler will then deschedule workloads from those Nodes. Finally, if the descheduled Node's resource
|
||||||
|
allocation falls below the Cluster Autoscaler's scale down threshold, the Node will become a scale down candidate
|
||||||
|
and can be removed by Cluster Autoscaler. These three components form an autohealing cycle for Node problems.
|
||||||
|
---
|
||||||
|
**NOTE**
|
||||||
|
|
||||||
|
Once [kubernetes/node-problem-detector#565](https://github.com/kubernetes/node-problem-detector/pull/565) is available in NPD, we need to update this section.
|
||||||
|
|
||||||
|
---
|
||||||
14
examples/failed-pods.yaml
Normal file
14
examples/failed-pods.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"RemoveFailedPods":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
failedPods:
|
||||||
|
reasons:
|
||||||
|
- "OutOfcpu"
|
||||||
|
- "CreateContainerConfigError"
|
||||||
|
includingInitContainers: true
|
||||||
|
excludeOwnerKinds:
|
||||||
|
- "Job"
|
||||||
|
minPodLifetimeSeconds: 3600 # 1 hour
|
||||||
9
examples/high-node-utilization.yml
Normal file
9
examples/high-node-utilization.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"HighNodeUtilization":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
nodeResourceUtilizationThresholds:
|
||||||
|
thresholds:
|
||||||
|
"memory": 20
|
||||||
11
examples/low-node-utilization.yml
Normal file
11
examples/low-node-utilization.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"LowNodeUtilization":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
nodeResourceUtilizationThresholds:
|
||||||
|
thresholds:
|
||||||
|
"memory": 20
|
||||||
|
targetThresholds:
|
||||||
|
"memory": 70
|
||||||
8
examples/pod-life-time.yml
Normal file
8
examples/pod-life-time.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"PodLifeTime":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podLifeTime:
|
||||||
|
maxPodLifeTimeSeconds: 604800 # 7 days
|
||||||
@@ -17,3 +17,13 @@ strategies:
|
|||||||
"cpu" : 50
|
"cpu" : 50
|
||||||
"memory": 50
|
"memory": 50
|
||||||
"pods": 50
|
"pods": 50
|
||||||
|
"RemovePodsHavingTooManyRestarts":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
podsHavingTooManyRestarts:
|
||||||
|
podRestartThreshold: 100
|
||||||
|
includingInitContainers: true
|
||||||
|
"RemovePodsViolatingTopologySpreadConstraint":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
includeSoftConstraints: true
|
||||||
|
|||||||
7
examples/topology-spread-constraint.yaml
Normal file
7
examples/topology-spread-constraint.yaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: "descheduler/v1alpha1"
|
||||||
|
kind: "DeschedulerPolicy"
|
||||||
|
strategies:
|
||||||
|
"RemovePodsViolatingTopologySpreadConstraint":
|
||||||
|
enabled: true
|
||||||
|
params:
|
||||||
|
includeSoftConstraints: true # Include 'ScheduleAnyways' constraints
|
||||||
119
go.mod
119
go.mod
@@ -1,14 +1,117 @@
|
|||||||
module sigs.k8s.io/descheduler
|
module sigs.k8s.io/descheduler
|
||||||
|
|
||||||
go 1.13
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/spf13/cobra v0.0.5
|
github.com/client9/misspell v0.3.4
|
||||||
|
github.com/spf13/cobra v1.4.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
k8s.io/api v0.17.0
|
k8s.io/api v0.24.0
|
||||||
k8s.io/apimachinery v0.17.3-beta.0
|
k8s.io/apimachinery v0.24.0
|
||||||
k8s.io/apiserver v0.17.0
|
k8s.io/apiserver v0.24.0
|
||||||
k8s.io/client-go v0.17.0
|
k8s.io/client-go v0.24.0
|
||||||
k8s.io/component-base v0.17.0
|
k8s.io/code-generator v0.24.0
|
||||||
k8s.io/klog v1.0.0
|
k8s.io/component-base v0.24.0
|
||||||
|
k8s.io/component-helpers v0.24.0
|
||||||
|
k8s.io/klog/v2 v2.60.1
|
||||||
|
k8s.io/kubectl v0.20.5
|
||||||
|
sigs.k8s.io/mdtoc v1.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cloud.google.com/go v0.81.0 // indirect
|
||||||
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||||
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||||
|
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
|
github.com/coreos/go-semver v0.3.0 // indirect
|
||||||
|
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||||
|
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.1 // indirect
|
||||||
|
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
|
github.com/go-logr/logr v1.2.0 // indirect
|
||||||
|
github.com/go-logr/zapr v1.2.0 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/swag v0.19.14 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/gomarkdown/markdown v0.0.0-20200824053859-8c8b3816f167 // indirect
|
||||||
|
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||||
|
github.com/google/go-cmp v0.5.5 // indirect
|
||||||
|
github.com/google/gofuzz v1.1.0 // indirect
|
||||||
|
github.com/google/uuid v1.1.2 // indirect
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.5 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
|
github.com/mmarkdown/mmark v2.0.40+incompatible // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.12.1 // indirect
|
||||||
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
|
github.com/prometheus/common v0.32.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
|
go.etcd.io/etcd/api/v3 v3.5.1 // indirect
|
||||||
|
go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
|
||||||
|
go.etcd.io/etcd/client/v3 v3.5.1 // indirect
|
||||||
|
go.opentelemetry.io/contrib v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v0.20.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v0.7.0 // indirect
|
||||||
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
|
go.uber.org/zap v1.19.0 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 // indirect
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||||
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||||
|
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
|
||||||
|
google.golang.org/grpc v1.40.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
|
k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 // indirect
|
||||||
|
k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
|
||||||
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
6
hack/.spelling_failures
Normal file
6
hack/.spelling_failures
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
BUILD
|
||||||
|
CHANGELOG
|
||||||
|
OWNERS
|
||||||
|
go.mod
|
||||||
|
go.sum
|
||||||
|
vendor/
|
||||||
@@ -18,15 +18,15 @@ E2E_GCE_HOME=$DESCHEDULER_ROOT/hack/e2e-gce
|
|||||||
|
|
||||||
create_cluster() {
|
create_cluster() {
|
||||||
echo "#################### Creating instances ##########################"
|
echo "#################### Creating instances ##########################"
|
||||||
gcloud compute instances create descheduler-$master_uuid --image="ubuntu-1604-xenial-v20180306" --image-project="ubuntu-os-cloud" --zone=us-east1-b
|
gcloud compute instances create descheduler-$master_uuid --image-family="ubuntu-1804-lts" --image-project="ubuntu-os-cloud" --zone=us-east1-b
|
||||||
# Keeping the --zone here so as to make sure that e2e's can run locally.
|
# Keeping the --zone here so as to make sure that e2e's can run locally.
|
||||||
echo "gcloud compute instances delete descheduler-$master_uuid --zone=us-east1-b --quiet" > $E2E_GCE_HOME/delete_cluster.sh
|
echo "gcloud compute instances delete descheduler-$master_uuid --zone=us-east1-b --quiet" > $E2E_GCE_HOME/delete_cluster.sh
|
||||||
|
|
||||||
gcloud compute instances create descheduler-$node1_uuid --image="ubuntu-1604-xenial-v20180306" --image-project="ubuntu-os-cloud" --zone=us-east1-b
|
gcloud compute instances create descheduler-$node1_uuid --image-family="ubuntu-1804-lts" --image-project="ubuntu-os-cloud" --zone=us-east1-b
|
||||||
echo "gcloud compute instances delete descheduler-$node1_uuid --zone=us-east1-b --quiet" >> $E2E_GCE_HOME/delete_cluster.sh
|
echo "gcloud compute instances delete descheduler-$node1_uuid --zone=us-east1-b --quiet" >> $E2E_GCE_HOME/delete_cluster.sh
|
||||||
|
|
||||||
gcloud compute instances create descheduler-$node2_uuid --image="ubuntu-1604-xenial-v20180306" --image-project="ubuntu-os-cloud" --zone=us-east1-b
|
gcloud compute instances create descheduler-$node2_uuid --image-family="ubuntu-1804-lts" --image-project="ubuntu-os-cloud" --zone=us-east1-b
|
||||||
echo "gcloud compute instances delete descheduler-$node2_uuid --zone=us-east1-b --quiet" >> $E2E_GCE_HOME/delete_cluster.sh
|
echo "gcloud compute instances delete descheduler-$node2_uuid --zone=us-east1-c --quiet" >> $E2E_GCE_HOME/delete_cluster.sh
|
||||||
|
|
||||||
# Delete the firewall port created for master.
|
# Delete the firewall port created for master.
|
||||||
echo "gcloud compute firewall-rules delete kubeapiserver-$master_uuid --quiet" >> $E2E_GCE_HOME/delete_cluster.sh
|
echo "gcloud compute firewall-rules delete kubeapiserver-$master_uuid --quiet" >> $E2E_GCE_HOME/delete_cluster.sh
|
||||||
@@ -44,10 +44,10 @@ generate_kubeadm_instance_files() {
|
|||||||
|
|
||||||
|
|
||||||
transfer_install_files() {
|
transfer_install_files() {
|
||||||
gcloud compute scp $E2E_GCE_HOME/kubeadm_preinstall.sh descheduler-$master_uuid:/tmp --zone=us-east1-b
|
gcloud compute scp $E2E_GCE_HOME/kubeadm_preinstall.sh descheduler-$master_uuid:/tmp --zone=us-east1-b
|
||||||
gcloud compute scp $E2E_GCE_HOME/kubeadm_install.sh descheduler-$master_uuid:/tmp --zone=us-east1-b
|
gcloud compute scp $E2E_GCE_HOME/kubeadm_install.sh descheduler-$master_uuid:/tmp --zone=us-east1-b
|
||||||
gcloud compute scp $E2E_GCE_HOME/kubeadm_preinstall.sh descheduler-$node1_uuid:/tmp --zone=us-east1-b
|
gcloud compute scp $E2E_GCE_HOME/kubeadm_preinstall.sh descheduler-$node1_uuid:/tmp --zone=us-east1-b
|
||||||
gcloud compute scp $E2E_GCE_HOME/kubeadm_preinstall.sh descheduler-$node2_uuid:/tmp --zone=us-east1-b
|
gcloud compute scp $E2E_GCE_HOME/kubeadm_preinstall.sh descheduler-$node2_uuid:/tmp --zone=us-east1-c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -55,19 +55,19 @@ install_kube() {
|
|||||||
# Docker installation.
|
# Docker installation.
|
||||||
gcloud compute ssh descheduler-$master_uuid --command "sudo apt-get update; sudo apt-get install -y docker.io" --zone=us-east1-b
|
gcloud compute ssh descheduler-$master_uuid --command "sudo apt-get update; sudo apt-get install -y docker.io" --zone=us-east1-b
|
||||||
gcloud compute ssh descheduler-$node1_uuid --command "sudo apt-get update; sudo apt-get install -y docker.io" --zone=us-east1-b
|
gcloud compute ssh descheduler-$node1_uuid --command "sudo apt-get update; sudo apt-get install -y docker.io" --zone=us-east1-b
|
||||||
gcloud compute ssh descheduler-$node2_uuid --command "sudo apt-get update; sudo apt-get install -y docker.io" --zone=us-east1-b
|
gcloud compute ssh descheduler-$node2_uuid --command "sudo apt-get update; sudo apt-get install -y docker.io" --zone=us-east1-c
|
||||||
# kubeadm installation.
|
# kubeadm installation.
|
||||||
# 1. Transfer files to master, nodes.
|
# 1. Transfer files to master, nodes.
|
||||||
transfer_install_files
|
transfer_install_files
|
||||||
# 2. Install kubeadm.
|
# 2. Install kubeadm.
|
||||||
#TODO: Add rm /tmp/kubeadm_install.sh
|
#TODO: Add rm /tmp/kubeadm_install.sh
|
||||||
# Open port for kube API server
|
# Open port for kube API server
|
||||||
gcloud compute firewall-rules create kubeapiserver-$master_uuid --allow tcp:6443 --source-tags=descheduler-$master_uuid --source-ranges=0.0.0.0/0 --description="Opening api server port"
|
gcloud compute firewall-rules create kubeapiserver-$master_uuid --allow tcp:6443 --source-tags=descheduler-$master_uuid --source-ranges=0.0.0.0/0 --description="Opening api server port"
|
||||||
|
|
||||||
gcloud compute ssh descheduler-$master_uuid --command "sudo chmod 755 /tmp/kubeadm_preinstall.sh; sudo /tmp/kubeadm_preinstall.sh" --zone=us-east1-b
|
gcloud compute ssh descheduler-$master_uuid --command "sudo chmod 755 /tmp/kubeadm_preinstall.sh; sudo /tmp/kubeadm_preinstall.sh" --zone=us-east1-b
|
||||||
kubeadm_join_command=$(gcloud compute ssh descheduler-$master_uuid --command "sudo chmod 755 /tmp/kubeadm_install.sh; sudo /tmp/kubeadm_install.sh" --zone=us-east1-b|grep 'kubeadm join')
|
kubeadm_join_command=$(gcloud compute ssh descheduler-$master_uuid --command "sudo chmod 755 /tmp/kubeadm_install.sh; sudo /tmp/kubeadm_install.sh" --zone=us-east1-b|grep 'kubeadm join')
|
||||||
|
|
||||||
# Copy the kubeconfig file onto /tmp for e2e tests.
|
# Copy the kubeconfig file onto /tmp for e2e tests.
|
||||||
gcloud compute ssh descheduler-$master_uuid --command "sudo cp /etc/kubernetes/admin.conf /tmp; sudo chmod 777 /tmp/admin.conf" --zone=us-east1-b
|
gcloud compute ssh descheduler-$master_uuid --command "sudo cp /etc/kubernetes/admin.conf /tmp; sudo chmod 777 /tmp/admin.conf" --zone=us-east1-b
|
||||||
gcloud compute scp descheduler-$master_uuid:/tmp/admin.conf /tmp/admin.conf --zone=us-east1-b
|
gcloud compute scp descheduler-$master_uuid:/tmp/admin.conf /tmp/admin.conf --zone=us-east1-b
|
||||||
|
|
||||||
@@ -75,16 +75,15 @@ install_kube() {
|
|||||||
gcloud compute ssh descheduler-$master_uuid --command "sudo kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter.yaml --kubeconfig /etc/kubernetes/admin.conf" --zone=us-east1-b
|
gcloud compute ssh descheduler-$master_uuid --command "sudo kubectl apply -f https://raw.githubusercontent.com/cloudnativelabs/kube-router/master/daemonset/kubeadm-kuberouter.yaml --kubeconfig /etc/kubernetes/admin.conf" --zone=us-east1-b
|
||||||
echo $kubeadm_join_command > $E2E_GCE_HOME/kubeadm_join.sh
|
echo $kubeadm_join_command > $E2E_GCE_HOME/kubeadm_join.sh
|
||||||
|
|
||||||
# Copy kubeadm_join to every node.
|
# Copy kubeadm_join to every node.
|
||||||
#TODO: Put these in a loop, so that extension becomes possible.
|
#TODO: Put these in a loop, so that extension becomes possible.
|
||||||
gcloud compute ssh descheduler-$node1_uuid --command "sudo chmod 755 /tmp/kubeadm_preinstall.sh; sudo /tmp/kubeadm_preinstall.sh" --zone=us-east1-b
|
gcloud compute ssh descheduler-$node1_uuid --command "sudo chmod 755 /tmp/kubeadm_preinstall.sh; sudo /tmp/kubeadm_preinstall.sh" --zone=us-east1-b
|
||||||
gcloud compute scp $E2E_GCE_HOME/kubeadm_join.sh descheduler-$node1_uuid:/tmp --zone=us-east1-b
|
gcloud compute scp $E2E_GCE_HOME/kubeadm_join.sh descheduler-$node1_uuid:/tmp --zone=us-east1-b
|
||||||
gcloud compute ssh descheduler-$node1_uuid --command "sudo chmod 755 /tmp/kubeadm_join.sh; sudo /tmp/kubeadm_join.sh" --zone=us-east1-b
|
gcloud compute ssh descheduler-$node1_uuid --command "sudo chmod 755 /tmp/kubeadm_join.sh; sudo /tmp/kubeadm_join.sh" --zone=us-east1-b
|
||||||
|
|
||||||
gcloud compute ssh descheduler-$node2_uuid --command "sudo chmod 755 /tmp/kubeadm_preinstall.sh; sudo /tmp/kubeadm_preinstall.sh" --zone=us-east1-b
|
gcloud compute ssh descheduler-$node2_uuid --command "sudo chmod 755 /tmp/kubeadm_preinstall.sh; sudo /tmp/kubeadm_preinstall.sh" --zone=us-east1-c
|
||||||
gcloud compute scp $E2E_GCE_HOME/kubeadm_join.sh descheduler-$node2_uuid:/tmp --zone=us-east1-b
|
gcloud compute scp $E2E_GCE_HOME/kubeadm_join.sh descheduler-$node2_uuid:/tmp --zone=us-east1-c
|
||||||
gcloud compute ssh descheduler-$node2_uuid --command "sudo chmod 755 /tmp/kubeadm_join.sh; sudo /tmp/kubeadm_join.sh" --zone=us-east1-b
|
gcloud compute ssh descheduler-$node2_uuid --command "sudo chmod 755 /tmp/kubeadm_join.sh; sudo /tmp/kubeadm_join.sh" --zone=us-east1-c
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
kind: Cluster
|
kind: Cluster
|
||||||
apiVersion: kind.sigs.k8s.io/v1alpha3
|
apiVersion: kind.x-k8s.io/v1alpha4
|
||||||
nodes:
|
nodes:
|
||||||
- role: control-plane
|
- role: control-plane
|
||||||
- role: worker
|
- role: worker
|
||||||
|
kubeadmConfigPatches:
|
||||||
|
- |
|
||||||
|
kind: JoinConfiguration
|
||||||
|
nodeRegistration:
|
||||||
|
kubeletExtraArgs:
|
||||||
|
node-labels: "topology.kubernetes.io/zone=local-a"
|
||||||
- role: worker
|
- role: worker
|
||||||
|
kubeadmConfigPatches:
|
||||||
|
- |
|
||||||
|
kind: JoinConfiguration
|
||||||
|
nodeRegistration:
|
||||||
|
kubeletExtraArgs:
|
||||||
|
node-labels: "topology.kubernetes.io/zone=local-b"
|
||||||
|
|||||||
27
hack/tools.go
Normal file
27
hack/tools.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build tools
|
||||||
|
// +build tools
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This package imports things required by build scripts, to force `go mod` to see them as dependencies
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/client9/misspell/cmd/misspell"
|
||||||
|
_ "k8s.io/code-generator"
|
||||||
|
_ "sigs.k8s.io/mdtoc"
|
||||||
|
)
|
||||||
@@ -23,7 +23,7 @@ DESCHEDULER_ROOT=$(dirname "${BASH_SOURCE}")/..
|
|||||||
|
|
||||||
GO_VERSION=($(go version))
|
GO_VERSION=($(go version))
|
||||||
|
|
||||||
if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.2|go1.3|go1.4|go1.5|go1.6|go1.7|go1.8|go1.9|go1.10|go1.11|go1.12|go1.13') ]]; then
|
if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.17|go1.18') ]]; then
|
||||||
echo "Unknown go version '${GO_VERSION[2]}', skipping gofmt."
|
echo "Unknown go version '${GO_VERSION[2]}', skipping gofmt."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
25
hack/update-toc.sh
Executable file
25
hack/update-toc.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2021 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
|
||||||
|
|
||||||
|
go build -o "${OS_OUTPUT_BINPATH}/mdtoc" "sigs.k8s.io/mdtoc"
|
||||||
|
|
||||||
|
${OS_OUTPUT_BINPATH}/mdtoc --inplace README.md
|
||||||
21
hack/update-vendor.sh
Executable file
21
hack/update-vendor.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2020 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
|
||||||
|
|
||||||
|
go mod tidy
|
||||||
|
go mod vendor
|
||||||
36
hack/verify-conversions.sh
Executable file
36
hack/verify-conversions.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
|
||||||
|
DESCHEDULER_ROOT=$(dirname "${BASH_SOURCE}")/..
|
||||||
|
mkdir -p "${DESCHEDULER_ROOT}/_tmp"
|
||||||
|
_tmpdir="$(mktemp -d "${DESCHEDULER_ROOT}/_tmp/kube-verify.XXXXXX")"
|
||||||
|
|
||||||
|
_deschedulertmp="${_tmpdir}"
|
||||||
|
mkdir -p "${_deschedulertmp}"
|
||||||
|
|
||||||
|
git archive --format=tar --prefix=descheduler/ "$(git write-tree)" | (cd "${_deschedulertmp}" && tar xf -)
|
||||||
|
_deschedulertmp="${_deschedulertmp}/descheduler"
|
||||||
|
|
||||||
|
pushd "${_deschedulertmp}" > /dev/null 2>&1
|
||||||
|
go build -o "${OS_OUTPUT_BINPATH}/conversion-gen" "k8s.io/code-generator/cmd/conversion-gen"
|
||||||
|
|
||||||
|
${OS_OUTPUT_BINPATH}/conversion-gen \
|
||||||
|
--go-header-file "hack/boilerplate/boilerplate.go.txt" \
|
||||||
|
--input-dirs "./pkg/apis/componentconfig/v1alpha1,./pkg/api/v1alpha1" \
|
||||||
|
--output-file-base zz_generated.conversion
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
|
pushd "${DESCHEDULER_ROOT}" > /dev/null 2>&1
|
||||||
|
if ! _out="$(diff -Naupr pkg/ "${_deschedulertmp}/pkg/")"; then
|
||||||
|
echo "Generated output differs:" >&2
|
||||||
|
echo "${_out}" >&2
|
||||||
|
echo "Generated conversions verify failed. Please run ./hack/update-generated-conversions.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
|
echo "Generated conversions verified."
|
||||||
36
hack/verify-deep-copies.sh
Executable file
36
hack/verify-deep-copies.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
|
||||||
|
DESCHEDULER_ROOT=$(dirname "${BASH_SOURCE}")/..
|
||||||
|
mkdir -p "${DESCHEDULER_ROOT}/_tmp"
|
||||||
|
_tmpdir="$(mktemp -d "${DESCHEDULER_ROOT}/_tmp/kube-verify.XXXXXX")"
|
||||||
|
|
||||||
|
_deschedulertmp="${_tmpdir}"
|
||||||
|
mkdir -p "${_deschedulertmp}"
|
||||||
|
|
||||||
|
git archive --format=tar --prefix=descheduler/ "$(git write-tree)" | (cd "${_deschedulertmp}" && tar xf -)
|
||||||
|
_deschedulertmp="${_deschedulertmp}/descheduler"
|
||||||
|
|
||||||
|
pushd "${_deschedulertmp}" > /dev/null 2>&1
|
||||||
|
go build -o "${OS_OUTPUT_BINPATH}/deepcopy-gen" "k8s.io/code-generator/cmd/deepcopy-gen"
|
||||||
|
|
||||||
|
${OS_OUTPUT_BINPATH}/deepcopy-gen \
|
||||||
|
--go-header-file "hack/boilerplate/boilerplate.go.txt" \
|
||||||
|
--input-dirs "./pkg/apis/componentconfig,./pkg/apis/componentconfig/v1alpha1,./pkg/api,./pkg/api/v1alpha1" \
|
||||||
|
--output-file-base zz_generated.deepcopy
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
|
pushd "${DESCHEDULER_ROOT}" > /dev/null 2>&1
|
||||||
|
if ! _out="$(diff -Naupr pkg/ "${_deschedulertmp}/pkg/")"; then
|
||||||
|
echo "Generated deep-copies output differs:" >&2
|
||||||
|
echo "${_out}" >&2
|
||||||
|
echo "Generated deep-copies verify failed. Please run ./hack/update-generated-deep-copies.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
|
echo "Generated deep-copies verified."
|
||||||
35
hack/verify-defaulters.sh
Executable file
35
hack/verify-defaulters.sh
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
|
||||||
|
DESCHEDULER_ROOT=$(dirname "${BASH_SOURCE}")/..
|
||||||
|
_tmpdir="$(mktemp -d "${DESCHEDULER_ROOT}/_tmp/kube-verify.XXXXXX")"
|
||||||
|
|
||||||
|
_deschedulertmp="${_tmpdir}"
|
||||||
|
mkdir -p "${_deschedulertmp}"
|
||||||
|
|
||||||
|
git archive --format=tar --prefix=descheduler/ "$(git write-tree)" | (cd "${_deschedulertmp}" && tar xf -)
|
||||||
|
_deschedulertmp="${_deschedulertmp}/descheduler"
|
||||||
|
|
||||||
|
pushd "${_deschedulertmp}" > /dev/null 2>&1
|
||||||
|
go build -o "${OS_OUTPUT_BINPATH}/defaulter-gen" "k8s.io/code-generator/cmd/defaulter-gen"
|
||||||
|
|
||||||
|
${OS_OUTPUT_BINPATH}/defaulter-gen \
|
||||||
|
--go-header-file "hack/boilerplate/boilerplate.go.txt" \
|
||||||
|
--input-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api/v1alpha1" \
|
||||||
|
--extra-peer-dirs "${PRJ_PREFIX}/pkg/apis/componentconfig/v1alpha1,${PRJ_PREFIX}/pkg/api/v1alpha1" \
|
||||||
|
--output-file-base zz_generated.defaults
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
|
pushd "${DESCHEDULER_ROOT}" > /dev/null 2>&1
|
||||||
|
if ! _out="$(diff -Naupr pkg/ "${_deschedulertmp}/pkg/")"; then
|
||||||
|
echo "Generated defaulters output differs:" >&2
|
||||||
|
echo "${_out}" >&2
|
||||||
|
echo "Generated defaulters verify failed. Please run ./hack/update-generated-defaulters.sh"
|
||||||
|
fi
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
|
echo "Generated Defaulters verified."
|
||||||
@@ -23,7 +23,7 @@ DESCHEDULER_ROOT=$(dirname "${BASH_SOURCE}")/..
|
|||||||
|
|
||||||
GO_VERSION=($(go version))
|
GO_VERSION=($(go version))
|
||||||
|
|
||||||
if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.2|go1.3|go1.4|go1.5|go1.6|go1.7|go1.8|go1.9|go1.10|go1.11|go1.12|go1.13') ]]; then
|
if [[ -z $(echo "${GO_VERSION[2]}" | grep -E 'go1.17|go1.18') ]]; then
|
||||||
echo "Unknown go version '${GO_VERSION[2]}', skipping gofmt."
|
echo "Unknown go version '${GO_VERSION[2]}', skipping gofmt."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
19
hack/verify-govet.sh
Executable file
19
hack/verify-govet.sh
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2021 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
|
||||||
|
|
||||||
|
go vet ${OS_ROOT}/...
|
||||||
41
hack/verify-spelling.sh
Executable file
41
hack/verify-spelling.sh
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Copyright 2018 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# This script checks commonly misspelled English words in all files in the
|
||||||
|
# working directory by client9/misspell package.
|
||||||
|
# Usage: `hack/verify-spelling.sh`.
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
|
||||||
|
export KUBE_ROOT
|
||||||
|
source "${KUBE_ROOT}/hack/lib/init.sh"
|
||||||
|
|
||||||
|
# Ensure that we find the binaries we build before anything else.
|
||||||
|
export GOBIN="${OS_OUTPUT_BINPATH}"
|
||||||
|
PATH="${GOBIN}:${PATH}"
|
||||||
|
|
||||||
|
# Install tools we need
|
||||||
|
pushd "${KUBE_ROOT}" >/dev/null
|
||||||
|
GO111MODULE=on go install github.com/client9/misspell/cmd/misspell
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
# Spell checking
|
||||||
|
# All the skipping files are defined in hack/.spelling_failures
|
||||||
|
skipping_file="${KUBE_ROOT}/hack/.spelling_failures"
|
||||||
|
failing_packages=$(sed "s| | -e |g" "${skipping_file}")
|
||||||
|
git ls-files | grep -v -e "${failing_packages}" | xargs misspell -i "Creater,creater,ect" -error -o stderr
|
||||||
29
hack/verify-toc.sh
Executable file
29
hack/verify-toc.sh
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2021 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
|
||||||
|
|
||||||
|
go build -o "${OS_OUTPUT_BINPATH}/mdtoc" "sigs.k8s.io/mdtoc"
|
||||||
|
|
||||||
|
if ! ${OS_OUTPUT_BINPATH}/mdtoc --inplace --dryrun README.md
|
||||||
|
then
|
||||||
|
echo "ERROR: Changes detected to table of contents. Run ./hack/update-toc.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
88
hack/verify-vendor.sh
Executable file
88
hack/verify-vendor.sh
Executable file
@@ -0,0 +1,88 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2020 The Kubernetes Authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# This is mostly copied from the hack/verify-vendor.sh script located in k8s.io/kubernetes
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
|
||||||
|
|
||||||
|
DESCHEDULER_ROOT=$(dirname "${BASH_SOURCE}")/..
|
||||||
|
|
||||||
|
mkdir -p "${DESCHEDULER_ROOT}/_tmp"
|
||||||
|
_tmpdir="$(mktemp -d "${DESCHEDULER_ROOT}/_tmp/kube-vendor.XXXXXX")"
|
||||||
|
|
||||||
|
if [[ -z ${KEEP_TMP:-} ]]; then
|
||||||
|
KEEP_TMP=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
function cleanup {
|
||||||
|
# make go module dirs writeable
|
||||||
|
chmod -R +w "${_tmpdir}"
|
||||||
|
if [ "${KEEP_TMP}" == "true" ]; then
|
||||||
|
echo "Leaving ${_tmpdir} for you to examine or copy. Please delete it manually when finished. (rm -rf ${_tmpdir})"
|
||||||
|
else
|
||||||
|
echo "Removing ${_tmpdir}"
|
||||||
|
rm -rf "${_tmpdir}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap "cleanup" EXIT
|
||||||
|
|
||||||
|
_deschedulertmp="${_tmpdir}"
|
||||||
|
mkdir -p "${_deschedulertmp}"
|
||||||
|
|
||||||
|
git archive --format=tar --prefix=descheduler/ "$(git write-tree)" | (cd "${_deschedulertmp}" && tar xf -)
|
||||||
|
_deschedulertmp="${_deschedulertmp}/descheduler"
|
||||||
|
|
||||||
|
pushd "${_deschedulertmp}" > /dev/null 2>&1
|
||||||
|
# Destroy deps in the copy of the kube tree
|
||||||
|
rm -rf ./vendor
|
||||||
|
|
||||||
|
# Recreate the vendor tree using the nice clean set we just downloaded
|
||||||
|
hack/update-vendor.sh
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
|
ret=0
|
||||||
|
|
||||||
|
pushd "${DESCHEDULER_ROOT}" > /dev/null 2>&1
|
||||||
|
# Test for diffs
|
||||||
|
if ! _out="$(diff -Naupr --ignore-matching-lines='^\s*\"GoVersion\":' go.mod "${_deschedulertmp}/go.mod")"; then
|
||||||
|
echo "Your go.mod file is different:" >&2
|
||||||
|
echo "${_out}" >&2
|
||||||
|
echo "Vendor Verify failed." >&2
|
||||||
|
echo "If you're seeing this locally, run the below command to fix your go.mod:" >&2
|
||||||
|
echo "hack/update-vendor.sh" >&2
|
||||||
|
ret=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! _out="$(diff -Naupr -x "BUILD" -x "AUTHORS*" -x "CONTRIBUTORS*" vendor "${_deschedulertmp}/vendor")"; then
|
||||||
|
echo "Your vendored results are different:" >&2
|
||||||
|
echo "${_out}" >&2
|
||||||
|
echo "Vendor Verify failed." >&2
|
||||||
|
echo "${_out}" > vendordiff.patch
|
||||||
|
echo "If you're seeing this locally, run the below command to fix your directories:" >&2
|
||||||
|
echo "hack/update-vendor.sh" >&2
|
||||||
|
ret=1
|
||||||
|
fi
|
||||||
|
popd > /dev/null 2>&1
|
||||||
|
|
||||||
|
if [[ ${ret} -gt 0 ]]; then
|
||||||
|
exit ${ret}
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Vendor Verified."
|
||||||
6
kubernetes/base/kustomization.yaml
Normal file
6
kubernetes/base/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- configmap.yaml
|
||||||
|
- rbac.yaml
|
||||||
@@ -3,7 +3,6 @@ kind: ClusterRole
|
|||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
metadata:
|
metadata:
|
||||||
name: descheduler-cluster-role
|
name: descheduler-cluster-role
|
||||||
namespace: kube-system
|
|
||||||
rules:
|
rules:
|
||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["events"]
|
resources: ["events"]
|
||||||
@@ -11,12 +10,25 @@ rules:
|
|||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["nodes"]
|
resources: ["nodes"]
|
||||||
verbs: ["get", "watch", "list"]
|
verbs: ["get", "watch", "list"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["namespaces"]
|
||||||
|
verbs: ["get", "watch", "list"]
|
||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["pods"]
|
resources: ["pods"]
|
||||||
verbs: ["get", "watch", "list", "delete"]
|
verbs: ["get", "watch", "list", "delete"]
|
||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["pods/eviction"]
|
resources: ["pods/eviction"]
|
||||||
verbs: ["create"]
|
verbs: ["create"]
|
||||||
|
- apiGroups: ["scheduling.k8s.io"]
|
||||||
|
resources: ["priorityclasses"]
|
||||||
|
verbs: ["get", "watch", "list"]
|
||||||
|
- apiGroups: ["coordination.k8s.io"]
|
||||||
|
resources: ["leases"]
|
||||||
|
verbs: ["create"]
|
||||||
|
- apiGroups: ["coordination.k8s.io"]
|
||||||
|
resources: ["leases"]
|
||||||
|
resourceNames: ["descheduler"]
|
||||||
|
verbs: ["get", "patch", "delete"]
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
@@ -28,7 +40,6 @@ apiVersion: rbac.authorization.k8s.io/v1
|
|||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
metadata:
|
metadata:
|
||||||
name: descheduler-cluster-role-binding
|
name: descheduler-cluster-role-binding
|
||||||
namespace: kube-system
|
|
||||||
roleRef:
|
roleRef:
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
@@ -37,4 +48,3 @@ subjects:
|
|||||||
- name: descheduler-sa
|
- name: descheduler-sa
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
apiVersion: batch/v1beta1
|
apiVersion: batch/v1 # for k8s version < 1.21.0, use batch/v1beta1
|
||||||
kind: CronJob
|
kind: CronJob
|
||||||
metadata:
|
metadata:
|
||||||
name: descheduler-cronjob
|
name: descheduler-cronjob
|
||||||
@@ -16,7 +16,7 @@ spec:
|
|||||||
priorityClassName: system-cluster-critical
|
priorityClassName: system-cluster-critical
|
||||||
containers:
|
containers:
|
||||||
- name: descheduler
|
- name: descheduler
|
||||||
image: us.gcr.io/k8s-artifacts-prod/descheduler:v0.10.0
|
image: k8s.gcr.io/descheduler/descheduler:v0.24.1
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /policy-dir
|
- mountPath: /policy-dir
|
||||||
name: policy-volume
|
name: policy-volume
|
||||||
@@ -27,6 +27,26 @@ spec:
|
|||||||
- "/policy-dir/policy.yaml"
|
- "/policy-dir/policy.yaml"
|
||||||
- "--v"
|
- "--v"
|
||||||
- "3"
|
- "3"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "256Mi"
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 10258
|
||||||
|
scheme: HTTPS
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
privileged: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
restartPolicy: "Never"
|
restartPolicy: "Never"
|
||||||
serviceAccountName: descheduler-sa
|
serviceAccountName: descheduler-sa
|
||||||
volumes:
|
volumes:
|
||||||
6
kubernetes/cronjob/kustomization.yaml
Normal file
6
kubernetes/cronjob/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
- cronjob.yaml
|
||||||
62
kubernetes/deployment/deployment.yaml
Normal file
62
kubernetes/deployment/deployment.yaml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: descheduler
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app: descheduler
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: descheduler
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: descheduler
|
||||||
|
spec:
|
||||||
|
priorityClassName: system-cluster-critical
|
||||||
|
serviceAccountName: descheduler-sa
|
||||||
|
containers:
|
||||||
|
- name: descheduler
|
||||||
|
image: k8s.gcr.io/descheduler/descheduler:v0.24.1
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- "/bin/descheduler"
|
||||||
|
args:
|
||||||
|
- "--policy-config-file"
|
||||||
|
- "/policy-dir/policy.yaml"
|
||||||
|
- "--descheduling-interval"
|
||||||
|
- "5m"
|
||||||
|
- "--v"
|
||||||
|
- "3"
|
||||||
|
ports:
|
||||||
|
- containerPort: 10258
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 10258
|
||||||
|
scheme: HTTPS
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 256Mi
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
privileged: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /policy-dir
|
||||||
|
name: policy-volume
|
||||||
|
volumes:
|
||||||
|
- name: policy-volume
|
||||||
|
configMap:
|
||||||
|
name: descheduler-policy-configmap
|
||||||
6
kubernetes/deployment/kustomization.yaml
Normal file
6
kubernetes/deployment/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
- deployment.yaml
|
||||||
@@ -14,7 +14,7 @@ spec:
|
|||||||
priorityClassName: system-cluster-critical
|
priorityClassName: system-cluster-critical
|
||||||
containers:
|
containers:
|
||||||
- name: descheduler
|
- name: descheduler
|
||||||
image: us.gcr.io/k8s-artifacts-prod/descheduler:v0.10.0
|
image: k8s.gcr.io/descheduler/descheduler:v0.24.1
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: /policy-dir
|
- mountPath: /policy-dir
|
||||||
name: policy-volume
|
name: policy-volume
|
||||||
@@ -25,6 +25,26 @@ spec:
|
|||||||
- "/policy-dir/policy.yaml"
|
- "/policy-dir/policy.yaml"
|
||||||
- "--v"
|
- "--v"
|
||||||
- "3"
|
- "3"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "256Mi"
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 10258
|
||||||
|
scheme: HTTPS
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
privileged: false
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
restartPolicy: "Never"
|
restartPolicy: "Never"
|
||||||
serviceAccountName: descheduler-sa
|
serviceAccountName: descheduler-sa
|
||||||
volumes:
|
volumes:
|
||||||
6
kubernetes/job/kustomization.yaml
Normal file
6
kubernetes/job/kustomization.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
- job.yaml
|
||||||
72
metrics/metrics.go
Normal file
72
metrics/metrics.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/component-base/metrics"
|
||||||
|
"k8s.io/component-base/metrics/legacyregistry"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DeschedulerSubsystem - subsystem name used by descheduler
|
||||||
|
DeschedulerSubsystem = "descheduler"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
PodsEvicted = metrics.NewCounterVec(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Subsystem: DeschedulerSubsystem,
|
||||||
|
Name: "pods_evicted",
|
||||||
|
Help: "Number of evicted pods, by the result, by the strategy, by the namespace, by the node name. 'error' result means a pod could not be evicted",
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
}, []string{"result", "strategy", "namespace", "node"})
|
||||||
|
|
||||||
|
buildInfo = metrics.NewGauge(
|
||||||
|
&metrics.GaugeOpts{
|
||||||
|
Subsystem: DeschedulerSubsystem,
|
||||||
|
Name: "build_info",
|
||||||
|
Help: "Build info about descheduler, including Go version, Descheduler version, Git SHA, Git branch",
|
||||||
|
ConstLabels: map[string]string{"GoVersion": version.Get().GoVersion, "DeschedulerVersion": version.Get().GitVersion, "GitBranch": version.Get().GitBranch, "GitSha1": version.Get().GitSha1},
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
metricsList = []metrics.Registerable{
|
||||||
|
PodsEvicted,
|
||||||
|
buildInfo,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var registerMetrics sync.Once
|
||||||
|
|
||||||
|
// Register all metrics.
|
||||||
|
func Register() {
|
||||||
|
// Register the metrics.
|
||||||
|
registerMetrics.Do(func() {
|
||||||
|
RegisterMetrics(metricsList...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterMetrics registers a list of metrics.
|
||||||
|
func RegisterMetrics(extraMetrics ...metrics.Registerable) {
|
||||||
|
for _, metric := range extraMetrics {
|
||||||
|
legacyregistry.MustRegister(metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,8 +19,6 @@ package api
|
|||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/scheme"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -35,12 +33,6 @@ const GroupName = "descheduler"
|
|||||||
// SchemeGroupVersion is group version used to register these objects
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||||
|
|
||||||
func init() {
|
|
||||||
if err := addKnownTypes(scheme.Scheme); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
||||||
func Kind(kind string) schema.GroupKind {
|
func Kind(kind string) schema.GroupKind {
|
||||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +28,27 @@ type DeschedulerPolicy struct {
|
|||||||
|
|
||||||
// Strategies
|
// Strategies
|
||||||
Strategies StrategyList
|
Strategies StrategyList
|
||||||
|
|
||||||
|
// NodeSelector for a set of nodes to operate over
|
||||||
|
NodeSelector *string
|
||||||
|
|
||||||
|
// EvictFailedBarePods allows pods without ownerReferences and in failed phase to be evicted.
|
||||||
|
EvictFailedBarePods *bool
|
||||||
|
|
||||||
|
// EvictLocalStoragePods allows pods using local storage to be evicted.
|
||||||
|
EvictLocalStoragePods *bool
|
||||||
|
|
||||||
|
// EvictSystemCriticalPods allows eviction of pods of any priority (including Kubernetes system pods)
|
||||||
|
EvictSystemCriticalPods *bool
|
||||||
|
|
||||||
|
// IgnorePVCPods prevents pods with PVCs from being evicted.
|
||||||
|
IgnorePVCPods *bool
|
||||||
|
|
||||||
|
// MaxNoOfPodsToEvictPerNode restricts maximum of pods to be evicted per node.
|
||||||
|
MaxNoOfPodsToEvictPerNode *uint
|
||||||
|
|
||||||
|
// MaxNoOfPodsToEvictPerNamespace restricts maximum of pods to be evicted per namespace.
|
||||||
|
MaxNoOfPodsToEvictPerNamespace *uint
|
||||||
}
|
}
|
||||||
|
|
||||||
type StrategyName string
|
type StrategyName string
|
||||||
@@ -41,20 +62,63 @@ type DeschedulerStrategy struct {
|
|||||||
Weight int
|
Weight int
|
||||||
|
|
||||||
// Strategy parameters
|
// Strategy parameters
|
||||||
Params StrategyParameters
|
Params *StrategyParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only one of its members may be specified
|
// Namespaces carries a list of included/excluded namespaces
|
||||||
|
// for which a given strategy is applicable
|
||||||
|
type Namespaces struct {
|
||||||
|
Include []string
|
||||||
|
Exclude []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Besides Namespaces only one of its members may be specified
|
||||||
|
// TODO(jchaloup): move Namespaces ThresholdPriority and ThresholdPriorityClassName to individual strategies
|
||||||
|
// once the policy version is bumped to v1alpha2
|
||||||
type StrategyParameters struct {
|
type StrategyParameters struct {
|
||||||
NodeResourceUtilizationThresholds NodeResourceUtilizationThresholds
|
NodeResourceUtilizationThresholds *NodeResourceUtilizationThresholds
|
||||||
NodeAffinityType []string
|
NodeAffinityType []string
|
||||||
|
PodsHavingTooManyRestarts *PodsHavingTooManyRestarts
|
||||||
|
PodLifeTime *PodLifeTime
|
||||||
|
RemoveDuplicates *RemoveDuplicates
|
||||||
|
FailedPods *FailedPods
|
||||||
|
IncludeSoftConstraints bool
|
||||||
|
Namespaces *Namespaces
|
||||||
|
ThresholdPriority *int32
|
||||||
|
ThresholdPriorityClassName string
|
||||||
|
LabelSelector *metav1.LabelSelector
|
||||||
|
NodeFit bool
|
||||||
|
IncludePreferNoSchedule bool
|
||||||
|
ExcludedTaints []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Percentage float64
|
type Percentage float64
|
||||||
type ResourceThresholds map[v1.ResourceName]Percentage
|
type ResourceThresholds map[v1.ResourceName]Percentage
|
||||||
|
|
||||||
type NodeResourceUtilizationThresholds struct {
|
type NodeResourceUtilizationThresholds struct {
|
||||||
Thresholds ResourceThresholds
|
UseDeviationThresholds bool
|
||||||
TargetThresholds ResourceThresholds
|
Thresholds ResourceThresholds
|
||||||
NumberOfNodes int
|
TargetThresholds ResourceThresholds
|
||||||
|
NumberOfNodes int
|
||||||
|
}
|
||||||
|
|
||||||
|
type PodsHavingTooManyRestarts struct {
|
||||||
|
PodRestartThreshold int32
|
||||||
|
IncludingInitContainers bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoveDuplicates struct {
|
||||||
|
ExcludeOwnerKinds []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PodLifeTime struct {
|
||||||
|
MaxPodLifeTimeSeconds *uint
|
||||||
|
PodStatusPhases []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FailedPods struct {
|
||||||
|
ExcludeOwnerKinds []string
|
||||||
|
MinPodLifetimeSeconds *uint
|
||||||
|
Reasons []string
|
||||||
|
IncludingInitContainers bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,6 +28,27 @@ type DeschedulerPolicy struct {
|
|||||||
|
|
||||||
// Strategies
|
// Strategies
|
||||||
Strategies StrategyList `json:"strategies,omitempty"`
|
Strategies StrategyList `json:"strategies,omitempty"`
|
||||||
|
|
||||||
|
// NodeSelector for a set of nodes to operate over
|
||||||
|
NodeSelector *string `json:"nodeSelector,omitempty"`
|
||||||
|
|
||||||
|
// EvictFailedBarePods allows pods without ownerReferences and in failed phase to be evicted.
|
||||||
|
EvictFailedBarePods *bool `json:"evictFailedBarePods,omitempty"`
|
||||||
|
|
||||||
|
// EvictLocalStoragePods allows pods using local storage to be evicted.
|
||||||
|
EvictLocalStoragePods *bool `json:"evictLocalStoragePods,omitempty"`
|
||||||
|
|
||||||
|
// EvictSystemCriticalPods allows eviction of pods of any priority (including Kubernetes system pods)
|
||||||
|
EvictSystemCriticalPods *bool `json:"evictSystemCriticalPods,omitempty"`
|
||||||
|
|
||||||
|
// IgnorePVCPods prevents pods with PVCs from being evicted.
|
||||||
|
IgnorePVCPods *bool `json:"ignorePvcPods,omitempty"`
|
||||||
|
|
||||||
|
// MaxNoOfPodsToEvictPerNode restricts maximum of pods to be evicted per node.
|
||||||
|
MaxNoOfPodsToEvictPerNode *int `json:"maxNoOfPodsToEvictPerNode,omitempty"`
|
||||||
|
|
||||||
|
// MaxNoOfPodsToEvictPerNamespace restricts maximum of pods to be evicted per namespace.
|
||||||
|
MaxNoOfPodsToEvictPerNamespace *int `json:"maxNoOfPodsToEvictPerNamespace,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StrategyName string
|
type StrategyName string
|
||||||
@@ -41,20 +62,61 @@ type DeschedulerStrategy struct {
|
|||||||
Weight int `json:"weight,omitempty"`
|
Weight int `json:"weight,omitempty"`
|
||||||
|
|
||||||
// Strategy parameters
|
// Strategy parameters
|
||||||
Params StrategyParameters `json:"params,omitempty"`
|
Params *StrategyParameters `json:"params,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only one of its members may be specified
|
// Namespaces carries a list of included/excluded namespaces
|
||||||
|
// for which a given strategy is applicable.
|
||||||
|
type Namespaces struct {
|
||||||
|
Include []string `json:"include"`
|
||||||
|
Exclude []string `json:"exclude"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Besides Namespaces ThresholdPriority and ThresholdPriorityClassName only one of its members may be specified
|
||||||
type StrategyParameters struct {
|
type StrategyParameters struct {
|
||||||
NodeResourceUtilizationThresholds NodeResourceUtilizationThresholds `json:"nodeResourceUtilizationThresholds,omitempty"`
|
NodeResourceUtilizationThresholds *NodeResourceUtilizationThresholds `json:"nodeResourceUtilizationThresholds,omitempty"`
|
||||||
NodeAffinityType []string `json:"nodeAffinityType,omitempty"`
|
NodeAffinityType []string `json:"nodeAffinityType,omitempty"`
|
||||||
|
PodsHavingTooManyRestarts *PodsHavingTooManyRestarts `json:"podsHavingTooManyRestarts,omitempty"`
|
||||||
|
PodLifeTime *PodLifeTime `json:"podLifeTime,omitempty"`
|
||||||
|
RemoveDuplicates *RemoveDuplicates `json:"removeDuplicates,omitempty"`
|
||||||
|
FailedPods *FailedPods `json:"failedPods,omitempty"`
|
||||||
|
IncludeSoftConstraints bool `json:"includeSoftConstraints"`
|
||||||
|
Namespaces *Namespaces `json:"namespaces"`
|
||||||
|
ThresholdPriority *int32 `json:"thresholdPriority"`
|
||||||
|
ThresholdPriorityClassName string `json:"thresholdPriorityClassName"`
|
||||||
|
LabelSelector *metav1.LabelSelector `json:"labelSelector"`
|
||||||
|
NodeFit bool `json:"nodeFit"`
|
||||||
|
IncludePreferNoSchedule bool `json:"includePreferNoSchedule"`
|
||||||
|
ExcludedTaints []string `json:"excludedTaints,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Percentage float64
|
type Percentage float64
|
||||||
type ResourceThresholds map[v1.ResourceName]Percentage
|
type ResourceThresholds map[v1.ResourceName]Percentage
|
||||||
|
|
||||||
type NodeResourceUtilizationThresholds struct {
|
type NodeResourceUtilizationThresholds struct {
|
||||||
Thresholds ResourceThresholds `json:"thresholds,omitempty"`
|
UseDeviationThresholds bool `json:"useDeviationThresholds,omitempty"`
|
||||||
TargetThresholds ResourceThresholds `json:"targetThresholds,omitempty"`
|
Thresholds ResourceThresholds `json:"thresholds,omitempty"`
|
||||||
NumberOfNodes int `json:"numberOfNodes,omitempty"`
|
TargetThresholds ResourceThresholds `json:"targetThresholds,omitempty"`
|
||||||
|
NumberOfNodes int `json:"numberOfNodes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PodsHavingTooManyRestarts struct {
|
||||||
|
PodRestartThreshold int32 `json:"podRestartThreshold,omitempty"`
|
||||||
|
IncludingInitContainers bool `json:"includingInitContainers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoveDuplicates struct {
|
||||||
|
ExcludeOwnerKinds []string `json:"excludeOwnerKinds,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PodLifeTime struct {
|
||||||
|
MaxPodLifeTimeSeconds *uint `json:"maxPodLifeTimeSeconds,omitempty"`
|
||||||
|
PodStatusPhases []string `json:"podStatusPhases,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FailedPods struct {
|
||||||
|
ExcludeOwnerKinds []string `json:"excludeOwnerKinds,omitempty"`
|
||||||
|
MinPodLifetimeSeconds *uint `json:"minPodLifetimeSeconds,omitempty"`
|
||||||
|
Reasons []string `json:"reasons,omitempty"`
|
||||||
|
IncludingInitContainers bool `json:"includingInitContainers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Kubernetes Authors.
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -23,6 +24,7 @@ package v1alpha1
|
|||||||
import (
|
import (
|
||||||
unsafe "unsafe"
|
unsafe "unsafe"
|
||||||
|
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
api "sigs.k8s.io/descheduler/pkg/api"
|
api "sigs.k8s.io/descheduler/pkg/api"
|
||||||
@@ -55,6 +57,26 @@ func RegisterConversions(s *runtime.Scheme) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*FailedPods)(nil), (*api.FailedPods)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_FailedPods_To_api_FailedPods(a.(*FailedPods), b.(*api.FailedPods), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*api.FailedPods)(nil), (*FailedPods)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_api_FailedPods_To_v1alpha1_FailedPods(a.(*api.FailedPods), b.(*FailedPods), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*Namespaces)(nil), (*api.Namespaces)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_Namespaces_To_api_Namespaces(a.(*Namespaces), b.(*api.Namespaces), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*api.Namespaces)(nil), (*Namespaces)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_api_Namespaces_To_v1alpha1_Namespaces(a.(*api.Namespaces), b.(*Namespaces), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := s.AddGeneratedConversionFunc((*NodeResourceUtilizationThresholds)(nil), (*api.NodeResourceUtilizationThresholds)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
if err := s.AddGeneratedConversionFunc((*NodeResourceUtilizationThresholds)(nil), (*api.NodeResourceUtilizationThresholds)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
return Convert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(a.(*NodeResourceUtilizationThresholds), b.(*api.NodeResourceUtilizationThresholds), scope)
|
return Convert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(a.(*NodeResourceUtilizationThresholds), b.(*api.NodeResourceUtilizationThresholds), scope)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -65,6 +87,36 @@ func RegisterConversions(s *runtime.Scheme) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*PodLifeTime)(nil), (*api.PodLifeTime)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_PodLifeTime_To_api_PodLifeTime(a.(*PodLifeTime), b.(*api.PodLifeTime), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*api.PodLifeTime)(nil), (*PodLifeTime)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_api_PodLifeTime_To_v1alpha1_PodLifeTime(a.(*api.PodLifeTime), b.(*PodLifeTime), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*PodsHavingTooManyRestarts)(nil), (*api.PodsHavingTooManyRestarts)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(a.(*PodsHavingTooManyRestarts), b.(*api.PodsHavingTooManyRestarts), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*api.PodsHavingTooManyRestarts)(nil), (*PodsHavingTooManyRestarts)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts(a.(*api.PodsHavingTooManyRestarts), b.(*PodsHavingTooManyRestarts), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*RemoveDuplicates)(nil), (*api.RemoveDuplicates)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates(a.(*RemoveDuplicates), b.(*api.RemoveDuplicates), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*api.RemoveDuplicates)(nil), (*RemoveDuplicates)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates(a.(*api.RemoveDuplicates), b.(*RemoveDuplicates), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := s.AddGeneratedConversionFunc((*StrategyParameters)(nil), (*api.StrategyParameters)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
if err := s.AddGeneratedConversionFunc((*StrategyParameters)(nil), (*api.StrategyParameters)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
return Convert_v1alpha1_StrategyParameters_To_api_StrategyParameters(a.(*StrategyParameters), b.(*api.StrategyParameters), scope)
|
return Convert_v1alpha1_StrategyParameters_To_api_StrategyParameters(a.(*StrategyParameters), b.(*api.StrategyParameters), scope)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -80,6 +132,25 @@ func RegisterConversions(s *runtime.Scheme) error {
|
|||||||
|
|
||||||
func autoConvert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy(in *DeschedulerPolicy, out *api.DeschedulerPolicy, s conversion.Scope) error {
|
func autoConvert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy(in *DeschedulerPolicy, out *api.DeschedulerPolicy, s conversion.Scope) error {
|
||||||
out.Strategies = *(*api.StrategyList)(unsafe.Pointer(&in.Strategies))
|
out.Strategies = *(*api.StrategyList)(unsafe.Pointer(&in.Strategies))
|
||||||
|
out.NodeSelector = (*string)(unsafe.Pointer(in.NodeSelector))
|
||||||
|
out.EvictFailedBarePods = (*bool)(unsafe.Pointer(in.EvictFailedBarePods))
|
||||||
|
out.EvictLocalStoragePods = (*bool)(unsafe.Pointer(in.EvictLocalStoragePods))
|
||||||
|
out.EvictSystemCriticalPods = (*bool)(unsafe.Pointer(in.EvictSystemCriticalPods))
|
||||||
|
out.IgnorePVCPods = (*bool)(unsafe.Pointer(in.IgnorePVCPods))
|
||||||
|
if in.MaxNoOfPodsToEvictPerNode != nil {
|
||||||
|
in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode
|
||||||
|
*out = new(uint)
|
||||||
|
**out = uint(**in)
|
||||||
|
} else {
|
||||||
|
out.MaxNoOfPodsToEvictPerNode = nil
|
||||||
|
}
|
||||||
|
if in.MaxNoOfPodsToEvictPerNamespace != nil {
|
||||||
|
in, out := &in.MaxNoOfPodsToEvictPerNamespace, &out.MaxNoOfPodsToEvictPerNamespace
|
||||||
|
*out = new(uint)
|
||||||
|
**out = uint(**in)
|
||||||
|
} else {
|
||||||
|
out.MaxNoOfPodsToEvictPerNamespace = nil
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +161,25 @@ func Convert_v1alpha1_DeschedulerPolicy_To_api_DeschedulerPolicy(in *Descheduler
|
|||||||
|
|
||||||
func autoConvert_api_DeschedulerPolicy_To_v1alpha1_DeschedulerPolicy(in *api.DeschedulerPolicy, out *DeschedulerPolicy, s conversion.Scope) error {
|
func autoConvert_api_DeschedulerPolicy_To_v1alpha1_DeschedulerPolicy(in *api.DeschedulerPolicy, out *DeschedulerPolicy, s conversion.Scope) error {
|
||||||
out.Strategies = *(*StrategyList)(unsafe.Pointer(&in.Strategies))
|
out.Strategies = *(*StrategyList)(unsafe.Pointer(&in.Strategies))
|
||||||
|
out.NodeSelector = (*string)(unsafe.Pointer(in.NodeSelector))
|
||||||
|
out.EvictFailedBarePods = (*bool)(unsafe.Pointer(in.EvictFailedBarePods))
|
||||||
|
out.EvictLocalStoragePods = (*bool)(unsafe.Pointer(in.EvictLocalStoragePods))
|
||||||
|
out.EvictSystemCriticalPods = (*bool)(unsafe.Pointer(in.EvictSystemCriticalPods))
|
||||||
|
out.IgnorePVCPods = (*bool)(unsafe.Pointer(in.IgnorePVCPods))
|
||||||
|
if in.MaxNoOfPodsToEvictPerNode != nil {
|
||||||
|
in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode
|
||||||
|
*out = new(int)
|
||||||
|
**out = int(**in)
|
||||||
|
} else {
|
||||||
|
out.MaxNoOfPodsToEvictPerNode = nil
|
||||||
|
}
|
||||||
|
if in.MaxNoOfPodsToEvictPerNamespace != nil {
|
||||||
|
in, out := &in.MaxNoOfPodsToEvictPerNamespace, &out.MaxNoOfPodsToEvictPerNamespace
|
||||||
|
*out = new(int)
|
||||||
|
**out = int(**in)
|
||||||
|
} else {
|
||||||
|
out.MaxNoOfPodsToEvictPerNamespace = nil
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,9 +191,7 @@ func Convert_api_DeschedulerPolicy_To_v1alpha1_DeschedulerPolicy(in *api.Desched
|
|||||||
func autoConvert_v1alpha1_DeschedulerStrategy_To_api_DeschedulerStrategy(in *DeschedulerStrategy, out *api.DeschedulerStrategy, s conversion.Scope) error {
|
func autoConvert_v1alpha1_DeschedulerStrategy_To_api_DeschedulerStrategy(in *DeschedulerStrategy, out *api.DeschedulerStrategy, s conversion.Scope) error {
|
||||||
out.Enabled = in.Enabled
|
out.Enabled = in.Enabled
|
||||||
out.Weight = in.Weight
|
out.Weight = in.Weight
|
||||||
if err := Convert_v1alpha1_StrategyParameters_To_api_StrategyParameters(&in.Params, &out.Params, s); err != nil {
|
out.Params = (*api.StrategyParameters)(unsafe.Pointer(in.Params))
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +203,7 @@ func Convert_v1alpha1_DeschedulerStrategy_To_api_DeschedulerStrategy(in *Desched
|
|||||||
func autoConvert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in *api.DeschedulerStrategy, out *DeschedulerStrategy, s conversion.Scope) error {
|
func autoConvert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in *api.DeschedulerStrategy, out *DeschedulerStrategy, s conversion.Scope) error {
|
||||||
out.Enabled = in.Enabled
|
out.Enabled = in.Enabled
|
||||||
out.Weight = in.Weight
|
out.Weight = in.Weight
|
||||||
if err := Convert_api_StrategyParameters_To_v1alpha1_StrategyParameters(&in.Params, &out.Params, s); err != nil {
|
out.Params = (*StrategyParameters)(unsafe.Pointer(in.Params))
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +212,56 @@ func Convert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in *api.Des
|
|||||||
return autoConvert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in, out, s)
|
return autoConvert_api_DeschedulerStrategy_To_v1alpha1_DeschedulerStrategy(in, out, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_FailedPods_To_api_FailedPods(in *FailedPods, out *api.FailedPods, s conversion.Scope) error {
|
||||||
|
out.ExcludeOwnerKinds = *(*[]string)(unsafe.Pointer(&in.ExcludeOwnerKinds))
|
||||||
|
out.MinPodLifetimeSeconds = (*uint)(unsafe.Pointer(in.MinPodLifetimeSeconds))
|
||||||
|
out.Reasons = *(*[]string)(unsafe.Pointer(&in.Reasons))
|
||||||
|
out.IncludingInitContainers = in.IncludingInitContainers
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_FailedPods_To_api_FailedPods is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_FailedPods_To_api_FailedPods(in *FailedPods, out *api.FailedPods, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_FailedPods_To_api_FailedPods(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_api_FailedPods_To_v1alpha1_FailedPods(in *api.FailedPods, out *FailedPods, s conversion.Scope) error {
|
||||||
|
out.ExcludeOwnerKinds = *(*[]string)(unsafe.Pointer(&in.ExcludeOwnerKinds))
|
||||||
|
out.MinPodLifetimeSeconds = (*uint)(unsafe.Pointer(in.MinPodLifetimeSeconds))
|
||||||
|
out.Reasons = *(*[]string)(unsafe.Pointer(&in.Reasons))
|
||||||
|
out.IncludingInitContainers = in.IncludingInitContainers
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_api_FailedPods_To_v1alpha1_FailedPods is an autogenerated conversion function.
|
||||||
|
func Convert_api_FailedPods_To_v1alpha1_FailedPods(in *api.FailedPods, out *FailedPods, s conversion.Scope) error {
|
||||||
|
return autoConvert_api_FailedPods_To_v1alpha1_FailedPods(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_Namespaces_To_api_Namespaces(in *Namespaces, out *api.Namespaces, s conversion.Scope) error {
|
||||||
|
out.Include = *(*[]string)(unsafe.Pointer(&in.Include))
|
||||||
|
out.Exclude = *(*[]string)(unsafe.Pointer(&in.Exclude))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_Namespaces_To_api_Namespaces is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_Namespaces_To_api_Namespaces(in *Namespaces, out *api.Namespaces, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_Namespaces_To_api_Namespaces(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_api_Namespaces_To_v1alpha1_Namespaces(in *api.Namespaces, out *Namespaces, s conversion.Scope) error {
|
||||||
|
out.Include = *(*[]string)(unsafe.Pointer(&in.Include))
|
||||||
|
out.Exclude = *(*[]string)(unsafe.Pointer(&in.Exclude))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_api_Namespaces_To_v1alpha1_Namespaces is an autogenerated conversion function.
|
||||||
|
func Convert_api_Namespaces_To_v1alpha1_Namespaces(in *api.Namespaces, out *Namespaces, s conversion.Scope) error {
|
||||||
|
return autoConvert_api_Namespaces_To_v1alpha1_Namespaces(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
func autoConvert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(in *NodeResourceUtilizationThresholds, out *api.NodeResourceUtilizationThresholds, s conversion.Scope) error {
|
func autoConvert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(in *NodeResourceUtilizationThresholds, out *api.NodeResourceUtilizationThresholds, s conversion.Scope) error {
|
||||||
|
out.UseDeviationThresholds = in.UseDeviationThresholds
|
||||||
out.Thresholds = *(*api.ResourceThresholds)(unsafe.Pointer(&in.Thresholds))
|
out.Thresholds = *(*api.ResourceThresholds)(unsafe.Pointer(&in.Thresholds))
|
||||||
out.TargetThresholds = *(*api.ResourceThresholds)(unsafe.Pointer(&in.TargetThresholds))
|
out.TargetThresholds = *(*api.ResourceThresholds)(unsafe.Pointer(&in.TargetThresholds))
|
||||||
out.NumberOfNodes = in.NumberOfNodes
|
out.NumberOfNodes = in.NumberOfNodes
|
||||||
@@ -139,6 +274,7 @@ func Convert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtili
|
|||||||
}
|
}
|
||||||
|
|
||||||
func autoConvert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(in *api.NodeResourceUtilizationThresholds, out *NodeResourceUtilizationThresholds, s conversion.Scope) error {
|
func autoConvert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(in *api.NodeResourceUtilizationThresholds, out *NodeResourceUtilizationThresholds, s conversion.Scope) error {
|
||||||
|
out.UseDeviationThresholds = in.UseDeviationThresholds
|
||||||
out.Thresholds = *(*ResourceThresholds)(unsafe.Pointer(&in.Thresholds))
|
out.Thresholds = *(*ResourceThresholds)(unsafe.Pointer(&in.Thresholds))
|
||||||
out.TargetThresholds = *(*ResourceThresholds)(unsafe.Pointer(&in.TargetThresholds))
|
out.TargetThresholds = *(*ResourceThresholds)(unsafe.Pointer(&in.TargetThresholds))
|
||||||
out.NumberOfNodes = in.NumberOfNodes
|
out.NumberOfNodes = in.NumberOfNodes
|
||||||
@@ -150,11 +286,85 @@ func Convert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtili
|
|||||||
return autoConvert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(in, out, s)
|
return autoConvert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(in, out, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_PodLifeTime_To_api_PodLifeTime(in *PodLifeTime, out *api.PodLifeTime, s conversion.Scope) error {
|
||||||
|
out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds))
|
||||||
|
out.PodStatusPhases = *(*[]string)(unsafe.Pointer(&in.PodStatusPhases))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_PodLifeTime_To_api_PodLifeTime is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_PodLifeTime_To_api_PodLifeTime(in *PodLifeTime, out *api.PodLifeTime, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_PodLifeTime_To_api_PodLifeTime(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_api_PodLifeTime_To_v1alpha1_PodLifeTime(in *api.PodLifeTime, out *PodLifeTime, s conversion.Scope) error {
|
||||||
|
out.MaxPodLifeTimeSeconds = (*uint)(unsafe.Pointer(in.MaxPodLifeTimeSeconds))
|
||||||
|
out.PodStatusPhases = *(*[]string)(unsafe.Pointer(&in.PodStatusPhases))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_api_PodLifeTime_To_v1alpha1_PodLifeTime is an autogenerated conversion function.
|
||||||
|
func Convert_api_PodLifeTime_To_v1alpha1_PodLifeTime(in *api.PodLifeTime, out *PodLifeTime, s conversion.Scope) error {
|
||||||
|
return autoConvert_api_PodLifeTime_To_v1alpha1_PodLifeTime(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(in *PodsHavingTooManyRestarts, out *api.PodsHavingTooManyRestarts, s conversion.Scope) error {
|
||||||
|
out.PodRestartThreshold = in.PodRestartThreshold
|
||||||
|
out.IncludingInitContainers = in.IncludingInitContainers
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(in *PodsHavingTooManyRestarts, out *api.PodsHavingTooManyRestarts, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_PodsHavingTooManyRestarts_To_api_PodsHavingTooManyRestarts(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts(in *api.PodsHavingTooManyRestarts, out *PodsHavingTooManyRestarts, s conversion.Scope) error {
|
||||||
|
out.PodRestartThreshold = in.PodRestartThreshold
|
||||||
|
out.IncludingInitContainers = in.IncludingInitContainers
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts is an autogenerated conversion function.
|
||||||
|
func Convert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts(in *api.PodsHavingTooManyRestarts, out *PodsHavingTooManyRestarts, s conversion.Scope) error {
|
||||||
|
return autoConvert_api_PodsHavingTooManyRestarts_To_v1alpha1_PodsHavingTooManyRestarts(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates(in *RemoveDuplicates, out *api.RemoveDuplicates, s conversion.Scope) error {
|
||||||
|
out.ExcludeOwnerKinds = *(*[]string)(unsafe.Pointer(&in.ExcludeOwnerKinds))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates(in *RemoveDuplicates, out *api.RemoveDuplicates, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_RemoveDuplicates_To_api_RemoveDuplicates(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates(in *api.RemoveDuplicates, out *RemoveDuplicates, s conversion.Scope) error {
|
||||||
|
out.ExcludeOwnerKinds = *(*[]string)(unsafe.Pointer(&in.ExcludeOwnerKinds))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates is an autogenerated conversion function.
|
||||||
|
func Convert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates(in *api.RemoveDuplicates, out *RemoveDuplicates, s conversion.Scope) error {
|
||||||
|
return autoConvert_api_RemoveDuplicates_To_v1alpha1_RemoveDuplicates(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
func autoConvert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in *StrategyParameters, out *api.StrategyParameters, s conversion.Scope) error {
|
func autoConvert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in *StrategyParameters, out *api.StrategyParameters, s conversion.Scope) error {
|
||||||
if err := Convert_v1alpha1_NodeResourceUtilizationThresholds_To_api_NodeResourceUtilizationThresholds(&in.NodeResourceUtilizationThresholds, &out.NodeResourceUtilizationThresholds, s); err != nil {
|
out.NodeResourceUtilizationThresholds = (*api.NodeResourceUtilizationThresholds)(unsafe.Pointer(in.NodeResourceUtilizationThresholds))
|
||||||
return err
|
|
||||||
}
|
|
||||||
out.NodeAffinityType = *(*[]string)(unsafe.Pointer(&in.NodeAffinityType))
|
out.NodeAffinityType = *(*[]string)(unsafe.Pointer(&in.NodeAffinityType))
|
||||||
|
out.PodsHavingTooManyRestarts = (*api.PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts))
|
||||||
|
out.PodLifeTime = (*api.PodLifeTime)(unsafe.Pointer(in.PodLifeTime))
|
||||||
|
out.RemoveDuplicates = (*api.RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates))
|
||||||
|
out.FailedPods = (*api.FailedPods)(unsafe.Pointer(in.FailedPods))
|
||||||
|
out.IncludeSoftConstraints = in.IncludeSoftConstraints
|
||||||
|
out.Namespaces = (*api.Namespaces)(unsafe.Pointer(in.Namespaces))
|
||||||
|
out.ThresholdPriority = (*int32)(unsafe.Pointer(in.ThresholdPriority))
|
||||||
|
out.ThresholdPriorityClassName = in.ThresholdPriorityClassName
|
||||||
|
out.LabelSelector = (*v1.LabelSelector)(unsafe.Pointer(in.LabelSelector))
|
||||||
|
out.NodeFit = in.NodeFit
|
||||||
|
out.IncludePreferNoSchedule = in.IncludePreferNoSchedule
|
||||||
|
out.ExcludedTaints = *(*[]string)(unsafe.Pointer(&in.ExcludedTaints))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,10 +374,20 @@ func Convert_v1alpha1_StrategyParameters_To_api_StrategyParameters(in *StrategyP
|
|||||||
}
|
}
|
||||||
|
|
||||||
func autoConvert_api_StrategyParameters_To_v1alpha1_StrategyParameters(in *api.StrategyParameters, out *StrategyParameters, s conversion.Scope) error {
|
func autoConvert_api_StrategyParameters_To_v1alpha1_StrategyParameters(in *api.StrategyParameters, out *StrategyParameters, s conversion.Scope) error {
|
||||||
if err := Convert_api_NodeResourceUtilizationThresholds_To_v1alpha1_NodeResourceUtilizationThresholds(&in.NodeResourceUtilizationThresholds, &out.NodeResourceUtilizationThresholds, s); err != nil {
|
out.NodeResourceUtilizationThresholds = (*NodeResourceUtilizationThresholds)(unsafe.Pointer(in.NodeResourceUtilizationThresholds))
|
||||||
return err
|
|
||||||
}
|
|
||||||
out.NodeAffinityType = *(*[]string)(unsafe.Pointer(&in.NodeAffinityType))
|
out.NodeAffinityType = *(*[]string)(unsafe.Pointer(&in.NodeAffinityType))
|
||||||
|
out.PodsHavingTooManyRestarts = (*PodsHavingTooManyRestarts)(unsafe.Pointer(in.PodsHavingTooManyRestarts))
|
||||||
|
out.PodLifeTime = (*PodLifeTime)(unsafe.Pointer(in.PodLifeTime))
|
||||||
|
out.RemoveDuplicates = (*RemoveDuplicates)(unsafe.Pointer(in.RemoveDuplicates))
|
||||||
|
out.FailedPods = (*FailedPods)(unsafe.Pointer(in.FailedPods))
|
||||||
|
out.IncludeSoftConstraints = in.IncludeSoftConstraints
|
||||||
|
out.Namespaces = (*Namespaces)(unsafe.Pointer(in.Namespaces))
|
||||||
|
out.ThresholdPriority = (*int32)(unsafe.Pointer(in.ThresholdPriority))
|
||||||
|
out.ThresholdPriorityClassName = in.ThresholdPriorityClassName
|
||||||
|
out.LabelSelector = (*v1.LabelSelector)(unsafe.Pointer(in.LabelSelector))
|
||||||
|
out.NodeFit = in.NodeFit
|
||||||
|
out.IncludePreferNoSchedule = in.IncludePreferNoSchedule
|
||||||
|
out.ExcludedTaints = *(*[]string)(unsafe.Pointer(&in.ExcludedTaints))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Kubernetes Authors.
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -21,6 +22,7 @@ limitations under the License.
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,6 +37,41 @@ func (in *DeschedulerPolicy) DeepCopyInto(out *DeschedulerPolicy) {
|
|||||||
(*out)[key] = *val.DeepCopy()
|
(*out)[key] = *val.DeepCopy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if in.NodeSelector != nil {
|
||||||
|
in, out := &in.NodeSelector, &out.NodeSelector
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.EvictFailedBarePods != nil {
|
||||||
|
in, out := &in.EvictFailedBarePods, &out.EvictFailedBarePods
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.EvictLocalStoragePods != nil {
|
||||||
|
in, out := &in.EvictLocalStoragePods, &out.EvictLocalStoragePods
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.EvictSystemCriticalPods != nil {
|
||||||
|
in, out := &in.EvictSystemCriticalPods, &out.EvictSystemCriticalPods
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.IgnorePVCPods != nil {
|
||||||
|
in, out := &in.IgnorePVCPods, &out.IgnorePVCPods
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.MaxNoOfPodsToEvictPerNode != nil {
|
||||||
|
in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode
|
||||||
|
*out = new(int)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.MaxNoOfPodsToEvictPerNamespace != nil {
|
||||||
|
in, out := &in.MaxNoOfPodsToEvictPerNamespace, &out.MaxNoOfPodsToEvictPerNamespace
|
||||||
|
*out = new(int)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +96,11 @@ func (in *DeschedulerPolicy) DeepCopyObject() runtime.Object {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *DeschedulerStrategy) DeepCopyInto(out *DeschedulerStrategy) {
|
func (in *DeschedulerStrategy) DeepCopyInto(out *DeschedulerStrategy) {
|
||||||
*out = *in
|
*out = *in
|
||||||
in.Params.DeepCopyInto(&out.Params)
|
if in.Params != nil {
|
||||||
|
in, out := &in.Params, &out.Params
|
||||||
|
*out = new(StrategyParameters)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +114,63 @@ func (in *DeschedulerStrategy) DeepCopy() *DeschedulerStrategy {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *FailedPods) DeepCopyInto(out *FailedPods) {
|
||||||
|
*out = *in
|
||||||
|
if in.ExcludeOwnerKinds != nil {
|
||||||
|
in, out := &in.ExcludeOwnerKinds, &out.ExcludeOwnerKinds
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.MinPodLifetimeSeconds != nil {
|
||||||
|
in, out := &in.MinPodLifetimeSeconds, &out.MinPodLifetimeSeconds
|
||||||
|
*out = new(uint)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.Reasons != nil {
|
||||||
|
in, out := &in.Reasons, &out.Reasons
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailedPods.
|
||||||
|
func (in *FailedPods) DeepCopy() *FailedPods {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(FailedPods)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Namespaces) DeepCopyInto(out *Namespaces) {
|
||||||
|
*out = *in
|
||||||
|
if in.Include != nil {
|
||||||
|
in, out := &in.Include, &out.Include
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Exclude != nil {
|
||||||
|
in, out := &in.Exclude, &out.Exclude
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Namespaces.
|
||||||
|
func (in *Namespaces) DeepCopy() *Namespaces {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Namespaces)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *NodeResourceUtilizationThresholds) DeepCopyInto(out *NodeResourceUtilizationThresholds) {
|
func (in *NodeResourceUtilizationThresholds) DeepCopyInto(out *NodeResourceUtilizationThresholds) {
|
||||||
*out = *in
|
*out = *in
|
||||||
@@ -103,6 +201,69 @@ func (in *NodeResourceUtilizationThresholds) DeepCopy() *NodeResourceUtilization
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *PodLifeTime) DeepCopyInto(out *PodLifeTime) {
|
||||||
|
*out = *in
|
||||||
|
if in.MaxPodLifeTimeSeconds != nil {
|
||||||
|
in, out := &in.MaxPodLifeTimeSeconds, &out.MaxPodLifeTimeSeconds
|
||||||
|
*out = new(uint)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PodStatusPhases != nil {
|
||||||
|
in, out := &in.PodStatusPhases, &out.PodStatusPhases
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLifeTime.
|
||||||
|
func (in *PodLifeTime) DeepCopy() *PodLifeTime {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(PodLifeTime)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *PodsHavingTooManyRestarts) DeepCopyInto(out *PodsHavingTooManyRestarts) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodsHavingTooManyRestarts.
|
||||||
|
func (in *PodsHavingTooManyRestarts) DeepCopy() *PodsHavingTooManyRestarts {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(PodsHavingTooManyRestarts)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *RemoveDuplicates) DeepCopyInto(out *RemoveDuplicates) {
|
||||||
|
*out = *in
|
||||||
|
if in.ExcludeOwnerKinds != nil {
|
||||||
|
in, out := &in.ExcludeOwnerKinds, &out.ExcludeOwnerKinds
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoveDuplicates.
|
||||||
|
func (in *RemoveDuplicates) DeepCopy() *RemoveDuplicates {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(RemoveDuplicates)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in ResourceThresholds) DeepCopyInto(out *ResourceThresholds) {
|
func (in ResourceThresholds) DeepCopyInto(out *ResourceThresholds) {
|
||||||
{
|
{
|
||||||
@@ -150,12 +311,56 @@ func (in StrategyList) DeepCopy() StrategyList {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) {
|
func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) {
|
||||||
*out = *in
|
*out = *in
|
||||||
in.NodeResourceUtilizationThresholds.DeepCopyInto(&out.NodeResourceUtilizationThresholds)
|
if in.NodeResourceUtilizationThresholds != nil {
|
||||||
|
in, out := &in.NodeResourceUtilizationThresholds, &out.NodeResourceUtilizationThresholds
|
||||||
|
*out = new(NodeResourceUtilizationThresholds)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
if in.NodeAffinityType != nil {
|
if in.NodeAffinityType != nil {
|
||||||
in, out := &in.NodeAffinityType, &out.NodeAffinityType
|
in, out := &in.NodeAffinityType, &out.NodeAffinityType
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.PodsHavingTooManyRestarts != nil {
|
||||||
|
in, out := &in.PodsHavingTooManyRestarts, &out.PodsHavingTooManyRestarts
|
||||||
|
*out = new(PodsHavingTooManyRestarts)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PodLifeTime != nil {
|
||||||
|
in, out := &in.PodLifeTime, &out.PodLifeTime
|
||||||
|
*out = new(PodLifeTime)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.RemoveDuplicates != nil {
|
||||||
|
in, out := &in.RemoveDuplicates, &out.RemoveDuplicates
|
||||||
|
*out = new(RemoveDuplicates)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.FailedPods != nil {
|
||||||
|
in, out := &in.FailedPods, &out.FailedPods
|
||||||
|
*out = new(FailedPods)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.Namespaces != nil {
|
||||||
|
in, out := &in.Namespaces, &out.Namespaces
|
||||||
|
*out = new(Namespaces)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.ThresholdPriority != nil {
|
||||||
|
in, out := &in.ThresholdPriority, &out.ThresholdPriority
|
||||||
|
*out = new(int32)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.LabelSelector != nil {
|
||||||
|
in, out := &in.LabelSelector, &out.LabelSelector
|
||||||
|
*out = new(v1.LabelSelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.ExcludedTaints != nil {
|
||||||
|
in, out := &in.ExcludedTaints, &out.ExcludedTaints
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Kubernetes Authors.
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Kubernetes Authors.
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -21,6 +22,7 @@ limitations under the License.
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,6 +37,41 @@ func (in *DeschedulerPolicy) DeepCopyInto(out *DeschedulerPolicy) {
|
|||||||
(*out)[key] = *val.DeepCopy()
|
(*out)[key] = *val.DeepCopy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if in.NodeSelector != nil {
|
||||||
|
in, out := &in.NodeSelector, &out.NodeSelector
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.EvictFailedBarePods != nil {
|
||||||
|
in, out := &in.EvictFailedBarePods, &out.EvictFailedBarePods
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.EvictLocalStoragePods != nil {
|
||||||
|
in, out := &in.EvictLocalStoragePods, &out.EvictLocalStoragePods
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.EvictSystemCriticalPods != nil {
|
||||||
|
in, out := &in.EvictSystemCriticalPods, &out.EvictSystemCriticalPods
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.IgnorePVCPods != nil {
|
||||||
|
in, out := &in.IgnorePVCPods, &out.IgnorePVCPods
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.MaxNoOfPodsToEvictPerNode != nil {
|
||||||
|
in, out := &in.MaxNoOfPodsToEvictPerNode, &out.MaxNoOfPodsToEvictPerNode
|
||||||
|
*out = new(uint)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.MaxNoOfPodsToEvictPerNamespace != nil {
|
||||||
|
in, out := &in.MaxNoOfPodsToEvictPerNamespace, &out.MaxNoOfPodsToEvictPerNamespace
|
||||||
|
*out = new(uint)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +96,11 @@ func (in *DeschedulerPolicy) DeepCopyObject() runtime.Object {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *DeschedulerStrategy) DeepCopyInto(out *DeschedulerStrategy) {
|
func (in *DeschedulerStrategy) DeepCopyInto(out *DeschedulerStrategy) {
|
||||||
*out = *in
|
*out = *in
|
||||||
in.Params.DeepCopyInto(&out.Params)
|
if in.Params != nil {
|
||||||
|
in, out := &in.Params, &out.Params
|
||||||
|
*out = new(StrategyParameters)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +114,63 @@ func (in *DeschedulerStrategy) DeepCopy() *DeschedulerStrategy {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *FailedPods) DeepCopyInto(out *FailedPods) {
|
||||||
|
*out = *in
|
||||||
|
if in.ExcludeOwnerKinds != nil {
|
||||||
|
in, out := &in.ExcludeOwnerKinds, &out.ExcludeOwnerKinds
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.MinPodLifetimeSeconds != nil {
|
||||||
|
in, out := &in.MinPodLifetimeSeconds, &out.MinPodLifetimeSeconds
|
||||||
|
*out = new(uint)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.Reasons != nil {
|
||||||
|
in, out := &in.Reasons, &out.Reasons
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FailedPods.
|
||||||
|
func (in *FailedPods) DeepCopy() *FailedPods {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(FailedPods)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Namespaces) DeepCopyInto(out *Namespaces) {
|
||||||
|
*out = *in
|
||||||
|
if in.Include != nil {
|
||||||
|
in, out := &in.Include, &out.Include
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.Exclude != nil {
|
||||||
|
in, out := &in.Exclude, &out.Exclude
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Namespaces.
|
||||||
|
func (in *Namespaces) DeepCopy() *Namespaces {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Namespaces)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *NodeResourceUtilizationThresholds) DeepCopyInto(out *NodeResourceUtilizationThresholds) {
|
func (in *NodeResourceUtilizationThresholds) DeepCopyInto(out *NodeResourceUtilizationThresholds) {
|
||||||
*out = *in
|
*out = *in
|
||||||
@@ -103,6 +201,69 @@ func (in *NodeResourceUtilizationThresholds) DeepCopy() *NodeResourceUtilization
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *PodLifeTime) DeepCopyInto(out *PodLifeTime) {
|
||||||
|
*out = *in
|
||||||
|
if in.MaxPodLifeTimeSeconds != nil {
|
||||||
|
in, out := &in.MaxPodLifeTimeSeconds, &out.MaxPodLifeTimeSeconds
|
||||||
|
*out = new(uint)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PodStatusPhases != nil {
|
||||||
|
in, out := &in.PodStatusPhases, &out.PodStatusPhases
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodLifeTime.
|
||||||
|
func (in *PodLifeTime) DeepCopy() *PodLifeTime {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(PodLifeTime)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *PodsHavingTooManyRestarts) DeepCopyInto(out *PodsHavingTooManyRestarts) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodsHavingTooManyRestarts.
|
||||||
|
func (in *PodsHavingTooManyRestarts) DeepCopy() *PodsHavingTooManyRestarts {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(PodsHavingTooManyRestarts)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *RemoveDuplicates) DeepCopyInto(out *RemoveDuplicates) {
|
||||||
|
*out = *in
|
||||||
|
if in.ExcludeOwnerKinds != nil {
|
||||||
|
in, out := &in.ExcludeOwnerKinds, &out.ExcludeOwnerKinds
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoveDuplicates.
|
||||||
|
func (in *RemoveDuplicates) DeepCopy() *RemoveDuplicates {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(RemoveDuplicates)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in ResourceThresholds) DeepCopyInto(out *ResourceThresholds) {
|
func (in ResourceThresholds) DeepCopyInto(out *ResourceThresholds) {
|
||||||
{
|
{
|
||||||
@@ -150,12 +311,56 @@ func (in StrategyList) DeepCopy() StrategyList {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) {
|
func (in *StrategyParameters) DeepCopyInto(out *StrategyParameters) {
|
||||||
*out = *in
|
*out = *in
|
||||||
in.NodeResourceUtilizationThresholds.DeepCopyInto(&out.NodeResourceUtilizationThresholds)
|
if in.NodeResourceUtilizationThresholds != nil {
|
||||||
|
in, out := &in.NodeResourceUtilizationThresholds, &out.NodeResourceUtilizationThresholds
|
||||||
|
*out = new(NodeResourceUtilizationThresholds)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
if in.NodeAffinityType != nil {
|
if in.NodeAffinityType != nil {
|
||||||
in, out := &in.NodeAffinityType, &out.NodeAffinityType
|
in, out := &in.NodeAffinityType, &out.NodeAffinityType
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.PodsHavingTooManyRestarts != nil {
|
||||||
|
in, out := &in.PodsHavingTooManyRestarts, &out.PodsHavingTooManyRestarts
|
||||||
|
*out = new(PodsHavingTooManyRestarts)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.PodLifeTime != nil {
|
||||||
|
in, out := &in.PodLifeTime, &out.PodLifeTime
|
||||||
|
*out = new(PodLifeTime)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.RemoveDuplicates != nil {
|
||||||
|
in, out := &in.RemoveDuplicates, &out.RemoveDuplicates
|
||||||
|
*out = new(RemoveDuplicates)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.FailedPods != nil {
|
||||||
|
in, out := &in.FailedPods, &out.FailedPods
|
||||||
|
*out = new(FailedPods)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.Namespaces != nil {
|
||||||
|
in, out := &in.Namespaces, &out.Namespaces
|
||||||
|
*out = new(Namespaces)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.ThresholdPriority != nil {
|
||||||
|
in, out := &in.ThresholdPriority, &out.ThresholdPriority
|
||||||
|
*out = new(int32)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
if in.LabelSelector != nil {
|
||||||
|
in, out := &in.LabelSelector, &out.LabelSelector
|
||||||
|
*out = new(v1.LabelSelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.ExcludedTaints != nil {
|
||||||
|
in, out := &in.ExcludedTaints, &out.ExcludedTaints
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ package componentconfig
|
|||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/scheme"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -34,12 +32,6 @@ const GroupName = "deschedulercomponentconfig"
|
|||||||
// SchemeGroupVersion is group version used to register these objects
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||||
|
|
||||||
func init() {
|
|
||||||
if err := addKnownTypes(scheme.Scheme); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
||||||
func Kind(kind string) schema.GroupKind {
|
func Kind(kind string) schema.GroupKind {
|
||||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
componentbaseconfig "k8s.io/component-base/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
@@ -48,4 +49,14 @@ type DeschedulerConfiguration struct {
|
|||||||
|
|
||||||
// EvictLocalStoragePods allows pods using local storage to be evicted.
|
// EvictLocalStoragePods allows pods using local storage to be evicted.
|
||||||
EvictLocalStoragePods bool
|
EvictLocalStoragePods bool
|
||||||
|
|
||||||
|
// IgnorePVCPods sets whether PVC pods should be allowed to be evicted
|
||||||
|
IgnorePVCPods bool
|
||||||
|
|
||||||
|
// LeaderElection starts Deployment using leader election loop
|
||||||
|
LeaderElection componentbaseconfig.LeaderElectionConfiguration
|
||||||
|
|
||||||
|
// Logging specifies the options of logging.
|
||||||
|
// Refer [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information.
|
||||||
|
Logging componentbaseconfig.LoggingConfiguration
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ package v1alpha1
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
componentbaseconfig "k8s.io/component-base/config"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,4 +50,14 @@ type DeschedulerConfiguration struct {
|
|||||||
|
|
||||||
// EvictLocalStoragePods allows pods using local storage to be evicted.
|
// EvictLocalStoragePods allows pods using local storage to be evicted.
|
||||||
EvictLocalStoragePods bool `json:"evictLocalStoragePods,omitempty"`
|
EvictLocalStoragePods bool `json:"evictLocalStoragePods,omitempty"`
|
||||||
|
|
||||||
|
// IgnorePVCPods sets whether PVC pods should be allowed to be evicted
|
||||||
|
IgnorePVCPods bool `json:"ignorePvcPods,omitempty"`
|
||||||
|
|
||||||
|
// LeaderElection starts Deployment using leader election loop
|
||||||
|
LeaderElection componentbaseconfig.LeaderElectionConfiguration `json:"leaderElection,omitempty"`
|
||||||
|
|
||||||
|
// Logging specifies the options of logging.
|
||||||
|
// Refer [Logs Options](https://github.com/kubernetes/component-base/blob/master/logs/options.go) for more information.
|
||||||
|
Logging componentbaseconfig.LoggingConfiguration `json:"logging,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Kubernetes Authors.
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -56,6 +57,9 @@ func autoConvert_v1alpha1_DeschedulerConfiguration_To_componentconfig_Deschedule
|
|||||||
out.NodeSelector = in.NodeSelector
|
out.NodeSelector = in.NodeSelector
|
||||||
out.MaxNoOfPodsToEvictPerNode = in.MaxNoOfPodsToEvictPerNode
|
out.MaxNoOfPodsToEvictPerNode = in.MaxNoOfPodsToEvictPerNode
|
||||||
out.EvictLocalStoragePods = in.EvictLocalStoragePods
|
out.EvictLocalStoragePods = in.EvictLocalStoragePods
|
||||||
|
out.IgnorePVCPods = in.IgnorePVCPods
|
||||||
|
out.LeaderElection = in.LeaderElection
|
||||||
|
out.Logging = in.Logging
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +76,9 @@ func autoConvert_componentconfig_DeschedulerConfiguration_To_v1alpha1_Deschedule
|
|||||||
out.NodeSelector = in.NodeSelector
|
out.NodeSelector = in.NodeSelector
|
||||||
out.MaxNoOfPodsToEvictPerNode = in.MaxNoOfPodsToEvictPerNode
|
out.MaxNoOfPodsToEvictPerNode = in.MaxNoOfPodsToEvictPerNode
|
||||||
out.EvictLocalStoragePods = in.EvictLocalStoragePods
|
out.EvictLocalStoragePods = in.EvictLocalStoragePods
|
||||||
|
out.IgnorePVCPods = in.IgnorePVCPods
|
||||||
|
out.LeaderElection = in.LeaderElection
|
||||||
|
out.Logging = in.Logging
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Kubernetes Authors.
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -28,6 +29,8 @@ import (
|
|||||||
func (in *DeschedulerConfiguration) DeepCopyInto(out *DeschedulerConfiguration) {
|
func (in *DeschedulerConfiguration) DeepCopyInto(out *DeschedulerConfiguration) {
|
||||||
*out = *in
|
*out = *in
|
||||||
out.TypeMeta = in.TypeMeta
|
out.TypeMeta = in.TypeMeta
|
||||||
|
out.LeaderElection = in.LeaderElection
|
||||||
|
in.Logging.DeepCopyInto(&out.Logging)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Kubernetes Authors.
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
// +build !ignore_autogenerated
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Kubernetes Authors.
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -28,6 +29,8 @@ import (
|
|||||||
func (in *DeschedulerConfiguration) DeepCopyInto(out *DeschedulerConfiguration) {
|
func (in *DeschedulerConfiguration) DeepCopyInto(out *DeschedulerConfiguration) {
|
||||||
*out = *in
|
*out = *in
|
||||||
out.TypeMeta = in.TypeMeta
|
out.TypeMeta = in.TypeMeta
|
||||||
|
out.LeaderElection = in.LeaderElection
|
||||||
|
in.Logging.DeepCopyInto(&out.Logging)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,21 +17,39 @@ limitations under the License.
|
|||||||
package descheduler
|
package descheduler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/klog"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
policy "k8s.io/api/policy/v1beta1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
||||||
|
core "k8s.io/client-go/testing"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||||
|
schedulingv1informers "k8s.io/client-go/informers/scheduling/v1"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/cmd/descheduler/app/options"
|
"sigs.k8s.io/descheduler/cmd/descheduler/app/options"
|
||||||
|
"sigs.k8s.io/descheduler/metrics"
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/client"
|
"sigs.k8s.io/descheduler/pkg/descheduler/client"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||||
eutils "sigs.k8s.io/descheduler/pkg/descheduler/evictions/utils"
|
eutils "sigs.k8s.io/descheduler/pkg/descheduler/evictions/utils"
|
||||||
nodeutil "sigs.k8s.io/descheduler/pkg/descheduler/node"
|
nodeutil "sigs.k8s.io/descheduler/pkg/descheduler/node"
|
||||||
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/strategies"
|
"sigs.k8s.io/descheduler/pkg/descheduler/strategies"
|
||||||
"sigs.k8s.io/descheduler/pkg/utils"
|
"sigs.k8s.io/descheduler/pkg/descheduler/strategies/nodeutilization"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Run(rs *options.DeschedulerServer) error {
|
func Run(ctx context.Context, rs *options.DeschedulerServer) error {
|
||||||
|
metrics.Register()
|
||||||
|
|
||||||
rsclient, err := client.CreateClient(rs.KubeconfigFile)
|
rsclient, err := client.CreateClient(rs.KubeconfigFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -46,39 +64,250 @@ func Run(rs *options.DeschedulerServer) error {
|
|||||||
return fmt.Errorf("deschedulerPolicy is nil")
|
return fmt.Errorf("deschedulerPolicy is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
return RunDeschedulerStrategies(rs, deschedulerPolicy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunDeschedulerStrategies(rs *options.DeschedulerServer, deschedulerPolicy *api.DeschedulerPolicy) error {
|
|
||||||
evictionPolicyGroupVersion, err := eutils.SupportEviction(rs.Client)
|
evictionPolicyGroupVersion, err := eutils.SupportEviction(rs.Client)
|
||||||
if err != nil || len(evictionPolicyGroupVersion) == 0 {
|
if err != nil || len(evictionPolicyGroupVersion) == 0 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stopChannel := make(chan struct{})
|
runFn := func() error {
|
||||||
nodes, err := nodeutil.ReadyNodes(rs.Client, rs.NodeSelector, stopChannel)
|
return RunDeschedulerStrategies(ctx, rs, deschedulerPolicy, evictionPolicyGroupVersion)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(nodes) <= 1 {
|
if rs.LeaderElection.LeaderElect && rs.DeschedulingInterval.Seconds() == 0 {
|
||||||
klog.V(1).Infof("The cluster size is 0 or 1 meaning eviction causes service disruption or degradation. So aborting..")
|
return fmt.Errorf("leaderElection must be used with deschedulingInterval")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.LeaderElection.LeaderElect && !rs.DryRun {
|
||||||
|
if err := NewLeaderElection(runFn, rsclient, &rs.LeaderElection, ctx); err != nil {
|
||||||
|
return fmt.Errorf("leaderElection: %w", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
nodePodCount := utils.InitializeNodePodCount(nodes)
|
return runFn()
|
||||||
wait.Until(func() {
|
}
|
||||||
strategies.RemoveDuplicatePods(rs, deschedulerPolicy.Strategies["RemoveDuplicates"], evictionPolicyGroupVersion, nodes, nodePodCount)
|
|
||||||
strategies.LowNodeUtilization(rs, deschedulerPolicy.Strategies["LowNodeUtilization"], evictionPolicyGroupVersion, nodes, nodePodCount)
|
type strategyFunction func(ctx context.Context, client clientset.Interface, strategy api.DeschedulerStrategy, nodes []*v1.Node, podEvictor *evictions.PodEvictor, getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc)
|
||||||
strategies.RemovePodsViolatingInterPodAntiAffinity(rs, deschedulerPolicy.Strategies["RemovePodsViolatingInterPodAntiAffinity"], evictionPolicyGroupVersion, nodes, nodePodCount)
|
|
||||||
strategies.RemovePodsViolatingNodeAffinity(rs, deschedulerPolicy.Strategies["RemovePodsViolatingNodeAffinity"], evictionPolicyGroupVersion, nodes, nodePodCount)
|
func cachedClient(
|
||||||
strategies.RemovePodsViolatingNodeTaints(rs, deschedulerPolicy.Strategies["RemovePodsViolatingNodeTaints"], evictionPolicyGroupVersion, nodes, nodePodCount)
|
realClient clientset.Interface,
|
||||||
|
podInformer corev1informers.PodInformer,
|
||||||
|
nodeInformer corev1informers.NodeInformer,
|
||||||
|
namespaceInformer corev1informers.NamespaceInformer,
|
||||||
|
priorityClassInformer schedulingv1informers.PriorityClassInformer,
|
||||||
|
) (clientset.Interface, error) {
|
||||||
|
fakeClient := fakeclientset.NewSimpleClientset()
|
||||||
|
// simulate a pod eviction by deleting a pod
|
||||||
|
fakeClient.PrependReactor("create", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
||||||
|
if action.GetSubresource() == "eviction" {
|
||||||
|
createAct, matched := action.(core.CreateActionImpl)
|
||||||
|
if !matched {
|
||||||
|
return false, nil, fmt.Errorf("unable to convert action to core.CreateActionImpl")
|
||||||
|
}
|
||||||
|
eviction, matched := createAct.Object.(*policy.Eviction)
|
||||||
|
if !matched {
|
||||||
|
return false, nil, fmt.Errorf("unable to convert action object into *policy.Eviction")
|
||||||
|
}
|
||||||
|
if err := fakeClient.Tracker().Delete(action.GetResource(), eviction.GetNamespace(), eviction.GetName()); err != nil {
|
||||||
|
return false, nil, fmt.Errorf("unable to delete pod %v/%v: %v", eviction.GetNamespace(), eviction.GetName(), err)
|
||||||
|
}
|
||||||
|
return true, nil, nil
|
||||||
|
}
|
||||||
|
// fallback to the default reactor
|
||||||
|
return false, nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
klog.V(3).Infof("Pulling resources for the cached client from the cluster")
|
||||||
|
pods, err := podInformer.Lister().List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to list pods: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range pods {
|
||||||
|
if _, err := fakeClient.CoreV1().Pods(item.Namespace).Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to copy pod: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, err := nodeInformer.Lister().List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to list nodes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range nodes {
|
||||||
|
if _, err := fakeClient.CoreV1().Nodes().Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to copy node: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaces, err := namespaceInformer.Lister().List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to list namespaces: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range namespaces {
|
||||||
|
if _, err := fakeClient.CoreV1().Namespaces().Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to copy node: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
priorityClasses, err := priorityClassInformer.Lister().List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to list priorityclasses: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range priorityClasses {
|
||||||
|
if _, err := fakeClient.SchedulingV1().PriorityClasses().Create(context.TODO(), item, metav1.CreateOptions{}); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to copy priorityclass: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fakeClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunDeschedulerStrategies(ctx context.Context, rs *options.DeschedulerServer, deschedulerPolicy *api.DeschedulerPolicy, evictionPolicyGroupVersion string) error {
|
||||||
|
sharedInformerFactory := informers.NewSharedInformerFactory(rs.Client, 0)
|
||||||
|
nodeInformer := sharedInformerFactory.Core().V1().Nodes()
|
||||||
|
podInformer := sharedInformerFactory.Core().V1().Pods()
|
||||||
|
namespaceInformer := sharedInformerFactory.Core().V1().Namespaces()
|
||||||
|
priorityClassInformer := sharedInformerFactory.Scheduling().V1().PriorityClasses()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// create the informers
|
||||||
|
namespaceInformer.Informer()
|
||||||
|
priorityClassInformer.Informer()
|
||||||
|
|
||||||
|
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("build get pods assigned to node function error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedInformerFactory.Start(ctx.Done())
|
||||||
|
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||||
|
|
||||||
|
strategyFuncs := map[api.StrategyName]strategyFunction{
|
||||||
|
"RemoveDuplicates": strategies.RemoveDuplicatePods,
|
||||||
|
"LowNodeUtilization": nodeutilization.LowNodeUtilization,
|
||||||
|
"HighNodeUtilization": nodeutilization.HighNodeUtilization,
|
||||||
|
"RemovePodsViolatingInterPodAntiAffinity": strategies.RemovePodsViolatingInterPodAntiAffinity,
|
||||||
|
"RemovePodsViolatingNodeAffinity": strategies.RemovePodsViolatingNodeAffinity,
|
||||||
|
"RemovePodsViolatingNodeTaints": strategies.RemovePodsViolatingNodeTaints,
|
||||||
|
"RemovePodsHavingTooManyRestarts": strategies.RemovePodsHavingTooManyRestarts,
|
||||||
|
"PodLifeTime": strategies.PodLifeTime,
|
||||||
|
"RemovePodsViolatingTopologySpreadConstraint": strategies.RemovePodsViolatingTopologySpreadConstraint,
|
||||||
|
"RemoveFailedPods": strategies.RemoveFailedPods,
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeSelector string
|
||||||
|
if deschedulerPolicy.NodeSelector != nil {
|
||||||
|
nodeSelector = *deschedulerPolicy.NodeSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
var evictLocalStoragePods bool
|
||||||
|
if deschedulerPolicy.EvictLocalStoragePods != nil {
|
||||||
|
evictLocalStoragePods = *deschedulerPolicy.EvictLocalStoragePods
|
||||||
|
}
|
||||||
|
|
||||||
|
evictBarePods := false
|
||||||
|
if deschedulerPolicy.EvictFailedBarePods != nil {
|
||||||
|
evictBarePods = *deschedulerPolicy.EvictFailedBarePods
|
||||||
|
if evictBarePods {
|
||||||
|
klog.V(1).InfoS("Warning: EvictFailedBarePods is set to True. This could cause eviction of pods without ownerReferences.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
evictSystemCriticalPods := false
|
||||||
|
if deschedulerPolicy.EvictSystemCriticalPods != nil {
|
||||||
|
evictSystemCriticalPods = *deschedulerPolicy.EvictSystemCriticalPods
|
||||||
|
if evictSystemCriticalPods {
|
||||||
|
klog.V(1).InfoS("Warning: EvictSystemCriticalPods is set to True. This could cause eviction of Kubernetes system pods.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ignorePvcPods := false
|
||||||
|
if deschedulerPolicy.IgnorePVCPods != nil {
|
||||||
|
ignorePvcPods = *deschedulerPolicy.IgnorePVCPods
|
||||||
|
}
|
||||||
|
|
||||||
|
wait.NonSlidingUntil(func() {
|
||||||
|
nodes, err := nodeutil.ReadyNodes(ctx, rs.Client, nodeInformer, nodeSelector)
|
||||||
|
if err != nil {
|
||||||
|
klog.V(1).InfoS("Unable to get ready nodes", "err", err)
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nodes) <= 1 {
|
||||||
|
klog.V(1).InfoS("The cluster size is 0 or 1 meaning eviction causes service disruption or degradation. So aborting..")
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var podEvictorClient clientset.Interface
|
||||||
|
// When the dry mode is enable, collect all the relevant objects (mostly pods) under a fake client.
|
||||||
|
// So when evicting pods while running multiple strategies in a row have the cummulative effect
|
||||||
|
// as is when evicting pods for real.
|
||||||
|
if rs.DryRun {
|
||||||
|
klog.V(3).Infof("Building a cached client from the cluster for the dry run")
|
||||||
|
// Create a new cache so we start from scratch without any leftovers
|
||||||
|
fakeClient, err := cachedClient(rs.Client, podInformer, nodeInformer, namespaceInformer, priorityClassInformer)
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeSharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
getPodsAssignedToNode, err = podutil.BuildGetPodsAssignedToNodeFunc(fakeSharedInformerFactory.Core().V1().Pods())
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("build get pods assigned to node function error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeCtx, cncl := context.WithCancel(context.TODO())
|
||||||
|
defer cncl()
|
||||||
|
fakeSharedInformerFactory.Start(fakeCtx.Done())
|
||||||
|
fakeSharedInformerFactory.WaitForCacheSync(fakeCtx.Done())
|
||||||
|
|
||||||
|
podEvictorClient = fakeClient
|
||||||
|
} else {
|
||||||
|
podEvictorClient = rs.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.V(3).Infof("Building a pod evictor")
|
||||||
|
podEvictor := evictions.NewPodEvictor(
|
||||||
|
podEvictorClient,
|
||||||
|
evictionPolicyGroupVersion,
|
||||||
|
rs.DryRun,
|
||||||
|
deschedulerPolicy.MaxNoOfPodsToEvictPerNode,
|
||||||
|
deschedulerPolicy.MaxNoOfPodsToEvictPerNamespace,
|
||||||
|
nodes,
|
||||||
|
getPodsAssignedToNode,
|
||||||
|
evictLocalStoragePods,
|
||||||
|
evictSystemCriticalPods,
|
||||||
|
ignorePvcPods,
|
||||||
|
evictBarePods,
|
||||||
|
!rs.DisableMetrics,
|
||||||
|
)
|
||||||
|
|
||||||
|
for name, strategy := range deschedulerPolicy.Strategies {
|
||||||
|
if f, ok := strategyFuncs[name]; ok {
|
||||||
|
if strategy.Enabled {
|
||||||
|
f(ctx, rs.Client, strategy, nodes, podEvictor, getPodsAssignedToNode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
klog.ErrorS(fmt.Errorf("unknown strategy name"), "skipping strategy", "strategy", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.V(1).InfoS("Number of evicted pods", "totalEvicted", podEvictor.TotalEvicted())
|
||||||
|
|
||||||
// If there was no interval specified, send a signal to the stopChannel to end the wait.Until loop after 1 iteration
|
// If there was no interval specified, send a signal to the stopChannel to end the wait.Until loop after 1 iteration
|
||||||
if rs.DeschedulingInterval.Seconds() == 0 {
|
if rs.DeschedulingInterval.Seconds() == 0 {
|
||||||
close(stopChannel)
|
cancel()
|
||||||
}
|
}
|
||||||
}, rs.DeschedulingInterval, stopChannel)
|
}, rs.DeschedulingInterval, ctx.Done())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
166
pkg/descheduler/descheduler_test.go
Normal file
166
pkg/descheduler/descheduler_test.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package descheduler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
||||||
|
"sigs.k8s.io/descheduler/cmd/descheduler/app/options"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
|
"sigs.k8s.io/descheduler/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTaintsUpdated(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
n1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
|
||||||
|
n2 := test.BuildTestNode("n2", 2000, 3000, 10, nil)
|
||||||
|
|
||||||
|
p1 := test.BuildTestPod(fmt.Sprintf("pod_1_%s", n1.Name), 200, 0, n1.Name, nil)
|
||||||
|
p1.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
|
||||||
|
{},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := fakeclientset.NewSimpleClientset(n1, n2, p1)
|
||||||
|
dp := &api.DeschedulerPolicy{
|
||||||
|
Strategies: api.StrategyList{
|
||||||
|
"RemovePodsViolatingNodeTaints": api.DeschedulerStrategy{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := options.NewDeschedulerServer()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to initialize server: %v", err)
|
||||||
|
}
|
||||||
|
rs.Client = client
|
||||||
|
rs.DeschedulingInterval = 100 * time.Millisecond
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
defer close(errChan)
|
||||||
|
go func() {
|
||||||
|
err := RunDeschedulerStrategies(ctx, rs, dp, "v1beta1")
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to run descheduler strategies: %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
// Wait for few cycles and then verify the only pod still exists
|
||||||
|
}
|
||||||
|
|
||||||
|
pods, err := client.CoreV1().Pods(p1.Namespace).List(ctx, metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to list pods: %v", err)
|
||||||
|
}
|
||||||
|
if len(pods.Items) < 1 {
|
||||||
|
t.Errorf("The pod was evicted before a node was tained")
|
||||||
|
}
|
||||||
|
|
||||||
|
n1WithTaint := n1.DeepCopy()
|
||||||
|
n1WithTaint.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: "key",
|
||||||
|
Value: "value",
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := client.CoreV1().Nodes().Update(ctx, n1WithTaint, metav1.UpdateOptions{}); err != nil {
|
||||||
|
t.Fatalf("Unable to update node: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wait.PollImmediate(100*time.Millisecond, time.Second, func() (bool, error) {
|
||||||
|
// Get over evicted pod result in panic
|
||||||
|
//pods, err := client.CoreV1().Pods(p1.Namespace).Get(p1.Name, metav1.GetOptions{})
|
||||||
|
// List is better, it does not panic.
|
||||||
|
// Though once the pod is evicted, List starts to error with "can't assign or convert v1beta1.Eviction into v1.Pod"
|
||||||
|
pods, err := client.CoreV1().Pods(p1.Namespace).List(ctx, metav1.ListOptions{})
|
||||||
|
if err == nil {
|
||||||
|
if len(pods.Items) > 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if strings.Contains(err.Error(), "can't assign or convert v1beta1.Eviction into v1.Pod") {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("Unable to evict pod, node taint did not get propagated to descheduler strategies")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRootCancel(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
n1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
|
||||||
|
n2 := test.BuildTestNode("n2", 2000, 3000, 10, nil)
|
||||||
|
client := fakeclientset.NewSimpleClientset(n1, n2)
|
||||||
|
dp := &api.DeschedulerPolicy{
|
||||||
|
Strategies: api.StrategyList{}, // no strategies needed for this test
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := options.NewDeschedulerServer()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to initialize server: %v", err)
|
||||||
|
}
|
||||||
|
rs.Client = client
|
||||||
|
rs.DeschedulingInterval = 100 * time.Millisecond
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
defer close(errChan)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := RunDeschedulerStrategies(ctx, rs, dp, "v1beta1")
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
cancel()
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to run descheduler strategies: %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
t.Fatal("Root ctx should have canceled immediately")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRootCancelWithNoInterval(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
n1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
|
||||||
|
n2 := test.BuildTestNode("n2", 2000, 3000, 10, nil)
|
||||||
|
client := fakeclientset.NewSimpleClientset(n1, n2)
|
||||||
|
dp := &api.DeschedulerPolicy{
|
||||||
|
Strategies: api.StrategyList{}, // no strategies needed for this test
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := options.NewDeschedulerServer()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to initialize server: %v", err)
|
||||||
|
}
|
||||||
|
rs.Client = client
|
||||||
|
rs.DeschedulingInterval = 0
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
defer close(errChan)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := RunDeschedulerStrategies(ctx, rs, dp, "v1beta1")
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
cancel()
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to run descheduler strategies: %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
t.Fatal("Root ctx should have canceled immediately")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,25 +17,160 @@ limitations under the License.
|
|||||||
package evictions
|
package evictions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
policy "k8s.io/api/policy/v1beta1"
|
policy "k8s.io/api/policy/v1beta1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/util/errors"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
clientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
clientcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog/v2"
|
||||||
|
"sigs.k8s.io/descheduler/metrics"
|
||||||
|
nodeutil "sigs.k8s.io/descheduler/pkg/descheduler/node"
|
||||||
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/utils"
|
||||||
|
|
||||||
eutils "sigs.k8s.io/descheduler/pkg/descheduler/evictions/utils"
|
eutils "sigs.k8s.io/descheduler/pkg/descheduler/evictions/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EvictPod(client clientset.Interface, pod *v1.Pod, policyGroupVersion string, dryRun bool) (bool, error) {
|
const (
|
||||||
if dryRun {
|
evictPodAnnotationKey = "descheduler.alpha.kubernetes.io/evict"
|
||||||
return true, nil
|
)
|
||||||
|
|
||||||
|
// nodePodEvictedCount keeps count of pods evicted on node
|
||||||
|
type nodePodEvictedCount map[*v1.Node]uint
|
||||||
|
type namespacePodEvictCount map[string]uint
|
||||||
|
|
||||||
|
type PodEvictor struct {
|
||||||
|
client clientset.Interface
|
||||||
|
nodes []*v1.Node
|
||||||
|
nodeIndexer podutil.GetPodsAssignedToNodeFunc
|
||||||
|
policyGroupVersion string
|
||||||
|
dryRun bool
|
||||||
|
maxPodsToEvictPerNode *uint
|
||||||
|
maxPodsToEvictPerNamespace *uint
|
||||||
|
nodepodCount nodePodEvictedCount
|
||||||
|
namespacePodCount namespacePodEvictCount
|
||||||
|
evictFailedBarePods bool
|
||||||
|
evictLocalStoragePods bool
|
||||||
|
evictSystemCriticalPods bool
|
||||||
|
ignorePvcPods bool
|
||||||
|
metricsEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPodEvictor(
|
||||||
|
client clientset.Interface,
|
||||||
|
policyGroupVersion string,
|
||||||
|
dryRun bool,
|
||||||
|
maxPodsToEvictPerNode *uint,
|
||||||
|
maxPodsToEvictPerNamespace *uint,
|
||||||
|
nodes []*v1.Node,
|
||||||
|
nodeIndexer podutil.GetPodsAssignedToNodeFunc,
|
||||||
|
evictLocalStoragePods bool,
|
||||||
|
evictSystemCriticalPods bool,
|
||||||
|
ignorePvcPods bool,
|
||||||
|
evictFailedBarePods bool,
|
||||||
|
metricsEnabled bool,
|
||||||
|
) *PodEvictor {
|
||||||
|
var nodePodCount = make(nodePodEvictedCount)
|
||||||
|
var namespacePodCount = make(namespacePodEvictCount)
|
||||||
|
for _, node := range nodes {
|
||||||
|
// Initialize podsEvicted till now with 0.
|
||||||
|
nodePodCount[node] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return &PodEvictor{
|
||||||
|
client: client,
|
||||||
|
nodes: nodes,
|
||||||
|
nodeIndexer: nodeIndexer,
|
||||||
|
policyGroupVersion: policyGroupVersion,
|
||||||
|
dryRun: dryRun,
|
||||||
|
maxPodsToEvictPerNode: maxPodsToEvictPerNode,
|
||||||
|
maxPodsToEvictPerNamespace: maxPodsToEvictPerNamespace,
|
||||||
|
nodepodCount: nodePodCount,
|
||||||
|
namespacePodCount: namespacePodCount,
|
||||||
|
evictLocalStoragePods: evictLocalStoragePods,
|
||||||
|
evictSystemCriticalPods: evictSystemCriticalPods,
|
||||||
|
evictFailedBarePods: evictFailedBarePods,
|
||||||
|
ignorePvcPods: ignorePvcPods,
|
||||||
|
metricsEnabled: metricsEnabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeEvicted gives a number of pods evicted for node
|
||||||
|
func (pe *PodEvictor) NodeEvicted(node *v1.Node) uint {
|
||||||
|
return pe.nodepodCount[node]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalEvicted gives a number of pods evicted through all nodes
|
||||||
|
func (pe *PodEvictor) TotalEvicted() uint {
|
||||||
|
var total uint
|
||||||
|
for _, count := range pe.nodepodCount {
|
||||||
|
total += count
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvictPod returns non-nil error only when evicting a pod on a node is not
|
||||||
|
// possible (due to maxPodsToEvictPerNode constraint). Success is true when the pod
|
||||||
|
// is evicted on the server side.
|
||||||
|
func (pe *PodEvictor) EvictPod(ctx context.Context, pod *v1.Pod, node *v1.Node, strategy string, reasons ...string) (bool, error) {
|
||||||
|
reason := strategy
|
||||||
|
if len(reasons) > 0 {
|
||||||
|
reason += " (" + strings.Join(reasons, ", ") + ")"
|
||||||
|
}
|
||||||
|
if pe.maxPodsToEvictPerNode != nil && pe.nodepodCount[node]+1 > *pe.maxPodsToEvictPerNode {
|
||||||
|
if pe.metricsEnabled {
|
||||||
|
metrics.PodsEvicted.With(map[string]string{"result": "maximum number of pods per node reached", "strategy": strategy, "namespace": pod.Namespace, "node": node.Name}).Inc()
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("Maximum number %v of evicted pods per %q node reached", *pe.maxPodsToEvictPerNode, node.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pe.maxPodsToEvictPerNamespace != nil && pe.namespacePodCount[pod.Namespace]+1 > *pe.maxPodsToEvictPerNamespace {
|
||||||
|
if pe.metricsEnabled {
|
||||||
|
metrics.PodsEvicted.With(map[string]string{"result": "maximum number of pods per namespace reached", "strategy": strategy, "namespace": pod.Namespace, "node": node.Name}).Inc()
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("Maximum number %v of evicted pods per %q namespace reached", *pe.maxPodsToEvictPerNamespace, pod.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := evictPod(ctx, pe.client, pod, pe.policyGroupVersion)
|
||||||
|
if err != nil {
|
||||||
|
// err is used only for logging purposes
|
||||||
|
klog.ErrorS(err, "Error evicting pod", "pod", klog.KObj(pod), "reason", reason)
|
||||||
|
if pe.metricsEnabled {
|
||||||
|
metrics.PodsEvicted.With(map[string]string{"result": "error", "strategy": strategy, "namespace": pod.Namespace, "node": node.Name}).Inc()
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pe.nodepodCount[node]++
|
||||||
|
pe.namespacePodCount[pod.Namespace]++
|
||||||
|
|
||||||
|
if pe.metricsEnabled {
|
||||||
|
metrics.PodsEvicted.With(map[string]string{"result": "success", "strategy": strategy, "namespace": pod.Namespace, "node": node.Name}).Inc()
|
||||||
|
}
|
||||||
|
|
||||||
|
if pe.dryRun {
|
||||||
|
klog.V(1).InfoS("Evicted pod in dry run mode", "pod", klog.KObj(pod), "reason", reason, "strategy", strategy, "node", node.Name)
|
||||||
|
} else {
|
||||||
|
klog.V(1).InfoS("Evicted pod", "pod", klog.KObj(pod), "reason", reason, "strategy", strategy, "node", node.Name)
|
||||||
|
eventBroadcaster := record.NewBroadcaster()
|
||||||
|
eventBroadcaster.StartStructuredLogging(3)
|
||||||
|
eventBroadcaster.StartRecordingToSink(&clientcorev1.EventSinkImpl{Interface: pe.client.CoreV1().Events(pod.Namespace)})
|
||||||
|
r := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "sigs.k8s.io.descheduler"})
|
||||||
|
r.Event(pod, v1.EventTypeNormal, "Descheduled", fmt.Sprintf("pod evicted by sigs.k8s.io/descheduler%s", reason))
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func evictPod(ctx context.Context, client clientset.Interface, pod *v1.Pod, policyGroupVersion string) error {
|
||||||
deleteOptions := &metav1.DeleteOptions{}
|
deleteOptions := &metav1.DeleteOptions{}
|
||||||
// GracePeriodSeconds ?
|
// GracePeriodSeconds ?
|
||||||
eviction := &policy.Eviction{
|
eviction := &policy.Eviction{
|
||||||
@@ -49,20 +184,181 @@ func EvictPod(client clientset.Interface, pod *v1.Pod, policyGroupVersion string
|
|||||||
},
|
},
|
||||||
DeleteOptions: deleteOptions,
|
DeleteOptions: deleteOptions,
|
||||||
}
|
}
|
||||||
err := client.PolicyV1beta1().Evictions(eviction.Namespace).Evict(eviction)
|
err := client.PolicyV1beta1().Evictions(eviction.Namespace).Evict(ctx, eviction)
|
||||||
|
|
||||||
if err == nil {
|
if apierrors.IsTooManyRequests(err) {
|
||||||
eventBroadcaster := record.NewBroadcaster()
|
return fmt.Errorf("error when evicting pod (ignoring) %q: %v", pod.Name, err)
|
||||||
eventBroadcaster.StartLogging(klog.V(3).Infof)
|
}
|
||||||
eventBroadcaster.StartRecordingToSink(&clientcorev1.EventSinkImpl{Interface: client.CoreV1().Events(pod.Namespace)})
|
if apierrors.IsNotFound(err) {
|
||||||
r := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "sigs.k8s.io.descheduler"})
|
return fmt.Errorf("pod not found when evicting %q: %v", pod.Name, err)
|
||||||
r.Event(pod, v1.EventTypeNormal, "Descheduled", "pod evicted by sigs.k8s.io/descheduler")
|
}
|
||||||
return true, nil
|
return err
|
||||||
} else if apierrors.IsTooManyRequests(err) {
|
}
|
||||||
return false, fmt.Errorf("error when evicting pod (ignoring) %q: %v", pod.Name, err)
|
|
||||||
} else if apierrors.IsNotFound(err) {
|
type Options struct {
|
||||||
return true, fmt.Errorf("pod not found when evicting %q: %v", pod.Name, err)
|
priority *int32
|
||||||
} else {
|
nodeFit bool
|
||||||
return false, err
|
labelSelector labels.Selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPriorityThreshold sets a threshold for pod's priority class.
|
||||||
|
// Any pod whose priority class is lower is evictable.
|
||||||
|
func WithPriorityThreshold(priority int32) func(opts *Options) {
|
||||||
|
return func(opts *Options) {
|
||||||
|
var p int32 = priority
|
||||||
|
opts.priority = &p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithNodeFit sets whether or not to consider taints, node selectors,
|
||||||
|
// and pod affinity when evicting. A pod whose tolerations, node selectors,
|
||||||
|
// and affinity match a node other than the one it is currently running on
|
||||||
|
// is evictable.
|
||||||
|
func WithNodeFit(nodeFit bool) func(opts *Options) {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.nodeFit = nodeFit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLabelSelector sets whether or not to apply label filtering when evicting.
|
||||||
|
// Any pod matching the label selector is considered evictable.
|
||||||
|
func WithLabelSelector(labelSelector labels.Selector) func(opts *Options) {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.labelSelector = labelSelector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type constraint func(pod *v1.Pod) error
|
||||||
|
|
||||||
|
type evictable struct {
|
||||||
|
constraints []constraint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evictable provides an implementation of IsEvictable(IsEvictable(pod *v1.Pod) bool).
|
||||||
|
// The method accepts a list of options which allow to extend constraints
|
||||||
|
// which decides when a pod is considered evictable.
|
||||||
|
func (pe *PodEvictor) Evictable(opts ...func(opts *Options)) *evictable {
|
||||||
|
options := &Options{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
ev := &evictable{}
|
||||||
|
if pe.evictFailedBarePods {
|
||||||
|
ev.constraints = append(ev.constraints, func(pod *v1.Pod) error {
|
||||||
|
ownerRefList := podutil.OwnerRef(pod)
|
||||||
|
// Enable evictFailedBarePods to evict bare pods in failed phase
|
||||||
|
if len(ownerRefList) == 0 && pod.Status.Phase != v1.PodFailed {
|
||||||
|
return fmt.Errorf("pod does not have any ownerRefs and is not in failed phase")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ev.constraints = append(ev.constraints, func(pod *v1.Pod) error {
|
||||||
|
ownerRefList := podutil.OwnerRef(pod)
|
||||||
|
// Moved from IsEvictable function for backward compatibility
|
||||||
|
if len(ownerRefList) == 0 {
|
||||||
|
return fmt.Errorf("pod does not have any ownerRefs")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !pe.evictSystemCriticalPods {
|
||||||
|
ev.constraints = append(ev.constraints, func(pod *v1.Pod) error {
|
||||||
|
// Moved from IsEvictable function to allow for disabling
|
||||||
|
if utils.IsCriticalPriorityPod(pod) {
|
||||||
|
return fmt.Errorf("pod has system critical priority")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if options.priority != nil {
|
||||||
|
ev.constraints = append(ev.constraints, func(pod *v1.Pod) error {
|
||||||
|
if IsPodEvictableBasedOnPriority(pod, *options.priority) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("pod has higher priority than specified priority class threshold")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !pe.evictLocalStoragePods {
|
||||||
|
ev.constraints = append(ev.constraints, func(pod *v1.Pod) error {
|
||||||
|
if utils.IsPodWithLocalStorage(pod) {
|
||||||
|
return fmt.Errorf("pod has local storage and descheduler is not configured with evictLocalStoragePods")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if pe.ignorePvcPods {
|
||||||
|
ev.constraints = append(ev.constraints, func(pod *v1.Pod) error {
|
||||||
|
if utils.IsPodWithPVC(pod) {
|
||||||
|
return fmt.Errorf("pod has a PVC and descheduler is configured to ignore PVC pods")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if options.nodeFit {
|
||||||
|
ev.constraints = append(ev.constraints, func(pod *v1.Pod) error {
|
||||||
|
if !nodeutil.PodFitsAnyOtherNode(pe.nodeIndexer, pod, pe.nodes) {
|
||||||
|
return fmt.Errorf("pod does not fit on any other node because of nodeSelector(s), Taint(s), or nodes marked as unschedulable")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if options.labelSelector != nil && !options.labelSelector.Empty() {
|
||||||
|
ev.constraints = append(ev.constraints, func(pod *v1.Pod) error {
|
||||||
|
if !options.labelSelector.Matches(labels.Set(pod.Labels)) {
|
||||||
|
return fmt.Errorf("pod labels do not match the labelSelector filter in the policy parameter")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ev
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEvictable decides when a pod is evictable
|
||||||
|
func (ev *evictable) IsEvictable(pod *v1.Pod) bool {
|
||||||
|
checkErrs := []error{}
|
||||||
|
|
||||||
|
ownerRefList := podutil.OwnerRef(pod)
|
||||||
|
if utils.IsDaemonsetPod(ownerRefList) {
|
||||||
|
checkErrs = append(checkErrs, fmt.Errorf("pod is a DaemonSet pod"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.IsMirrorPod(pod) {
|
||||||
|
checkErrs = append(checkErrs, fmt.Errorf("pod is a mirror pod"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.IsStaticPod(pod) {
|
||||||
|
checkErrs = append(checkErrs, fmt.Errorf("pod is a static pod"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.IsPodTerminating(pod) {
|
||||||
|
checkErrs = append(checkErrs, fmt.Errorf("pod is terminating"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range ev.constraints {
|
||||||
|
if err := c(pod); err != nil {
|
||||||
|
checkErrs = append(checkErrs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(checkErrs) > 0 && !HaveEvictAnnotation(pod) {
|
||||||
|
klog.V(4).InfoS("Pod lacks an eviction annotation and fails the following checks", "pod", klog.KObj(pod), "checks", errors.NewAggregate(checkErrs).Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// HaveEvictAnnotation checks if the pod have evict annotation
|
||||||
|
func HaveEvictAnnotation(pod *v1.Pod) bool {
|
||||||
|
_, found := pod.ObjectMeta.Annotations[evictPodAnnotationKey]
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPodEvictableBasedOnPriority checks if the given pod is evictable based on priority resolved from pod Spec.
|
||||||
|
func IsPodEvictableBasedOnPriority(pod *v1.Pod, priority int32) bool {
|
||||||
|
return pod.Spec.Priority == nil || *pod.Spec.Priority < priority
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,37 +17,45 @@ limitations under the License.
|
|||||||
package evictions
|
package evictions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/api/core/v1"
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
policyv1 "k8s.io/api/policy/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
core "k8s.io/client-go/testing"
|
core "k8s.io/client-go/testing"
|
||||||
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/utils"
|
||||||
"sigs.k8s.io/descheduler/test"
|
"sigs.k8s.io/descheduler/test"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEvictPod(t *testing.T) {
|
func TestEvictPod(t *testing.T) {
|
||||||
node1 := test.BuildTestNode("node1", 1000, 2000, 9)
|
ctx := context.Background()
|
||||||
pod1 := test.BuildTestPod("p1", 400, 0, "node1")
|
node1 := test.BuildTestNode("node1", 1000, 2000, 9, nil)
|
||||||
|
pod1 := test.BuildTestPod("p1", 400, 0, "node1", nil)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
description string
|
description string
|
||||||
node *v1.Node
|
node *v1.Node
|
||||||
pod *v1.Pod
|
pod *v1.Pod
|
||||||
pods []v1.Pod
|
pods []v1.Pod
|
||||||
want bool
|
want error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "test pod eviction - pod present",
|
description: "test pod eviction - pod present",
|
||||||
node: node1,
|
node: node1,
|
||||||
pod: pod1,
|
pod: pod1,
|
||||||
pods: []v1.Pod{*pod1},
|
pods: []v1.Pod{*pod1},
|
||||||
want: true,
|
want: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "test pod eviction - pod absent",
|
description: "test pod eviction - pod absent",
|
||||||
node: node1,
|
node: node1,
|
||||||
pod: pod1,
|
pod: pod1,
|
||||||
pods: []v1.Pod{*test.BuildTestPod("p2", 400, 0, "node1"), *test.BuildTestPod("p3", 450, 0, "node1")},
|
pods: []v1.Pod{*test.BuildTestPod("p2", 400, 0, "node1", nil), *test.BuildTestPod("p3", 450, 0, "node1", nil)},
|
||||||
want: true,
|
want: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +64,651 @@ func TestEvictPod(t *testing.T) {
|
|||||||
fakeClient.Fake.AddReactor("list", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
fakeClient.Fake.AddReactor("list", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
||||||
return true, &v1.PodList{Items: test.pods}, nil
|
return true, &v1.PodList{Items: test.pods}, nil
|
||||||
})
|
})
|
||||||
got, _ := EvictPod(fakeClient, test.pod, "v1", false)
|
got := evictPod(ctx, fakeClient, test.pod, "v1")
|
||||||
if got != test.want {
|
if got != test.want {
|
||||||
t.Errorf("Test error for Desc: %s. Expected %v pod eviction to be %v, got %v", test.description, test.pod.Name, test.want, got)
|
t.Errorf("Test error for Desc: %s. Expected %v pod eviction to be %v, got %v", test.description, test.pod.Name, test.want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsEvictable(t *testing.T) {
|
||||||
|
n1 := test.BuildTestNode("node1", 1000, 2000, 13, nil)
|
||||||
|
lowPriority := int32(800)
|
||||||
|
highPriority := int32(900)
|
||||||
|
|
||||||
|
nodeTaintKey := "hardware"
|
||||||
|
nodeTaintValue := "gpu"
|
||||||
|
|
||||||
|
nodeLabelKey := "datacenter"
|
||||||
|
nodeLabelValue := "east"
|
||||||
|
type testCase struct {
|
||||||
|
description string
|
||||||
|
pods []*v1.Pod
|
||||||
|
nodes []*v1.Node
|
||||||
|
evictFailedBarePods bool
|
||||||
|
evictLocalStoragePods bool
|
||||||
|
evictSystemCriticalPods bool
|
||||||
|
priorityThreshold *int32
|
||||||
|
nodeFit bool
|
||||||
|
result bool
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
description: "Failed pod eviction with no ownerRefs",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("bare_pod_failed", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.Status.Phase = v1.PodFailed
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictFailedBarePods: false,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Normal pod eviction with no ownerRefs and evictFailedBarePods enabled",
|
||||||
|
pods: []*v1.Pod{test.BuildTestPod("bare_pod", 400, 0, n1.Name, nil)},
|
||||||
|
evictFailedBarePods: true,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Failed pod eviction with no ownerRefs",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("bare_pod_failed_but_can_be_evicted", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.Status.Phase = v1.PodFailed
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictFailedBarePods: true,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Normal pod eviction with normal ownerRefs",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Normal pod eviction with normal ownerRefs and descheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p2", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Normal pod eviction with replicaSet ownerRefs",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p3", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Normal pod eviction with replicaSet ownerRefs and descheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p4", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetReplicaSetOwnerRefList()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Normal pod eviction with statefulSet ownerRefs",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p18", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Normal pod eviction with statefulSet ownerRefs and descheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p19", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetStatefulSetOwnerRefList()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod not evicted because it is bound to a PV and evictLocalStoragePods = false",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p5", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.Volumes = []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: "sample",
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
|
||||||
|
EmptyDir: &v1.EmptyDirVolumeSource{
|
||||||
|
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted because it is bound to a PV and evictLocalStoragePods = true",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p6", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.Volumes = []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: "sample",
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
|
||||||
|
EmptyDir: &v1.EmptyDirVolumeSource{
|
||||||
|
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: true,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted because it is bound to a PV and evictLocalStoragePods = false, but it has scheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p7", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.Volumes = []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: "sample",
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
|
||||||
|
EmptyDir: &v1.EmptyDirVolumeSource{
|
||||||
|
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod not evicted becasuse it is part of a daemonSet",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p8", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetDaemonSetOwnerRefList()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted becasuse it is part of a daemonSet, but it has scheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p9", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetDaemonSetOwnerRefList()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod not evicted becasuse it is a mirror poddsa",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p10", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Annotations = test.GetMirrorPodAnnotation()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted becasuse it is a mirror pod, but it has scheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p11", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Annotations = test.GetMirrorPodAnnotation()
|
||||||
|
pod.Annotations["descheduler.alpha.kubernetes.io/evict"] = "true"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod not evicted becasuse it has system critical priority",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p12", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
priority := utils.SystemCriticalPriority
|
||||||
|
pod.Spec.Priority = &priority
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted becasuse it has system critical priority, but it has scheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p13", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
priority := utils.SystemCriticalPriority
|
||||||
|
pod.Spec.Priority = &priority
|
||||||
|
pod.Annotations = map[string]string{
|
||||||
|
"descheduler.alpha.kubernetes.io/evict": "true",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod not evicted becasuse it has a priority higher than the configured priority threshold",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p14", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.Priority = &highPriority
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
priorityThreshold: &lowPriority,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted becasuse it has a priority higher than the configured priority threshold, but it has scheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p15", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
||||||
|
pod.Spec.Priority = &highPriority
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
priorityThreshold: &lowPriority,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted becasuse it has system critical priority, but evictSystemCriticalPods = true",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p16", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
priority := utils.SystemCriticalPriority
|
||||||
|
pod.Spec.Priority = &priority
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: true,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted becasuse it has system critical priority, but evictSystemCriticalPods = true and it has scheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p16", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
||||||
|
priority := utils.SystemCriticalPriority
|
||||||
|
pod.Spec.Priority = &priority
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: true,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted becasuse it has a priority higher than the configured priority threshold, but evictSystemCriticalPods = true",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p17", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.Priority = &highPriority
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: true,
|
||||||
|
priorityThreshold: &lowPriority,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod is evicted becasuse it has a priority higher than the configured priority threshold, but evictSystemCriticalPods = true and it has scheduler.alpha.kubernetes.io/evict annotation",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p17", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
||||||
|
pod.Spec.Priority = &highPriority
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: true,
|
||||||
|
priorityThreshold: &lowPriority,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod with no tolerations running on normal node, all other nodes tainted",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("node2", 1000, 2000, 13, func(node *v1.Node) {
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestNode("node3", 1000, 2000, 13, func(node *v1.Node) {
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
nodeFit: true,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Pod with correct tolerations running on normal node, all other nodes tainted",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.Tolerations = []v1.Toleration{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("node2", 1000, 2000, 13, func(node *v1.Node) {
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestNode("node3", 1000, 2000, 13, func(node *v1.Node) {
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
nodeFit: true,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod with incorrect node selector",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: "fail",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("node2", 1000, 2000, 13, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestNode("node3", 1000, 2000, 13, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
nodeFit: true,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Pod with correct node selector",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("node2", 1000, 2000, 13, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestNode("node3", 1000, 2000, 13, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
nodeFit: true,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod with correct node selector, but only available node doesn't have enough CPU",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p1", 12, 8, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("node2-TEST", 10, 16, 10, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestNode("node3-TEST", 10, 16, 10, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
nodeFit: true,
|
||||||
|
result: false,
|
||||||
|
}, {
|
||||||
|
description: "Pod with correct node selector, and one node has enough memory",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p1", 12, 8, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestPod("node2-pod-10GB-mem", 20, 10, "node2", func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.Labels = map[string]string{
|
||||||
|
"test": "true",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestPod("node3-pod-10GB-mem", 20, 10, "node3", func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.Labels = map[string]string{
|
||||||
|
"test": "true",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("node2", 100, 16, 10, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestNode("node3", 100, 20, 10, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
nodeFit: true,
|
||||||
|
result: true,
|
||||||
|
}, {
|
||||||
|
description: "Pod with correct node selector, but both nodes don't have enough memory",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("p1", 12, 8, n1.Name, func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestPod("node2-pod-10GB-mem", 10, 10, "node2", func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.Labels = map[string]string{
|
||||||
|
"test": "true",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestPod("node3-pod-10GB-mem", 10, 10, "node3", func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.Labels = map[string]string{
|
||||||
|
"test": "true",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("node2", 100, 16, 10, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestNode("node3", 100, 16, 10, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
evictLocalStoragePods: false,
|
||||||
|
evictSystemCriticalPods: false,
|
||||||
|
nodeFit: true,
|
||||||
|
result: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
|
||||||
|
t.Run(test.description, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
nodes := append(test.nodes, n1)
|
||||||
|
|
||||||
|
var objs []runtime.Object
|
||||||
|
for _, node := range test.nodes {
|
||||||
|
objs = append(objs, node)
|
||||||
|
}
|
||||||
|
for _, pod := range test.pods {
|
||||||
|
objs = append(objs, pod)
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeClient := fake.NewSimpleClientset(objs...)
|
||||||
|
|
||||||
|
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
podInformer := sharedInformerFactory.Core().V1().Pods()
|
||||||
|
|
||||||
|
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Build get pods assigned to node function error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedInformerFactory.Start(ctx.Done())
|
||||||
|
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||||
|
|
||||||
|
podEvictor := &PodEvictor{
|
||||||
|
client: fakeClient,
|
||||||
|
nodes: nodes,
|
||||||
|
nodeIndexer: getPodsAssignedToNode,
|
||||||
|
policyGroupVersion: policyv1.SchemeGroupVersion.String(),
|
||||||
|
dryRun: false,
|
||||||
|
maxPodsToEvictPerNode: nil,
|
||||||
|
maxPodsToEvictPerNamespace: nil,
|
||||||
|
evictLocalStoragePods: test.evictLocalStoragePods,
|
||||||
|
evictSystemCriticalPods: test.evictSystemCriticalPods,
|
||||||
|
evictFailedBarePods: test.evictFailedBarePods,
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts []func(opts *Options)
|
||||||
|
if test.priorityThreshold != nil {
|
||||||
|
opts = append(opts, WithPriorityThreshold(*test.priorityThreshold))
|
||||||
|
}
|
||||||
|
if test.nodeFit {
|
||||||
|
opts = append(opts, WithNodeFit(true))
|
||||||
|
}
|
||||||
|
evictable := podEvictor.Evictable(opts...)
|
||||||
|
|
||||||
|
result := evictable.IsEvictable(test.pods[0])
|
||||||
|
if result != test.result {
|
||||||
|
t.Errorf("IsEvictable should return for pod %s %t, but it returns %t", test.pods[0].Name, test.result, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestPodTypes(t *testing.T) {
|
||||||
|
n1 := test.BuildTestNode("node1", 1000, 2000, 9, nil)
|
||||||
|
p1 := test.BuildTestPod("p1", 400, 0, n1.Name, nil)
|
||||||
|
|
||||||
|
// These won't be evicted.
|
||||||
|
p2 := test.BuildTestPod("p2", 400, 0, n1.Name, nil)
|
||||||
|
p3 := test.BuildTestPod("p3", 400, 0, n1.Name, nil)
|
||||||
|
p4 := test.BuildTestPod("p4", 400, 0, n1.Name, nil)
|
||||||
|
|
||||||
|
p1.ObjectMeta.OwnerReferences = test.GetReplicaSetOwnerRefList()
|
||||||
|
// The following 4 pods won't get evicted.
|
||||||
|
// A daemonset.
|
||||||
|
//p2.Annotations = test.GetDaemonSetAnnotation()
|
||||||
|
p2.ObjectMeta.OwnerReferences = test.GetDaemonSetOwnerRefList()
|
||||||
|
// A pod with local storage.
|
||||||
|
p3.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
||||||
|
p3.Spec.Volumes = []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: "sample",
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
|
||||||
|
EmptyDir: &v1.EmptyDirVolumeSource{
|
||||||
|
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// A Mirror Pod.
|
||||||
|
p4.Annotations = test.GetMirrorPodAnnotation()
|
||||||
|
if !utils.IsMirrorPod(p4) {
|
||||||
|
t.Errorf("Expected p4 to be a mirror pod.")
|
||||||
|
}
|
||||||
|
if !utils.IsPodWithLocalStorage(p3) {
|
||||||
|
t.Errorf("Expected p3 to be a pod with local storage.")
|
||||||
|
}
|
||||||
|
ownerRefList := podutil.OwnerRef(p2)
|
||||||
|
if !utils.IsDaemonsetPod(ownerRefList) {
|
||||||
|
t.Errorf("Expected p2 to be a daemonset pod.")
|
||||||
|
}
|
||||||
|
ownerRefList = podutil.OwnerRef(p1)
|
||||||
|
if utils.IsDaemonsetPod(ownerRefList) || utils.IsPodWithLocalStorage(p1) || utils.IsCriticalPriorityPod(p1) || utils.IsMirrorPod(p1) || utils.IsStaticPod(p1) {
|
||||||
|
t.Errorf("Expected p1 to be a normal pod.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
99
pkg/descheduler/leaderelection.go
Normal file
99
pkg/descheduler/leaderelection.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package descheduler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"k8s.io/apimachinery/pkg/util/uuid"
|
||||||
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/tools/leaderelection"
|
||||||
|
"k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||||
|
componentbaseconfig "k8s.io/component-base/config"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLeaderElection starts the leader election code loop
|
||||||
|
func NewLeaderElection(
|
||||||
|
run func() error,
|
||||||
|
client clientset.Interface,
|
||||||
|
LeaderElectionConfig *componentbaseconfig.LeaderElectionConfiguration,
|
||||||
|
ctx context.Context,
|
||||||
|
) error {
|
||||||
|
var id string
|
||||||
|
|
||||||
|
if hostname, err := os.Hostname(); err != nil {
|
||||||
|
// on errors, make sure we're unique
|
||||||
|
id = string(uuid.NewUUID())
|
||||||
|
} else {
|
||||||
|
// add a uniquifier so that two processes on the same host don't accidentally both become active
|
||||||
|
id = hostname + "_" + string(uuid.NewUUID())
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.V(3).Infof("Assigned unique lease holder id: %s", id)
|
||||||
|
|
||||||
|
if len(LeaderElectionConfig.ResourceNamespace) == 0 {
|
||||||
|
return fmt.Errorf("namespace may not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(LeaderElectionConfig.ResourceName) == 0 {
|
||||||
|
return fmt.Errorf("name may not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
lock, err := resourcelock.New(
|
||||||
|
LeaderElectionConfig.ResourceLock,
|
||||||
|
LeaderElectionConfig.ResourceNamespace,
|
||||||
|
LeaderElectionConfig.ResourceName,
|
||||||
|
client.CoreV1(),
|
||||||
|
client.CoordinationV1(),
|
||||||
|
resourcelock.ResourceLockConfig{
|
||||||
|
Identity: id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to create leader election lock: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{
|
||||||
|
Lock: lock,
|
||||||
|
ReleaseOnCancel: true,
|
||||||
|
LeaseDuration: LeaderElectionConfig.LeaseDuration.Duration,
|
||||||
|
RenewDeadline: LeaderElectionConfig.RenewDeadline.Duration,
|
||||||
|
RetryPeriod: LeaderElectionConfig.RetryPeriod.Duration,
|
||||||
|
Callbacks: leaderelection.LeaderCallbacks{
|
||||||
|
OnStartedLeading: func(ctx context.Context) {
|
||||||
|
klog.V(1).InfoS("Started leading")
|
||||||
|
err := run()
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnStoppedLeading: func() {
|
||||||
|
klog.V(1).InfoS("Leader lost")
|
||||||
|
},
|
||||||
|
OnNewLeader: func(identity string) {
|
||||||
|
// Just got the lock
|
||||||
|
if identity == id {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
klog.V(1).Infof("New leader elected: %v", identity)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -17,40 +17,38 @@ limitations under the License.
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
corelisters "k8s.io/client-go/listers/core/v1"
|
"k8s.io/klog/v2"
|
||||||
"k8s.io/client-go/tools/cache"
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
"k8s.io/klog"
|
|
||||||
"sigs.k8s.io/descheduler/pkg/utils"
|
"sigs.k8s.io/descheduler/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadyNodes returns ready nodes irrespective of whether they are
|
// ReadyNodes returns ready nodes irrespective of whether they are
|
||||||
// schedulable or not.
|
// schedulable or not.
|
||||||
func ReadyNodes(client clientset.Interface, nodeSelector string, stopChannel <-chan struct{}) ([]*v1.Node, error) {
|
func ReadyNodes(ctx context.Context, client clientset.Interface, nodeInformer coreinformers.NodeInformer, nodeSelector string) ([]*v1.Node, error) {
|
||||||
ns, err := labels.Parse(nodeSelector)
|
ns, err := labels.Parse(nodeSelector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*v1.Node{}, err
|
return []*v1.Node{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var nodes []*v1.Node
|
var nodes []*v1.Node
|
||||||
nl := GetNodeLister(client, stopChannel)
|
// err is defined above
|
||||||
if nl != nil {
|
if nodes, err = nodeInformer.Lister().List(ns); err != nil {
|
||||||
// err is defined above
|
return []*v1.Node{}, err
|
||||||
if nodes, err = nl.List(ns); err != nil {
|
|
||||||
return []*v1.Node{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(nodes) == 0 {
|
if len(nodes) == 0 {
|
||||||
klog.V(2).Infof("node lister returned empty list, now fetch directly")
|
klog.V(2).InfoS("Node lister returned empty list, now fetch directly")
|
||||||
|
|
||||||
nItems, err := client.CoreV1().Nodes().List(metav1.ListOptions{LabelSelector: nodeSelector})
|
nItems, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{LabelSelector: nodeSelector})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*v1.Node{}, err
|
return []*v1.Node{}, err
|
||||||
}
|
}
|
||||||
@@ -74,22 +72,6 @@ func ReadyNodes(client clientset.Interface, nodeSelector string, stopChannel <-c
|
|||||||
return readyNodes, nil
|
return readyNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetNodeLister(client clientset.Interface, stopChannel <-chan struct{}) corelisters.NodeLister {
|
|
||||||
if stopChannel == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
listWatcher := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), "nodes", v1.NamespaceAll, fields.Everything())
|
|
||||||
store := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
|
||||||
nodeLister := corelisters.NewNodeLister(store)
|
|
||||||
reflector := cache.NewReflector(listWatcher, &v1.Node{}, store, time.Hour)
|
|
||||||
go reflector.Run(stopChannel)
|
|
||||||
|
|
||||||
// To give some time so that listing works, chosen randomly
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
|
|
||||||
return nodeLister
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsReady checks if the descheduler could run against given node.
|
// IsReady checks if the descheduler could run against given node.
|
||||||
func IsReady(node *v1.Node) bool {
|
func IsReady(node *v1.Node) bool {
|
||||||
for i := range node.Status.Conditions {
|
for i := range node.Status.Conditions {
|
||||||
@@ -99,66 +81,208 @@ func IsReady(node *v1.Node) bool {
|
|||||||
// - NodeOutOfDisk condition status is ConditionFalse,
|
// - NodeOutOfDisk condition status is ConditionFalse,
|
||||||
// - NodeNetworkUnavailable condition status is ConditionFalse.
|
// - NodeNetworkUnavailable condition status is ConditionFalse.
|
||||||
if cond.Type == v1.NodeReady && cond.Status != v1.ConditionTrue {
|
if cond.Type == v1.NodeReady && cond.Status != v1.ConditionTrue {
|
||||||
klog.V(1).Infof("Ignoring node %v with %v condition status %v", node.Name, cond.Type, cond.Status)
|
klog.V(1).InfoS("Ignoring node", "node", klog.KObj(node), "condition", cond.Type, "status", cond.Status)
|
||||||
return false
|
return false
|
||||||
} /*else if cond.Type == v1.NodeOutOfDisk && cond.Status != v1.ConditionFalse {
|
} /*else if cond.Type == v1.NodeOutOfDisk && cond.Status != v1.ConditionFalse {
|
||||||
klog.V(4).Infof("Ignoring node %v with %v condition status %v", node.Name, cond.Type, cond.Status)
|
klog.V(4).InfoS("Ignoring node with condition status", "node", klog.KObj(node.Name), "condition", cond.Type, "status", cond.Status)
|
||||||
return false
|
return false
|
||||||
} else if cond.Type == v1.NodeNetworkUnavailable && cond.Status != v1.ConditionFalse {
|
} else if cond.Type == v1.NodeNetworkUnavailable && cond.Status != v1.ConditionFalse {
|
||||||
klog.V(4).Infof("Ignoring node %v with %v condition status %v", node.Name, cond.Type, cond.Status)
|
klog.V(4).InfoS("Ignoring node with condition status", "node", klog.KObj(node.Name), "condition", cond.Type, "status", cond.Status)
|
||||||
return false
|
return false
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
// Ignore nodes that are marked unschedulable
|
// Ignore nodes that are marked unschedulable
|
||||||
/*if node.Spec.Unschedulable {
|
/*if node.Spec.Unschedulable {
|
||||||
klog.V(4).Infof("Ignoring node %v since it is unschedulable", node.Name)
|
klog.V(4).InfoS("Ignoring node since it is unschedulable", "node", klog.KObj(node.Name))
|
||||||
return false
|
return false
|
||||||
}*/
|
}*/
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNodeUnschedulable checks if the node is unschedulable. This is helper function to check only in case of
|
// NodeFit returns true if the provided pod can be scheduled onto the provided node.
|
||||||
// underutilized node so that they won't be accounted for.
|
// This function is used when the NodeFit pod filtering feature of the Descheduler is enabled.
|
||||||
func IsNodeUnschedulable(node *v1.Node) bool {
|
// This function currently considers a subset of the Kubernetes Scheduler's predicates when
|
||||||
return node.Spec.Unschedulable
|
// deciding if a pod would fit on a node, but more predicates may be added in the future.
|
||||||
|
func NodeFit(nodeIndexer podutil.GetPodsAssignedToNodeFunc, pod *v1.Pod, node *v1.Node) []error {
|
||||||
|
// Check node selector and required affinity
|
||||||
|
var errors []error
|
||||||
|
if ok, err := utils.PodMatchNodeSelector(pod, node); err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
} else if !ok {
|
||||||
|
errors = append(errors, fmt.Errorf("pod node selector does not match the node label"))
|
||||||
|
}
|
||||||
|
// Check taints (we only care about NoSchedule and NoExecute taints)
|
||||||
|
ok := utils.TolerationsTolerateTaintsWithFilter(pod.Spec.Tolerations, node.Spec.Taints, func(taint *v1.Taint) bool {
|
||||||
|
return taint.Effect == v1.TaintEffectNoSchedule || taint.Effect == v1.TaintEffectNoExecute
|
||||||
|
})
|
||||||
|
if !ok {
|
||||||
|
errors = append(errors, fmt.Errorf("pod does not tolerate taints on the node"))
|
||||||
|
}
|
||||||
|
// Check if the pod can fit on a node based off it's requests
|
||||||
|
ok, reqErrors := fitsRequest(nodeIndexer, pod, node)
|
||||||
|
if !ok {
|
||||||
|
errors = append(errors, reqErrors...)
|
||||||
|
}
|
||||||
|
// Check if node is schedulable
|
||||||
|
if IsNodeUnschedulable(node) {
|
||||||
|
errors = append(errors, fmt.Errorf("node is not schedulable"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// PodFitsAnyNode checks if the given pod fits any of the given nodes, based on
|
// PodFitsAnyOtherNode checks if the given pod will fit any of the given nodes, besides the node
|
||||||
// multiple criteria, like, pod node selector matching the node label, node
|
// the pod is already running on. The predicates used to determine if the pod will fit can be found in the NodeFit function.
|
||||||
// being schedulable or not.
|
func PodFitsAnyOtherNode(nodeIndexer podutil.GetPodsAssignedToNodeFunc, pod *v1.Pod, nodes []*v1.Node) bool {
|
||||||
func PodFitsAnyNode(pod *v1.Pod, nodes []*v1.Node) bool {
|
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
|
// Skip node pod is already on
|
||||||
ok, err := utils.PodMatchNodeSelector(pod, node)
|
if node.Name == pod.Spec.NodeName {
|
||||||
if err != nil || !ok {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ok {
|
|
||||||
if !IsNodeUnschedulable(node) {
|
errors := NodeFit(nodeIndexer, pod, node)
|
||||||
klog.V(2).Infof("Pod %v can possibly be scheduled on %v", pod.Name, node.Name)
|
if len(errors) == 0 {
|
||||||
return true
|
klog.V(4).InfoS("Pod fits on node", "pod", klog.KObj(pod), "node", klog.KObj(node))
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
klog.V(4).InfoS("Pod does not fit on node", "pod", klog.KObj(pod), "node", klog.KObj(node))
|
||||||
|
for _, err := range errors {
|
||||||
|
klog.V(4).InfoS(err.Error())
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// PodFitsCurrentNode checks if the given pod fits on the given node if the pod
|
// PodFitsAnyNode checks if the given pod will fit any of the given nodes. The predicates used
|
||||||
// node selector matches the node label.
|
// to determine if the pod will fit can be found in the NodeFit function.
|
||||||
func PodFitsCurrentNode(pod *v1.Pod, node *v1.Node) bool {
|
func PodFitsAnyNode(nodeIndexer podutil.GetPodsAssignedToNodeFunc, pod *v1.Pod, nodes []*v1.Node) bool {
|
||||||
ok, err := utils.PodMatchNodeSelector(pod, node)
|
for _, node := range nodes {
|
||||||
|
errors := NodeFit(nodeIndexer, pod, node)
|
||||||
if err != nil {
|
if len(errors) == 0 {
|
||||||
klog.Error(err)
|
klog.V(4).InfoS("Pod fits on node", "pod", klog.KObj(pod), "node", klog.KObj(node))
|
||||||
return false
|
return true
|
||||||
|
} else {
|
||||||
|
klog.V(4).InfoS("Pod does not fit on node", "pod", klog.KObj(pod), "node", klog.KObj(node))
|
||||||
|
for _, err := range errors {
|
||||||
|
klog.V(4).InfoS(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PodFitsCurrentNode checks if the given pod will fit onto the given node. The predicates used
|
||||||
|
// to determine if the pod will fit can be found in the NodeFit function.
|
||||||
|
func PodFitsCurrentNode(nodeIndexer podutil.GetPodsAssignedToNodeFunc, pod *v1.Pod, node *v1.Node) bool {
|
||||||
|
errors := NodeFit(nodeIndexer, pod, node)
|
||||||
|
if len(errors) == 0 {
|
||||||
|
klog.V(4).InfoS("Pod fits on node", "pod", klog.KObj(pod), "node", klog.KObj(node))
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
klog.V(4).InfoS("Pod does not fit on node", "pod", klog.KObj(pod), "node", klog.KObj(node))
|
||||||
|
for _, err := range errors {
|
||||||
|
klog.V(4).InfoS(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNodeUnschedulable checks if the node is unschedulable. This is a helper function to check only in case of
|
||||||
|
// underutilized node so that they won't be accounted for.
|
||||||
|
func IsNodeUnschedulable(node *v1.Node) bool {
|
||||||
|
return node.Spec.Unschedulable
|
||||||
|
}
|
||||||
|
|
||||||
|
// fitsRequest determines if a pod can fit on a node based on its resource requests. It returns true if
|
||||||
|
// the pod will fit.
|
||||||
|
func fitsRequest(nodeIndexer podutil.GetPodsAssignedToNodeFunc, pod *v1.Pod, node *v1.Node) (bool, []error) {
|
||||||
|
var insufficientResources []error
|
||||||
|
|
||||||
|
// Get pod requests
|
||||||
|
podRequests, _ := utils.PodRequestsAndLimits(pod)
|
||||||
|
resourceNames := make([]v1.ResourceName, 0, len(podRequests))
|
||||||
|
for name := range podRequests {
|
||||||
|
resourceNames = append(resourceNames, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
availableResources, err := nodeAvailableResources(nodeIndexer, node, resourceNames)
|
||||||
|
if err != nil {
|
||||||
|
return false, []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
podFitsOnNode := true
|
||||||
|
for _, resource := range resourceNames {
|
||||||
|
podResourceRequest := podRequests[resource]
|
||||||
|
availableResource, ok := availableResources[resource]
|
||||||
|
if !ok || podResourceRequest.MilliValue() > availableResource.MilliValue() {
|
||||||
|
insufficientResources = append(insufficientResources, fmt.Errorf("insufficient %v", resource))
|
||||||
|
podFitsOnNode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return podFitsOnNode, insufficientResources
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeAvailableResources returns resources mapped to the quanitity available on the node.
|
||||||
|
func nodeAvailableResources(nodeIndexer podutil.GetPodsAssignedToNodeFunc, node *v1.Node, resourceNames []v1.ResourceName) (map[v1.ResourceName]*resource.Quantity, error) {
|
||||||
|
podsOnNode, err := podutil.ListPodsOnANode(node.Name, nodeIndexer, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodeUtilization := NodeUtilization(podsOnNode, resourceNames)
|
||||||
|
remainingResources := map[v1.ResourceName]*resource.Quantity{
|
||||||
|
v1.ResourceCPU: resource.NewMilliQuantity(node.Status.Allocatable.Cpu().MilliValue()-nodeUtilization[v1.ResourceCPU].MilliValue(), resource.DecimalSI),
|
||||||
|
v1.ResourceMemory: resource.NewQuantity(node.Status.Allocatable.Memory().Value()-nodeUtilization[v1.ResourceMemory].Value(), resource.BinarySI),
|
||||||
|
v1.ResourcePods: resource.NewQuantity(node.Status.Allocatable.Pods().Value()-nodeUtilization[v1.ResourcePods].Value(), resource.DecimalSI),
|
||||||
|
}
|
||||||
|
for _, name := range resourceNames {
|
||||||
|
if !IsBasicResource(name) {
|
||||||
|
if _, exists := node.Status.Allocatable[name]; exists {
|
||||||
|
allocatableResource := node.Status.Allocatable[name]
|
||||||
|
remainingResources[name] = resource.NewQuantity(allocatableResource.Value()-nodeUtilization[name].Value(), resource.DecimalSI)
|
||||||
|
} else {
|
||||||
|
remainingResources[name] = resource.NewQuantity(0, resource.DecimalSI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return remainingResources, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeUtilization returns the resources requested by the given pods. Only resources supplied in the resourceNames parameter are calculated.
|
||||||
|
func NodeUtilization(pods []*v1.Pod, resourceNames []v1.ResourceName) map[v1.ResourceName]*resource.Quantity {
|
||||||
|
totalReqs := map[v1.ResourceName]*resource.Quantity{
|
||||||
|
v1.ResourceCPU: resource.NewMilliQuantity(0, resource.DecimalSI),
|
||||||
|
v1.ResourceMemory: resource.NewQuantity(0, resource.BinarySI),
|
||||||
|
v1.ResourcePods: resource.NewQuantity(int64(len(pods)), resource.DecimalSI),
|
||||||
|
}
|
||||||
|
for _, name := range resourceNames {
|
||||||
|
if !IsBasicResource(name) {
|
||||||
|
totalReqs[name] = resource.NewQuantity(0, resource.DecimalSI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
req, _ := utils.PodRequestsAndLimits(pod)
|
||||||
|
for _, name := range resourceNames {
|
||||||
|
quantity, ok := req[name]
|
||||||
|
if ok && name != v1.ResourcePods {
|
||||||
|
// As Quantity.Add says: Add adds the provided y quantity to the current value. If the current value is zero,
|
||||||
|
// the format of the quantity will be updated to the format of y.
|
||||||
|
totalReqs[name].Add(quantity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalReqs
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBasicResource checks if resource is basic native.
|
||||||
|
func IsBasicResource(name v1.ResourceName) bool {
|
||||||
|
switch name {
|
||||||
|
case v1.ResourceCPU, v1.ResourceMemory, v1.ResourcePods:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
|
||||||
klog.V(1).Infof("Pod %v does not fit on node %v", pod.Name, node.Name)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
klog.V(3).Infof("Pod %v fits on node %v", pod.Name, node.Name)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,23 +17,28 @@ limitations under the License.
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
"sigs.k8s.io/descheduler/test"
|
"sigs.k8s.io/descheduler/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReadyNodes(t *testing.T) {
|
func TestReadyNodes(t *testing.T) {
|
||||||
node1 := test.BuildTestNode("node2", 1000, 2000, 9)
|
node1 := test.BuildTestNode("node2", 1000, 2000, 9, nil)
|
||||||
node2 := test.BuildTestNode("node3", 1000, 2000, 9)
|
node2 := test.BuildTestNode("node3", 1000, 2000, 9, nil)
|
||||||
node2.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeMemoryPressure, Status: v1.ConditionTrue}}
|
node2.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeMemoryPressure, Status: v1.ConditionTrue}}
|
||||||
node3 := test.BuildTestNode("node4", 1000, 2000, 9)
|
node3 := test.BuildTestNode("node4", 1000, 2000, 9, nil)
|
||||||
node3.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeNetworkUnavailable, Status: v1.ConditionTrue}}
|
node3.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeNetworkUnavailable, Status: v1.ConditionTrue}}
|
||||||
node4 := test.BuildTestNode("node5", 1000, 2000, 9)
|
node4 := test.BuildTestNode("node5", 1000, 2000, 9, nil)
|
||||||
node4.Spec.Unschedulable = true
|
node4.Spec.Unschedulable = true
|
||||||
node5 := test.BuildTestNode("node6", 1000, 2000, 9)
|
node5 := test.BuildTestNode("node6", 1000, 2000, 9, nil)
|
||||||
node5.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}}
|
node5.Status.Conditions = []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}}
|
||||||
|
|
||||||
if !IsReady(node1) {
|
if !IsReady(node1) {
|
||||||
@@ -55,14 +60,24 @@ func TestReadyNodes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadyNodesWithNodeSelector(t *testing.T) {
|
func TestReadyNodesWithNodeSelector(t *testing.T) {
|
||||||
node1 := test.BuildTestNode("node1", 1000, 2000, 9)
|
ctx := context.Background()
|
||||||
|
node1 := test.BuildTestNode("node1", 1000, 2000, 9, nil)
|
||||||
node1.Labels = map[string]string{"type": "compute"}
|
node1.Labels = map[string]string{"type": "compute"}
|
||||||
node2 := test.BuildTestNode("node2", 1000, 2000, 9)
|
node2 := test.BuildTestNode("node2", 1000, 2000, 9, nil)
|
||||||
node2.Labels = map[string]string{"type": "infra"}
|
node2.Labels = map[string]string{"type": "infra"}
|
||||||
|
|
||||||
fakeClient := fake.NewSimpleClientset(node1, node2)
|
fakeClient := fake.NewSimpleClientset(node1, node2)
|
||||||
nodeSelector := "type=compute"
|
nodeSelector := "type=compute"
|
||||||
nodes, _ := ReadyNodes(fakeClient, nodeSelector, nil)
|
|
||||||
|
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
nodeInformer := sharedInformerFactory.Core().V1().Nodes()
|
||||||
|
|
||||||
|
stopChannel := make(chan struct{})
|
||||||
|
sharedInformerFactory.Start(stopChannel)
|
||||||
|
sharedInformerFactory.WaitForCacheSync(stopChannel)
|
||||||
|
defer close(stopChannel)
|
||||||
|
|
||||||
|
nodes, _ := ReadyNodes(ctx, fakeClient, nodeInformer, nodeSelector)
|
||||||
|
|
||||||
if nodes[0].Name != "node1" {
|
if nodes[0].Name != "node1" {
|
||||||
t.Errorf("Expected node1, got %s", nodes[0].Name)
|
t.Errorf("Expected node1, got %s", nodes[0].Name)
|
||||||
@@ -135,13 +150,13 @@ func TestPodFitsCurrentNode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
node: &v1.Node{
|
node: test.BuildTestNode("node1", 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
Labels: map[string]string{
|
nodeLabelKey: nodeLabelValue,
|
||||||
nodeLabelKey: nodeLabelValue,
|
}
|
||||||
},
|
|
||||||
},
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
},
|
}),
|
||||||
success: true,
|
success: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -169,174 +184,582 @@ func TestPodFitsCurrentNode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
node: &v1.Node{
|
node: test.BuildTestNode("node1", 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
Labels: map[string]string{
|
nodeLabelKey: "no",
|
||||||
nodeLabelKey: "no",
|
}
|
||||||
},
|
|
||||||
},
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
},
|
}),
|
||||||
success: false,
|
success: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
actual := PodFitsCurrentNode(tc.pod, tc.node)
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
if actual != tc.success {
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
t.Errorf("Test %#v failed", tc.description)
|
defer cancel()
|
||||||
}
|
|
||||||
|
var objs []runtime.Object
|
||||||
|
objs = append(objs, tc.node)
|
||||||
|
objs = append(objs, tc.pod)
|
||||||
|
|
||||||
|
fakeClient := fake.NewSimpleClientset(objs...)
|
||||||
|
|
||||||
|
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
podInformer := sharedInformerFactory.Core().V1().Pods()
|
||||||
|
|
||||||
|
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Build get pods assigned to node function error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedInformerFactory.Start(ctx.Done())
|
||||||
|
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||||
|
|
||||||
|
actual := PodFitsCurrentNode(getPodsAssignedToNode, tc.pod, tc.node)
|
||||||
|
if actual != tc.success {
|
||||||
|
t.Errorf("Test %#v failed", tc.description)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPodFitsAnyNode(t *testing.T) {
|
func TestPodFitsAnyOtherNode(t *testing.T) {
|
||||||
|
|
||||||
nodeLabelKey := "kubernetes.io/desiredNode"
|
nodeLabelKey := "kubernetes.io/desiredNode"
|
||||||
nodeLabelValue := "yes"
|
nodeLabelValue := "yes"
|
||||||
|
nodeTaintKey := "hardware"
|
||||||
|
nodeTaintValue := "gpu"
|
||||||
|
|
||||||
|
// Staging node has no scheduling restrictions, but the pod always starts here and PodFitsAnyOtherNode() doesn't take into account the node the pod is running on.
|
||||||
|
nodeNames := []string{"node1", "node2", "stagingNode"}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
description string
|
description string
|
||||||
pod *v1.Pod
|
pod *v1.Pod
|
||||||
nodes []*v1.Node
|
nodes []*v1.Node
|
||||||
success bool
|
success bool
|
||||||
|
podsOnNodes []*v1.Pod
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "Pod expected to fit one of the nodes",
|
description: "Pod fits another node matching node affinity",
|
||||||
pod: &v1.Pod{
|
pod: test.BuildTestPod("p1", 0, 0, nodeNames[2], func(pod *v1.Pod) {
|
||||||
Spec: v1.PodSpec{
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
Affinity: &v1.Affinity{
|
nodeLabelKey: nodeLabelValue,
|
||||||
NodeAffinity: &v1.NodeAffinity{
|
}
|
||||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
}),
|
||||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
|
||||||
{
|
|
||||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
|
||||||
{
|
|
||||||
Key: nodeLabelKey,
|
|
||||||
Operator: "In",
|
|
||||||
Values: []string{
|
|
||||||
nodeLabelValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nodes: []*v1.Node{
|
nodes: []*v1.Node{
|
||||||
{
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
Labels: map[string]string{
|
nodeLabelKey: nodeLabelValue,
|
||||||
nodeLabelKey: nodeLabelValue,
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[1], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: "no",
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{},
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Pod expected to fit one of the nodes",
|
||||||
|
pod: test.BuildTestPod("p1", 0, 0, nodeNames[2], func(pod *v1.Pod) {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: "no",
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[1], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{},
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Pod expected to fit none of the nodes",
|
||||||
|
pod: test.BuildTestPod("p1", 0, 0, nodeNames[2], func(pod *v1.Pod) {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: "unfit1",
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[1], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: "unfit2",
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Nodes are unschedulable but labels match, should fail",
|
||||||
|
pod: test.BuildTestPod("p1", 0, 0, nodeNames[2], func(pod *v1.Pod) {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Unschedulable = true
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[1], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: "no",
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Both nodes are tained, should fail",
|
||||||
|
pod: test.BuildTestPod("p1", 2000, 2*1000*1000*1000, nodeNames[2], func(pod *v1.Pod) {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
},
|
}),
|
||||||
{
|
test.BuildTestNode(nodeNames[1], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
Labels: map[string]string{
|
nodeLabelKey: nodeLabelValue,
|
||||||
nodeLabelKey: "no",
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
},
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Two nodes matches node selector, one of them is tained, there is a pod on the available node, and requests are low, should pass",
|
||||||
|
pod: test.BuildTestPod("p1", 2000, 2*1000*1000*1000, nodeNames[2], func(pod *v1.Pod) {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[1], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{
|
||||||
|
test.BuildTestPod("test-pod", 12*1000, 20*1000*1000*1000, nodeNames[1], func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta = metav1.ObjectMeta{
|
||||||
|
Namespace: "test",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"test": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(40*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Pod expected to fit none of the nodes",
|
description: "Two nodes matches node selector, one of them is tained, but CPU requests are too big, should fail",
|
||||||
pod: &v1.Pod{
|
pod: test.BuildTestPod("p1", 2000, 2*1000*1000*1000, nodeNames[2], func(pod *v1.Pod) {
|
||||||
Spec: v1.PodSpec{
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
Affinity: &v1.Affinity{
|
nodeLabelKey: nodeLabelValue,
|
||||||
NodeAffinity: &v1.NodeAffinity{
|
}
|
||||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
}),
|
||||||
{
|
|
||||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
|
||||||
{
|
|
||||||
Key: nodeLabelKey,
|
|
||||||
Operator: "In",
|
|
||||||
Values: []string{
|
|
||||||
nodeLabelValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nodes: []*v1.Node{
|
nodes: []*v1.Node{
|
||||||
{
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
Labels: map[string]string{
|
nodeLabelKey: nodeLabelValue,
|
||||||
nodeLabelKey: "unfit1",
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
},
|
}),
|
||||||
{
|
// Notice that this node only has 4 cores, the pod already on the node below requests 3 cores, and the pod above requests 2 cores
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
test.BuildTestNode(nodeNames[1], 4000, 8*1000*1000*1000, 12, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(200*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{
|
||||||
|
test.BuildTestPod("3-core-pod", 3000, 4*1000*1000*1000, nodeNames[1], func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta = metav1.ObjectMeta{
|
||||||
|
Namespace: "test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
nodeLabelKey: "unfit2",
|
"test": "true",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
},
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
success: false,
|
success: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Nodes are unschedulable but labels match, should fail",
|
description: "Two nodes matches node selector, one of them is tained, but memory requests are too big, should fail",
|
||||||
pod: &v1.Pod{
|
pod: test.BuildTestPod("p1", 2000, 5*1000*1000*1000, nodeNames[2], func(pod *v1.Pod) {
|
||||||
Spec: v1.PodSpec{
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
Affinity: &v1.Affinity{
|
nodeLabelKey: nodeLabelValue,
|
||||||
NodeAffinity: &v1.NodeAffinity{
|
}
|
||||||
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
}),
|
||||||
{
|
|
||||||
MatchExpressions: []v1.NodeSelectorRequirement{
|
|
||||||
{
|
|
||||||
Key: nodeLabelKey,
|
|
||||||
Operator: "In",
|
|
||||||
Values: []string{
|
|
||||||
nodeLabelValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
nodes: []*v1.Node{
|
nodes: []*v1.Node{
|
||||||
{
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
Labels: map[string]string{
|
nodeLabelKey: nodeLabelValue,
|
||||||
nodeLabelKey: nodeLabelValue,
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
Spec: v1.NodeSpec{
|
}),
|
||||||
Unschedulable: true,
|
// Notice that this node only has 8GB of memory, the pod already on the node below requests 4GB, and the pod above requests 5GB
|
||||||
},
|
test.BuildTestNode(nodeNames[1], 10*1000, 8*1000*1000*1000, 12, func(node *v1.Node) {
|
||||||
},
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
{
|
nodeLabelKey: nodeLabelValue,
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(200*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{
|
||||||
|
test.BuildTestPod("4GB-mem-pod", 2000, 4*1000*1000*1000, nodeNames[1], func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta = metav1.ObjectMeta{
|
||||||
|
Namespace: "test",
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
nodeLabelKey: "no",
|
"test": "true",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
},
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Two nodes matches node selector, one of them is tained, but ephemeral storage requests are too big, should fail",
|
||||||
|
pod: test.BuildTestPod("p1", 2000, 4*1000*1000*1000, nodeNames[2], func(pod *v1.Pod) {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// Notice that this node only has 20GB of storage, the pod already on the node below requests 11GB, and the pod above requests 10GB
|
||||||
|
test.BuildTestNode(nodeNames[1], 10*1000, 8*1000*1000*1000, 12, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(20*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{
|
||||||
|
test.BuildTestPod("11GB-storage-pod", 2000, 4*1000*1000*1000, nodeNames[1], func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta = metav1.ObjectMeta{
|
||||||
|
Namespace: "test",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"test": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(11*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Two nodes matches node selector, one of them is tained, but custom resource requests are too big, should fail",
|
||||||
|
pod: test.BuildTestPod("p1", 2000, 2*1000*1000*1000, nodeNames[2], func(pod *v1.Pod) {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
pod.Spec.Containers[0].Resources.Requests["example.com/custom-resource"] = *resource.NewQuantity(10, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
node.Status.Allocatable["example.com/custom-resource"] = *resource.NewQuantity(15, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
// Notice that this node only has 15 of the custom resource, the pod already on the node below requests 10, and the pod above requests 10
|
||||||
|
test.BuildTestNode(nodeNames[1], 10*1000, 8*1000*1000*1000, 12, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(200*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Status.Allocatable["example.com/custom-resource"] = *resource.NewQuantity(15, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{
|
||||||
|
test.BuildTestPod("10-custom-resource-pod", 0, 0, nodeNames[1], func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta = metav1.ObjectMeta{
|
||||||
|
Namespace: "test",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"test": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests["example.com/custom-resource"] = *resource.NewQuantity(10, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Two nodes matches node selector, one of them is tained, CPU requests will fit, and pod Overhead is low enough, should pass",
|
||||||
|
pod: test.BuildTestPod("p1", 1000, 2*1000*1000*1000, nodeNames[2], func(pod *v1.Pod) {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// Notice that this node has 5 CPU cores, the pod below requests 2 cores, and has CPU overhead of 1 cores, and the pod above requests 1 core
|
||||||
|
test.BuildTestNode(nodeNames[1], 5000, 8*1000*1000*1000, 12, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(200*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{
|
||||||
|
test.BuildTestPod("3-core-pod", 2000, 4*1000*1000*1000, nodeNames[1], func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta = metav1.ObjectMeta{
|
||||||
|
Namespace: "test",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"test": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
pod.Spec.Overhead = createResourceList(1000, 1000*1000*1000, 1000*1000*1000)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Two nodes matches node selector, one of them is tained, CPU requests will fit, but pod Overhead is too high, should fail",
|
||||||
|
pod: test.BuildTestPod("p1", 2000, 2*1000*1000*1000, nodeNames[2], func(pod *v1.Pod) {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode(nodeNames[0], 64000, 128*1000*1000*1000, 200, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(1000*1000*1000*1000, resource.DecimalSI)
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: nodeTaintKey,
|
||||||
|
Value: nodeTaintValue,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
// Notice that this node only has 5 CPU cores, the pod below requests 2 cores, but has CPU overhead of 2 cores, and the pod above requests 2 cores
|
||||||
|
test.BuildTestNode(nodeNames[1], 5000, 8*1000*1000*1000, 12, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
nodeLabelKey: nodeLabelValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Status.Allocatable[v1.ResourceEphemeralStorage] = *resource.NewQuantity(200*1000*1000*1000, resource.DecimalSI)
|
||||||
|
}),
|
||||||
|
test.BuildTestNode(nodeNames[2], 0, 0, 0, nil),
|
||||||
|
},
|
||||||
|
podsOnNodes: []*v1.Pod{
|
||||||
|
test.BuildTestPod("3-core-pod", 2000, 4*1000*1000*1000, nodeNames[1], func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta = metav1.ObjectMeta{
|
||||||
|
Namespace: "test",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"test": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Resources.Requests[v1.ResourceEphemeralStorage] = *resource.NewQuantity(10*1000*1000*1000, resource.DecimalSI)
|
||||||
|
pod.Spec.Overhead = createResourceList(2000, 1000*1000*1000, 1000*1000*1000)
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
success: false,
|
success: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
actual := PodFitsAnyNode(tc.pod, tc.nodes)
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
if actual != tc.success {
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
t.Errorf("Test %#v failed", tc.description)
|
defer cancel()
|
||||||
}
|
|
||||||
|
var objs []runtime.Object
|
||||||
|
for _, node := range tc.nodes {
|
||||||
|
objs = append(objs, node)
|
||||||
|
}
|
||||||
|
for _, pod := range tc.podsOnNodes {
|
||||||
|
objs = append(objs, pod)
|
||||||
|
}
|
||||||
|
objs = append(objs, tc.pod)
|
||||||
|
|
||||||
|
fakeClient := fake.NewSimpleClientset(objs...)
|
||||||
|
|
||||||
|
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
podInformer := sharedInformerFactory.Core().V1().Pods()
|
||||||
|
|
||||||
|
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Build get pods assigned to node function error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedInformerFactory.Start(ctx.Done())
|
||||||
|
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||||
|
|
||||||
|
actual := PodFitsAnyOtherNode(getPodsAssignedToNode, tc.pod, tc.nodes)
|
||||||
|
if actual != tc.success {
|
||||||
|
t.Errorf("Test %#v failed", tc.description)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// createResourceList builds a small resource list of core resources
|
||||||
|
func createResourceList(cpu int64, memory int64, ephemeralStorage int64) v1.ResourceList {
|
||||||
|
resourceList := make(map[v1.ResourceName]resource.Quantity)
|
||||||
|
resourceList[v1.ResourceCPU] = *resource.NewMilliQuantity(cpu, resource.DecimalSI)
|
||||||
|
resourceList[v1.ResourceMemory] = *resource.NewQuantity(memory, resource.DecimalSI)
|
||||||
|
resourceList[v1.ResourceEphemeralStorage] = *resource.NewQuantity(ephemeralStorage, resource.DecimalSI)
|
||||||
|
return resourceList
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,64 +17,180 @@ limitations under the License.
|
|||||||
package pod
|
package pod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/pkg/utils"
|
"sigs.k8s.io/descheduler/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
evictPodAnnotationKey = "descheduler.alpha.kubernetes.io/evict"
|
nodeNameKeyIndex = "spec.nodeName"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsEvictable checks if a pod is evictable or not.
|
// FilterFunc is a filter for a pod.
|
||||||
func IsEvictable(pod *v1.Pod, evictLocalStoragePods bool) bool {
|
type FilterFunc func(*v1.Pod) bool
|
||||||
ownerRefList := OwnerRef(pod)
|
|
||||||
if !HaveEvictAnnotation(pod) && (IsMirrorPod(pod) || (!evictLocalStoragePods && IsPodWithLocalStorage(pod)) || len(ownerRefList) == 0 || IsDaemonsetPod(ownerRefList) || IsCriticalPod(pod)) {
|
// GetPodsAssignedToNodeFunc is a function which accept a node name and a pod filter function
|
||||||
return false
|
// as input and returns the pods that assigned to the node.
|
||||||
|
type GetPodsAssignedToNodeFunc func(string, FilterFunc) ([]*v1.Pod, error)
|
||||||
|
|
||||||
|
// WrapFilterFuncs wraps a set of FilterFunc in one.
|
||||||
|
func WrapFilterFuncs(filters ...FilterFunc) FilterFunc {
|
||||||
|
return func(pod *v1.Pod) bool {
|
||||||
|
for _, filter := range filters {
|
||||||
|
if filter != nil && !filter(pod) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListEvictablePodsOnNode returns the list of evictable pods on node.
|
type Options struct {
|
||||||
func ListEvictablePodsOnNode(client clientset.Interface, node *v1.Node, evictLocalStoragePods bool) ([]*v1.Pod, error) {
|
filter FilterFunc
|
||||||
pods, err := ListPodsOnANode(client, node)
|
includedNamespaces sets.String
|
||||||
if err != nil {
|
excludedNamespaces sets.String
|
||||||
return []*v1.Pod{}, err
|
labelSelector *metav1.LabelSelector
|
||||||
}
|
}
|
||||||
evictablePods := make([]*v1.Pod, 0)
|
|
||||||
for _, pod := range pods {
|
// NewOptions returns an empty Options.
|
||||||
if !IsEvictable(pod, evictLocalStoragePods) {
|
func NewOptions() *Options {
|
||||||
continue
|
return &Options{}
|
||||||
} else {
|
}
|
||||||
evictablePods = append(evictablePods, pod)
|
|
||||||
|
// WithFilter sets a pod filter.
|
||||||
|
// The filter function should return true if the pod should be returned from ListPodsOnANode
|
||||||
|
func (o *Options) WithFilter(filter FilterFunc) *Options {
|
||||||
|
o.filter = filter
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNamespaces sets included namespaces
|
||||||
|
func (o *Options) WithNamespaces(namespaces sets.String) *Options {
|
||||||
|
o.includedNamespaces = namespaces
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutNamespaces sets excluded namespaces
|
||||||
|
func (o *Options) WithoutNamespaces(namespaces sets.String) *Options {
|
||||||
|
o.excludedNamespaces = namespaces
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLabelSelector sets a pod label selector
|
||||||
|
func (o *Options) WithLabelSelector(labelSelector *metav1.LabelSelector) *Options {
|
||||||
|
o.labelSelector = labelSelector
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildFilterFunc builds a final FilterFunc based on Options.
|
||||||
|
func (o *Options) BuildFilterFunc() (FilterFunc, error) {
|
||||||
|
var s labels.Selector
|
||||||
|
var err error
|
||||||
|
if o.labelSelector != nil {
|
||||||
|
s, err = metav1.LabelSelectorAsSelector(o.labelSelector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return evictablePods, nil
|
return func(pod *v1.Pod) bool {
|
||||||
|
if o.filter != nil && !o.filter(pod) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(o.includedNamespaces) > 0 && !o.includedNamespaces.Has(pod.Namespace) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(o.excludedNamespaces) > 0 && o.excludedNamespaces.Has(pod.Namespace) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s != nil && !s.Matches(labels.Set(pod.GetLabels())) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListPodsOnANode(client clientset.Interface, node *v1.Node) ([]*v1.Pod, error) {
|
// BuildGetPodsAssignedToNodeFunc establishes an indexer to map the pods and their assigned nodes.
|
||||||
fieldSelector, err := fields.ParseSelector("spec.nodeName=" + node.Name + ",status.phase!=" + string(v1.PodSucceeded) + ",status.phase!=" + string(v1.PodFailed))
|
// It returns a function to help us get all the pods that assigned to a node based on the indexer.
|
||||||
|
func BuildGetPodsAssignedToNodeFunc(podInformer coreinformers.PodInformer) (GetPodsAssignedToNodeFunc, error) {
|
||||||
|
// Establish an indexer to map the pods and their assigned nodes.
|
||||||
|
err := podInformer.Informer().AddIndexers(cache.Indexers{
|
||||||
|
nodeNameKeyIndex: func(obj interface{}) ([]string, error) {
|
||||||
|
pod, ok := obj.(*v1.Pod)
|
||||||
|
if !ok {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
if len(pod.Spec.NodeName) == 0 {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
return []string{pod.Spec.NodeName}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The indexer helps us get all the pods that assigned to a node.
|
||||||
|
podIndexer := podInformer.Informer().GetIndexer()
|
||||||
|
getPodsAssignedToNode := func(nodeName string, filter FilterFunc) ([]*v1.Pod, error) {
|
||||||
|
objs, err := podIndexer.ByIndex(nodeNameKeyIndex, nodeName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pods := make([]*v1.Pod, 0, len(objs))
|
||||||
|
for _, obj := range objs {
|
||||||
|
pod, ok := obj.(*v1.Pod)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if filter(pod) {
|
||||||
|
pods = append(pods, pod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pods, nil
|
||||||
|
}
|
||||||
|
return getPodsAssignedToNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPodsOnANode lists all pods on a node.
|
||||||
|
// It also accepts a "filter" function which can be used to further limit the pods that are returned.
|
||||||
|
// (Usually this is podEvictor.Evictable().IsEvictable, in order to only list the evictable pods on a node, but can
|
||||||
|
// be used by strategies to extend it if there are further restrictions, such as with NodeAffinity).
|
||||||
|
func ListPodsOnANode(
|
||||||
|
nodeName string,
|
||||||
|
getPodsAssignedToNode GetPodsAssignedToNodeFunc,
|
||||||
|
filter FilterFunc,
|
||||||
|
) ([]*v1.Pod, error) {
|
||||||
|
// Succeeded and failed pods are not considered because they don't occupy any resource.
|
||||||
|
f := func(pod *v1.Pod) bool {
|
||||||
|
return pod.Status.Phase != v1.PodSucceeded && pod.Status.Phase != v1.PodFailed
|
||||||
|
}
|
||||||
|
return ListAllPodsOnANode(nodeName, getPodsAssignedToNode, WrapFilterFuncs(f, filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAllPodsOnANode lists all the pods on a node no matter what the phase of the pod is.
|
||||||
|
func ListAllPodsOnANode(
|
||||||
|
nodeName string,
|
||||||
|
getPodsAssignedToNode GetPodsAssignedToNodeFunc,
|
||||||
|
filter FilterFunc,
|
||||||
|
) ([]*v1.Pod, error) {
|
||||||
|
pods, err := getPodsAssignedToNode(nodeName, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*v1.Pod{}, err
|
return []*v1.Pod{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
podList, err := client.CoreV1().Pods(v1.NamespaceAll).List(
|
|
||||||
metav1.ListOptions{FieldSelector: fieldSelector.String()})
|
|
||||||
if err != nil {
|
|
||||||
return []*v1.Pod{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pods := make([]*v1.Pod, 0)
|
|
||||||
for i := range podList.Items {
|
|
||||||
pods = append(pods, &podList.Items[i])
|
|
||||||
}
|
|
||||||
return pods, nil
|
return pods, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsCriticalPod(pod *v1.Pod) bool {
|
// OwnerRef returns the ownerRefList for the pod.
|
||||||
return utils.IsCriticalPod(pod)
|
func OwnerRef(pod *v1.Pod) []metav1.OwnerReference {
|
||||||
|
return pod.ObjectMeta.GetOwnerReferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsBestEffortPod(pod *v1.Pod) bool {
|
func IsBestEffortPod(pod *v1.Pod) bool {
|
||||||
@@ -89,37 +205,26 @@ func IsGuaranteedPod(pod *v1.Pod) bool {
|
|||||||
return utils.GetPodQOS(pod) == v1.PodQOSGuaranteed
|
return utils.GetPodQOS(pod) == v1.PodQOSGuaranteed
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsDaemonsetPod(ownerRefList []metav1.OwnerReference) bool {
|
// SortPodsBasedOnPriorityLowToHigh sorts pods based on their priorities from low to high.
|
||||||
for _, ownerRef := range ownerRefList {
|
// If pods have same priorities, they will be sorted by QoS in the following order:
|
||||||
if ownerRef.Kind == "DaemonSet" {
|
// BestEffort, Burstable, Guaranteed
|
||||||
|
func SortPodsBasedOnPriorityLowToHigh(pods []*v1.Pod) {
|
||||||
|
sort.Slice(pods, func(i, j int) bool {
|
||||||
|
if pods[i].Spec.Priority == nil && pods[j].Spec.Priority != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
if pods[j].Spec.Priority == nil && pods[i].Spec.Priority != nil {
|
||||||
return false
|
return false
|
||||||
}
|
|
||||||
|
|
||||||
// IsMirrorPod checks whether the pod is a mirror pod.
|
|
||||||
func IsMirrorPod(pod *v1.Pod) bool {
|
|
||||||
return utils.IsMirrorPod(pod)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HaveEvictAnnotation checks if the pod have evict annotation
|
|
||||||
func HaveEvictAnnotation(pod *v1.Pod) bool {
|
|
||||||
_, found := pod.ObjectMeta.Annotations[evictPodAnnotationKey]
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsPodWithLocalStorage(pod *v1.Pod) bool {
|
|
||||||
for _, volume := range pod.Spec.Volumes {
|
|
||||||
if volume.HostPath != nil || volume.EmptyDir != nil {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
if (pods[j].Spec.Priority == nil && pods[i].Spec.Priority == nil) || (*pods[i].Spec.Priority == *pods[j].Spec.Priority) {
|
||||||
|
if IsBestEffortPod(pods[i]) {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
|
if IsBurstablePod(pods[i]) && IsGuaranteedPod(pods[j]) {
|
||||||
// OwnerRef returns the ownerRefList for the pod.
|
return true
|
||||||
func OwnerRef(pod *v1.Pod) []metav1.OwnerReference {
|
}
|
||||||
return pod.ObjectMeta.GetOwnerReferences()
|
return false
|
||||||
|
}
|
||||||
|
return *pods[i].Spec.Priority < *pods[j].Spec.Priority
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,221 +17,140 @@ limitations under the License.
|
|||||||
package pod
|
package pod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"sigs.k8s.io/descheduler/pkg/utils"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/test"
|
"sigs.k8s.io/descheduler/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsEvictable(t *testing.T) {
|
var (
|
||||||
n1 := test.BuildTestNode("node1", 1000, 2000, 13)
|
lowPriority = int32(0)
|
||||||
type testCase struct {
|
highPriority = int32(10000)
|
||||||
pod *v1.Pod
|
)
|
||||||
runBefore func(*v1.Pod)
|
|
||||||
evictLocalStoragePods bool
|
|
||||||
result bool
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []testCase{
|
func TestListPodsOnANode(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
pods []*v1.Pod
|
||||||
|
node *v1.Node
|
||||||
|
labelSelector *metav1.LabelSelector
|
||||||
|
expectedPodCount int
|
||||||
|
}{
|
||||||
{
|
{
|
||||||
pod: test.BuildTestPod("p1", 400, 0, n1.Name),
|
name: "test listing pods on a node",
|
||||||
runBefore: func(pod *v1.Pod) {
|
pods: []*v1.Pod{
|
||||||
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
test.BuildTestPod("pod1", 100, 0, "n1", nil),
|
||||||
|
test.BuildTestPod("pod2", 100, 0, "n1", nil),
|
||||||
|
test.BuildTestPod("pod3", 100, 0, "n2", nil),
|
||||||
},
|
},
|
||||||
evictLocalStoragePods: false,
|
node: test.BuildTestNode("n1", 2000, 3000, 10, nil),
|
||||||
result: true,
|
labelSelector: nil,
|
||||||
}, {
|
expectedPodCount: 2,
|
||||||
pod: test.BuildTestPod("p2", 400, 0, n1.Name),
|
},
|
||||||
runBefore: func(pod *v1.Pod) {
|
{
|
||||||
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
name: "test listing pods with label selector",
|
||||||
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
pods: []*v1.Pod{
|
||||||
|
test.BuildTestPod("pod1", 100, 0, "n1", nil),
|
||||||
|
test.BuildTestPod("pod2", 100, 0, "n1", func(pod *v1.Pod) {
|
||||||
|
pod.Labels = map[string]string{"foo": "bar"}
|
||||||
|
}),
|
||||||
|
test.BuildTestPod("pod3", 100, 0, "n1", func(pod *v1.Pod) {
|
||||||
|
pod.Labels = map[string]string{"foo": "bar1"}
|
||||||
|
}),
|
||||||
|
test.BuildTestPod("pod4", 100, 0, "n2", nil),
|
||||||
},
|
},
|
||||||
evictLocalStoragePods: false,
|
node: test.BuildTestNode("n1", 2000, 3000, 10, nil),
|
||||||
result: true,
|
labelSelector: &metav1.LabelSelector{
|
||||||
}, {
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||||
pod: test.BuildTestPod("p3", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
pod.ObjectMeta.OwnerReferences = test.GetReplicaSetOwnerRefList()
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: false,
|
|
||||||
result: true,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p4", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
|
||||||
pod.ObjectMeta.OwnerReferences = test.GetReplicaSetOwnerRefList()
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: false,
|
|
||||||
result: true,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p5", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
|
||||||
pod.Spec.Volumes = []v1.Volume{
|
|
||||||
{
|
{
|
||||||
Name: "sample",
|
Key: "foo",
|
||||||
VolumeSource: v1.VolumeSource{
|
Operator: metav1.LabelSelectorOpIn,
|
||||||
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
|
Values: []string{"bar", "bar1"},
|
||||||
EmptyDir: &v1.EmptyDirVolumeSource{
|
|
||||||
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI)},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
evictLocalStoragePods: false,
|
expectedPodCount: 2,
|
||||||
result: false,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p6", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
|
||||||
pod.Spec.Volumes = []v1.Volume{
|
|
||||||
{
|
|
||||||
Name: "sample",
|
|
||||||
VolumeSource: v1.VolumeSource{
|
|
||||||
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
|
|
||||||
EmptyDir: &v1.EmptyDirVolumeSource{
|
|
||||||
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: true,
|
|
||||||
result: true,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p7", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
|
||||||
pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
|
||||||
pod.Spec.Volumes = []v1.Volume{
|
|
||||||
{
|
|
||||||
Name: "sample",
|
|
||||||
VolumeSource: v1.VolumeSource{
|
|
||||||
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
|
|
||||||
EmptyDir: &v1.EmptyDirVolumeSource{
|
|
||||||
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: false,
|
|
||||||
result: true,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p8", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
pod.ObjectMeta.OwnerReferences = test.GetDaemonSetOwnerRefList()
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: false,
|
|
||||||
result: false,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p9", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
pod.Annotations = map[string]string{"descheduler.alpha.kubernetes.io/evict": "true"}
|
|
||||||
pod.ObjectMeta.OwnerReferences = test.GetDaemonSetOwnerRefList()
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: false,
|
|
||||||
result: true,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p10", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
pod.Annotations = test.GetMirrorPodAnnotation()
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: false,
|
|
||||||
result: false,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p11", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
pod.Annotations = test.GetMirrorPodAnnotation()
|
|
||||||
pod.Annotations["descheduler.alpha.kubernetes.io/evict"] = "true"
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: false,
|
|
||||||
result: true,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p12", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
priority := utils.SystemCriticalPriority
|
|
||||||
pod.Spec.Priority = &priority
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: false,
|
|
||||||
result: false,
|
|
||||||
}, {
|
|
||||||
pod: test.BuildTestPod("p13", 400, 0, n1.Name),
|
|
||||||
runBefore: func(pod *v1.Pod) {
|
|
||||||
priority := utils.SystemCriticalPriority
|
|
||||||
pod.Spec.Priority = &priority
|
|
||||||
pod.Annotations = map[string]string{
|
|
||||||
"descheduler.alpha.kubernetes.io/evict": "true",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
evictLocalStoragePods: false,
|
|
||||||
result: true,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
for _, test := range testCases {
|
var objs []runtime.Object
|
||||||
test.runBefore(test.pod)
|
objs = append(objs, testCase.node)
|
||||||
result := IsEvictable(test.pod, test.evictLocalStoragePods)
|
for _, pod := range testCase.pods {
|
||||||
if result != test.result {
|
objs = append(objs, pod)
|
||||||
t.Errorf("IsEvictable should return for pod %s %t, but it returns %t", test.pod.Name, test.result, result)
|
}
|
||||||
}
|
fakeClient := fake.NewSimpleClientset(objs...)
|
||||||
|
|
||||||
|
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
podInformer := sharedInformerFactory.Core().V1().Pods()
|
||||||
|
|
||||||
|
getPodsAssignedToNode, err := BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Build get pods assigned to node function error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedInformerFactory.Start(ctx.Done())
|
||||||
|
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||||
|
|
||||||
|
filter, err := NewOptions().WithLabelSelector(testCase.labelSelector).BuildFilterFunc()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Build filter function error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pods, _ := ListPodsOnANode(testCase.node.Name, getPodsAssignedToNode, filter)
|
||||||
|
if len(pods) != testCase.expectedPodCount {
|
||||||
|
t.Errorf("Expected %v pods on node %v, got %+v", testCase.expectedPodCount, testCase.node.Name, len(pods))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestPodTypes(t *testing.T) {
|
|
||||||
n1 := test.BuildTestNode("node1", 1000, 2000, 9)
|
|
||||||
p1 := test.BuildTestPod("p1", 400, 0, n1.Name)
|
|
||||||
|
|
||||||
// These won't be evicted.
|
func TestSortPodsBasedOnPriorityLowToHigh(t *testing.T) {
|
||||||
p2 := test.BuildTestPod("p2", 400, 0, n1.Name)
|
n1 := test.BuildTestNode("n1", 4000, 3000, 9, nil)
|
||||||
p3 := test.BuildTestPod("p3", 400, 0, n1.Name)
|
|
||||||
p4 := test.BuildTestPod("p4", 400, 0, n1.Name)
|
|
||||||
p5 := test.BuildTestPod("p5", 400, 0, n1.Name)
|
|
||||||
p6 := test.BuildTestPod("p6", 400, 0, n1.Name)
|
|
||||||
|
|
||||||
p6.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
p1 := test.BuildTestPod("p1", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
test.SetPodPriority(pod, lowPriority)
|
||||||
|
})
|
||||||
|
|
||||||
p1.ObjectMeta.OwnerReferences = test.GetReplicaSetOwnerRefList()
|
// BestEffort
|
||||||
// The following 4 pods won't get evicted.
|
p2 := test.BuildTestPod("p2", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
// A daemonset.
|
test.SetPodPriority(pod, highPriority)
|
||||||
//p2.Annotations = test.GetDaemonSetAnnotation()
|
test.MakeBestEffortPod(pod)
|
||||||
p2.ObjectMeta.OwnerReferences = test.GetDaemonSetOwnerRefList()
|
})
|
||||||
// A pod with local storage.
|
|
||||||
p3.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList()
|
|
||||||
p3.Spec.Volumes = []v1.Volume{
|
|
||||||
{
|
|
||||||
Name: "sample",
|
|
||||||
VolumeSource: v1.VolumeSource{
|
|
||||||
HostPath: &v1.HostPathVolumeSource{Path: "somePath"},
|
|
||||||
EmptyDir: &v1.EmptyDirVolumeSource{
|
|
||||||
SizeLimit: resource.NewQuantity(int64(10), resource.BinarySI)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// A Mirror Pod.
|
|
||||||
p4.Annotations = test.GetMirrorPodAnnotation()
|
|
||||||
// A Critical Pod.
|
|
||||||
p5.Namespace = "kube-system"
|
|
||||||
priority := utils.SystemCriticalPriority
|
|
||||||
p5.Spec.Priority = &priority
|
|
||||||
systemCriticalPriority := utils.SystemCriticalPriority
|
|
||||||
p5.Spec.Priority = &systemCriticalPriority
|
|
||||||
if !IsMirrorPod(p4) {
|
|
||||||
t.Errorf("Expected p4 to be a mirror pod.")
|
|
||||||
}
|
|
||||||
if !IsCriticalPod(p5) {
|
|
||||||
t.Errorf("Expected p5 to be a critical pod.")
|
|
||||||
}
|
|
||||||
if !IsPodWithLocalStorage(p3) {
|
|
||||||
t.Errorf("Expected p3 to be a pod with local storage.")
|
|
||||||
}
|
|
||||||
ownerRefList := OwnerRef(p2)
|
|
||||||
if !IsDaemonsetPod(ownerRefList) {
|
|
||||||
t.Errorf("Expected p2 to be a daemonset pod.")
|
|
||||||
}
|
|
||||||
ownerRefList = OwnerRef(p1)
|
|
||||||
if IsDaemonsetPod(ownerRefList) || IsPodWithLocalStorage(p1) || IsCriticalPod(p1) || IsMirrorPod(p1) {
|
|
||||||
t.Errorf("Expected p1 to be a normal pod.")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Burstable
|
||||||
|
p3 := test.BuildTestPod("p3", 400, 0, n1.Name, func(pod *v1.Pod) {
|
||||||
|
test.SetPodPriority(pod, highPriority)
|
||||||
|
test.MakeBurstablePod(pod)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Guaranteed
|
||||||
|
p4 := test.BuildTestPod("p4", 400, 100, n1.Name, func(pod *v1.Pod) {
|
||||||
|
test.SetPodPriority(pod, highPriority)
|
||||||
|
test.MakeGuaranteedPod(pod)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Best effort with nil priorities.
|
||||||
|
p5 := test.BuildTestPod("p5", 400, 100, n1.Name, test.MakeBestEffortPod)
|
||||||
|
p5.Spec.Priority = nil
|
||||||
|
|
||||||
|
p6 := test.BuildTestPod("p6", 400, 100, n1.Name, test.MakeGuaranteedPod)
|
||||||
|
p6.Spec.Priority = nil
|
||||||
|
|
||||||
|
podList := []*v1.Pod{p4, p3, p2, p1, p6, p5}
|
||||||
|
|
||||||
|
SortPodsBasedOnPriorityLowToHigh(podList)
|
||||||
|
if !reflect.DeepEqual(podList[len(podList)-1], p4) {
|
||||||
|
t.Errorf("Expected last pod in sorted list to be %v which of highest priority and guaranteed but got %v", p4, podList[len(podList)-1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
"sigs.k8s.io/descheduler/pkg/api/v1alpha1"
|
"sigs.k8s.io/descheduler/pkg/api/v1alpha1"
|
||||||
@@ -30,7 +30,7 @@ import (
|
|||||||
|
|
||||||
func LoadPolicyConfig(policyConfigFile string) (*api.DeschedulerPolicy, error) {
|
func LoadPolicyConfig(policyConfigFile string) (*api.DeschedulerPolicy, error) {
|
||||||
if policyConfigFile == "" {
|
if policyConfigFile == "" {
|
||||||
klog.V(1).Infof("policy config file not specified")
|
klog.V(1).InfoS("Policy config file not specified")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,22 @@ package scheme
|
|||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/api/v1alpha1"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/apis/componentconfig"
|
||||||
|
componentconfigv1alpha1 "sigs.k8s.io/descheduler/pkg/apis/componentconfig/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Scheme = runtime.NewScheme()
|
Scheme = runtime.NewScheme()
|
||||||
Codecs = serializer.NewCodecFactory(Scheme)
|
Codecs = serializer.NewCodecFactory(Scheme)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
utilruntime.Must(api.AddToScheme(Scheme))
|
||||||
|
utilruntime.Must(v1alpha1.AddToScheme(Scheme))
|
||||||
|
|
||||||
|
utilruntime.Must(componentconfig.AddToScheme(Scheme))
|
||||||
|
utilruntime.Must(componentconfigv1alpha1.AddToScheme(Scheme))
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,81 +17,285 @@ limitations under the License.
|
|||||||
package strategies
|
package strategies
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"sigs.k8s.io/descheduler/cmd/descheduler/app/options"
|
|
||||||
"sigs.k8s.io/descheduler/pkg/api"
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||||
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
"sigs.k8s.io/descheduler/pkg/utils"
|
"sigs.k8s.io/descheduler/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
//type creator string
|
func validateRemoveDuplicatePodsParams(params *api.StrategyParameters) error {
|
||||||
type DuplicatePodsMap map[string][]*v1.Pod
|
if params == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// At most one of include/exclude can be set
|
||||||
|
if params.Namespaces != nil && len(params.Namespaces.Include) > 0 && len(params.Namespaces.Exclude) > 0 {
|
||||||
|
return fmt.Errorf("only one of Include/Exclude namespaces can be set")
|
||||||
|
}
|
||||||
|
if params.ThresholdPriority != nil && params.ThresholdPriorityClassName != "" {
|
||||||
|
return fmt.Errorf("only one of thresholdPriority and thresholdPriorityClassName can be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type podOwner struct {
|
||||||
|
namespace, kind, name string
|
||||||
|
imagesHash string
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveDuplicatePods removes the duplicate pods on node. This strategy evicts all duplicate pods on node.
|
// RemoveDuplicatePods removes the duplicate pods on node. This strategy evicts all duplicate pods on node.
|
||||||
// A pod is said to be a duplicate of other if both of them are from same creator, kind and are within the same
|
// A pod is said to be a duplicate of other if both of them are from same creator, kind and are within the same
|
||||||
// namespace. As of now, this strategy won't evict daemonsets, mirror pods, critical pods and pods with local storages.
|
// namespace, and have at least one container with the same image.
|
||||||
func RemoveDuplicatePods(ds *options.DeschedulerServer, strategy api.DeschedulerStrategy, policyGroupVersion string, nodes []*v1.Node, nodepodCount utils.NodePodEvictedCount) {
|
// As of now, this strategy won't evict daemonsets, mirror pods, critical pods and pods with local storages.
|
||||||
if !strategy.Enabled {
|
func RemoveDuplicatePods(
|
||||||
|
ctx context.Context,
|
||||||
|
client clientset.Interface,
|
||||||
|
strategy api.DeschedulerStrategy,
|
||||||
|
nodes []*v1.Node,
|
||||||
|
podEvictor *evictions.PodEvictor,
|
||||||
|
getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc,
|
||||||
|
) {
|
||||||
|
if err := validateRemoveDuplicatePodsParams(strategy.Params); err != nil {
|
||||||
|
klog.ErrorS(err, "Invalid RemoveDuplicatePods parameters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
thresholdPriority, err := utils.GetPriorityFromStrategyParams(ctx, client, strategy.Params)
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "Failed to get threshold priority from strategy's params")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var includedNamespaces, excludedNamespaces sets.String
|
||||||
|
if strategy.Params != nil && strategy.Params.Namespaces != nil {
|
||||||
|
includedNamespaces = sets.NewString(strategy.Params.Namespaces.Include...)
|
||||||
|
excludedNamespaces = sets.NewString(strategy.Params.Namespaces.Exclude...)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeFit := false
|
||||||
|
if strategy.Params != nil {
|
||||||
|
nodeFit = strategy.Params.NodeFit
|
||||||
|
}
|
||||||
|
|
||||||
|
evictable := podEvictor.Evictable(evictions.WithPriorityThreshold(thresholdPriority), evictions.WithNodeFit(nodeFit))
|
||||||
|
|
||||||
|
duplicatePods := make(map[podOwner]map[string][]*v1.Pod)
|
||||||
|
ownerKeyOccurence := make(map[podOwner]int32)
|
||||||
|
nodeCount := 0
|
||||||
|
nodeMap := make(map[string]*v1.Node)
|
||||||
|
|
||||||
|
podFilter, err := podutil.NewOptions().
|
||||||
|
WithFilter(evictable.IsEvictable).
|
||||||
|
WithNamespaces(includedNamespaces).
|
||||||
|
WithoutNamespaces(excludedNamespaces).
|
||||||
|
BuildFilterFunc()
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "Error initializing pod filter function")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
deleteDuplicatePods(ds.Client, policyGroupVersion, nodes, ds.DryRun, nodepodCount, ds.MaxNoOfPodsToEvictPerNode, ds.EvictLocalStoragePods)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteDuplicatePods evicts the pod from node and returns the count of evicted pods.
|
|
||||||
func deleteDuplicatePods(client clientset.Interface, policyGroupVersion string, nodes []*v1.Node, dryRun bool, nodepodCount utils.NodePodEvictedCount, maxPodsToEvict int, evictLocalStoragePods bool) int {
|
|
||||||
podsEvicted := 0
|
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
klog.V(1).Infof("Processing node: %#v", node.Name)
|
klog.V(1).InfoS("Processing node", "node", klog.KObj(node))
|
||||||
dpm := ListDuplicatePodsOnANode(client, node, evictLocalStoragePods)
|
pods, err := podutil.ListPodsOnANode(node.Name, getPodsAssignedToNode, podFilter)
|
||||||
for creator, pods := range dpm {
|
if err != nil {
|
||||||
if len(pods) > 1 {
|
klog.ErrorS(err, "Error listing evictable pods on node", "node", klog.KObj(node))
|
||||||
klog.V(1).Infof("%#v", creator)
|
continue
|
||||||
// i = 0 does not evict the first pod
|
}
|
||||||
for i := 1; i < len(pods); i++ {
|
nodeMap[node.Name] = node
|
||||||
if maxPodsToEvict > 0 && nodepodCount[node]+1 > maxPodsToEvict {
|
nodeCount++
|
||||||
|
// Each pod has a list of owners and a list of containers, and each container has 1 image spec.
|
||||||
|
// For each pod, we go through all the OwnerRef/Image mappings and represent them as a "key" string.
|
||||||
|
// All of those mappings together makes a list of "key" strings that essentially represent that pod's uniqueness.
|
||||||
|
// This list of keys representing a single pod is then sorted alphabetically.
|
||||||
|
// If any other pod has a list that matches that pod's list, those pods are undeniably duplicates for the following reasons:
|
||||||
|
// - The 2 pods have the exact same ownerrefs
|
||||||
|
// - The 2 pods have the exact same container images
|
||||||
|
//
|
||||||
|
// duplicateKeysMap maps the first Namespace/Kind/Name/Image in a pod's list to a 2D-slice of all the other lists where that is the first key
|
||||||
|
// (Since we sort each pod's list, we only need to key the map on the first entry in each list. Any pod that doesn't have
|
||||||
|
// the same first entry is clearly not a duplicate. This makes lookup quick and minimizes storage needed).
|
||||||
|
// If any of the existing lists for that first key matches the current pod's list, the current pod is a duplicate.
|
||||||
|
// If not, then we add this pod's list to the list of lists for that key.
|
||||||
|
duplicateKeysMap := map[string][][]string{}
|
||||||
|
for _, pod := range pods {
|
||||||
|
ownerRefList := podutil.OwnerRef(pod)
|
||||||
|
if hasExcludedOwnerRefKind(ownerRefList, strategy) || len(ownerRefList) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
podContainerKeys := make([]string, 0, len(ownerRefList)*len(pod.Spec.Containers))
|
||||||
|
imageList := []string{}
|
||||||
|
for _, container := range pod.Spec.Containers {
|
||||||
|
imageList = append(imageList, container.Image)
|
||||||
|
}
|
||||||
|
sort.Strings(imageList)
|
||||||
|
imagesHash := strings.Join(imageList, "#")
|
||||||
|
for _, ownerRef := range ownerRefList {
|
||||||
|
ownerKey := podOwner{
|
||||||
|
namespace: pod.ObjectMeta.Namespace,
|
||||||
|
kind: ownerRef.Kind,
|
||||||
|
name: ownerRef.Name,
|
||||||
|
imagesHash: imagesHash,
|
||||||
|
}
|
||||||
|
ownerKeyOccurence[ownerKey] = ownerKeyOccurence[ownerKey] + 1
|
||||||
|
for _, image := range imageList {
|
||||||
|
// Namespace/Kind/Name should be unique for the cluster.
|
||||||
|
// We also consider the image, as 2 pods could have the same owner but serve different purposes
|
||||||
|
// So any non-unique Namespace/Kind/Name/Image pattern is a duplicate pod.
|
||||||
|
s := strings.Join([]string{pod.ObjectMeta.Namespace, ownerRef.Kind, ownerRef.Name, image}, "/")
|
||||||
|
podContainerKeys = append(podContainerKeys, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(podContainerKeys)
|
||||||
|
|
||||||
|
// If there have been any other pods with the same first "key", look through all the lists to see if any match
|
||||||
|
if existing, ok := duplicateKeysMap[podContainerKeys[0]]; ok {
|
||||||
|
matched := false
|
||||||
|
for _, keys := range existing {
|
||||||
|
if reflect.DeepEqual(keys, podContainerKeys) {
|
||||||
|
matched = true
|
||||||
|
klog.V(3).InfoS("Duplicate found", "pod", klog.KObj(pod))
|
||||||
|
for _, ownerRef := range ownerRefList {
|
||||||
|
ownerKey := podOwner{
|
||||||
|
namespace: pod.ObjectMeta.Namespace,
|
||||||
|
kind: ownerRef.Kind,
|
||||||
|
name: ownerRef.Name,
|
||||||
|
imagesHash: imagesHash,
|
||||||
|
}
|
||||||
|
if _, ok := duplicatePods[ownerKey]; !ok {
|
||||||
|
duplicatePods[ownerKey] = make(map[string][]*v1.Pod)
|
||||||
|
}
|
||||||
|
duplicatePods[ownerKey][node.Name] = append(duplicatePods[ownerKey][node.Name], pod)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
success, err := evictions.EvictPod(client, pods[i], policyGroupVersion, dryRun)
|
}
|
||||||
if !success {
|
if !matched {
|
||||||
klog.Infof("Error when evicting pod: %#v (%#v)", pods[i].Name, err)
|
// Found no matches, add this list of keys to the list of lists that have the same first key
|
||||||
} else {
|
duplicateKeysMap[podContainerKeys[0]] = append(duplicateKeysMap[podContainerKeys[0]], podContainerKeys)
|
||||||
nodepodCount[node]++
|
}
|
||||||
klog.V(1).Infof("Evicted pod: %#v (%#v)", pods[i].Name, err)
|
} else {
|
||||||
|
// This is the first pod we've seen that has this first "key" entry
|
||||||
|
duplicateKeysMap[podContainerKeys[0]] = [][]string{podContainerKeys}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. how many pods can be evicted to respect uniform placement of pods among viable nodes?
|
||||||
|
for ownerKey, podNodes := range duplicatePods {
|
||||||
|
targetNodes := getTargetNodes(podNodes, nodes)
|
||||||
|
|
||||||
|
klog.V(2).InfoS("Adjusting feasible nodes", "owner", ownerKey, "from", nodeCount, "to", len(targetNodes))
|
||||||
|
if len(targetNodes) < 2 {
|
||||||
|
klog.V(1).InfoS("Less than two feasible nodes for duplicates to land, skipping eviction", "owner", ownerKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
upperAvg := int(math.Ceil(float64(ownerKeyOccurence[ownerKey]) / float64(len(targetNodes))))
|
||||||
|
for nodeName, pods := range podNodes {
|
||||||
|
klog.V(2).InfoS("Average occurrence per node", "node", klog.KObj(nodeMap[nodeName]), "ownerKey", ownerKey, "avg", upperAvg)
|
||||||
|
// list of duplicated pods does not contain the original referential pod
|
||||||
|
if len(pods)+1 > upperAvg {
|
||||||
|
// It's assumed all duplicated pods are in the same priority class
|
||||||
|
// TODO(jchaloup): check if the pod has a different node to lend to
|
||||||
|
for _, pod := range pods[upperAvg-1:] {
|
||||||
|
if _, err := podEvictor.EvictPod(ctx, pod, nodeMap[nodeName], "RemoveDuplicatePods"); err != nil {
|
||||||
|
klog.ErrorS(err, "Error evicting pod", "pod", klog.KObj(pod))
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
podsEvicted += nodepodCount[node]
|
|
||||||
}
|
}
|
||||||
return podsEvicted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListDuplicatePodsOnANode lists duplicate pods on a given node.
|
func getNodeAffinityNodeSelector(pod *v1.Pod) *v1.NodeSelector {
|
||||||
func ListDuplicatePodsOnANode(client clientset.Interface, node *v1.Node, evictLocalStoragePods bool) DuplicatePodsMap {
|
if pod.Spec.Affinity == nil {
|
||||||
pods, err := podutil.ListEvictablePodsOnNode(client, node, evictLocalStoragePods)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return FindDuplicatePods(pods)
|
if pod.Spec.Affinity.NodeAffinity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindDuplicatePods takes a list of pods and returns a duplicatePodsMap.
|
func getTargetNodes(podNodes map[string][]*v1.Pod, nodes []*v1.Node) []*v1.Node {
|
||||||
func FindDuplicatePods(pods []*v1.Pod) DuplicatePodsMap {
|
// In order to reduce the number of pods processed, identify pods which have
|
||||||
dpm := DuplicatePodsMap{}
|
// equal (tolerations, nodeselectors, node affinity) terms and considered them
|
||||||
// Ignoring the error here as in the ListDuplicatePodsOnNode function we call ListEvictablePodsOnNode which checks for error.
|
// as identical. Identical pods wrt. (tolerations, nodeselectors, node affinity) terms
|
||||||
for _, pod := range pods {
|
// will produce the same result when checking if a pod is feasible for a node.
|
||||||
ownerRefList := podutil.OwnerRef(pod)
|
// Thus, improving efficiency of processing pods marked for eviction.
|
||||||
for _, ownerRef := range ownerRefList {
|
|
||||||
// Namespace/Kind/Name should be unique for the cluster.
|
// Collect all distinct pods which differ in at least taints, node affinity or node selector terms
|
||||||
s := strings.Join([]string{pod.ObjectMeta.Namespace, ownerRef.Kind, ownerRef.Name}, "/")
|
distinctPods := map[*v1.Pod]struct{}{}
|
||||||
dpm[s] = append(dpm[s], pod)
|
for _, pods := range podNodes {
|
||||||
|
for _, pod := range pods {
|
||||||
|
duplicated := false
|
||||||
|
for dp := range distinctPods {
|
||||||
|
if utils.TolerationsEqual(pod.Spec.Tolerations, dp.Spec.Tolerations) &&
|
||||||
|
utils.NodeSelectorsEqual(getNodeAffinityNodeSelector(pod), getNodeAffinityNodeSelector(dp)) &&
|
||||||
|
reflect.DeepEqual(pod.Spec.NodeSelector, dp.Spec.NodeSelector) {
|
||||||
|
duplicated = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if duplicated {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
distinctPods[pod] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dpm
|
|
||||||
|
// For each distinct pod get a list of nodes where it can land
|
||||||
|
targetNodesMap := map[string]*v1.Node{}
|
||||||
|
for pod := range distinctPods {
|
||||||
|
matchingNodes := map[string]*v1.Node{}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if !utils.TolerationsTolerateTaintsWithFilter(pod.Spec.Tolerations, node.Spec.Taints, func(taint *v1.Taint) bool {
|
||||||
|
return taint.Effect == v1.TaintEffectNoSchedule || taint.Effect == v1.TaintEffectNoExecute
|
||||||
|
}) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if match, err := utils.PodMatchNodeSelector(pod, node); err == nil && !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
matchingNodes[node.Name] = node
|
||||||
|
}
|
||||||
|
if len(matchingNodes) > 1 {
|
||||||
|
for nodeName := range matchingNodes {
|
||||||
|
targetNodesMap[nodeName] = matchingNodes[nodeName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
targetNodes := []*v1.Node{}
|
||||||
|
for _, node := range targetNodesMap {
|
||||||
|
targetNodes = append(targetNodes, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasExcludedOwnerRefKind(ownerRefs []metav1.OwnerReference, strategy api.DeschedulerStrategy) bool {
|
||||||
|
if strategy.Params == nil || strategy.Params.RemoveDuplicates == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
exclude := sets.NewString(strategy.Params.RemoveDuplicates.ExcludeOwnerKinds...)
|
||||||
|
for _, owner := range ownerRefs {
|
||||||
|
if exclude.Has(owner.Kind) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,37 +17,99 @@ limitations under the License.
|
|||||||
package strategies
|
package strategies
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
policyv1 "k8s.io/api/policy/v1"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
core "k8s.io/client-go/testing"
|
|
||||||
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||||
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
"sigs.k8s.io/descheduler/pkg/utils"
|
"sigs.k8s.io/descheduler/pkg/utils"
|
||||||
"sigs.k8s.io/descheduler/test"
|
"sigs.k8s.io/descheduler/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func buildTestPodWithImage(podName, node, image string) *v1.Pod {
|
||||||
|
pod := test.BuildTestPod(podName, 100, 0, node, test.SetRSOwnerRef)
|
||||||
|
pod.Spec.Containers = append(pod.Spec.Containers, v1.Container{
|
||||||
|
Name: image,
|
||||||
|
Image: image,
|
||||||
|
})
|
||||||
|
return pod
|
||||||
|
}
|
||||||
|
|
||||||
func TestFindDuplicatePods(t *testing.T) {
|
func TestFindDuplicatePods(t *testing.T) {
|
||||||
// first setup pods
|
// first setup pods
|
||||||
node := test.BuildTestNode("n1", 2000, 3000, 10)
|
node1 := test.BuildTestNode("n1", 2000, 3000, 10, nil)
|
||||||
p1 := test.BuildTestPod("p1", 100, 0, node.Name)
|
node2 := test.BuildTestNode("n2", 2000, 3000, 10, nil)
|
||||||
|
node3 := test.BuildTestNode("n3", 2000, 3000, 10, func(node *v1.Node) {
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Key: "hardware",
|
||||||
|
Value: "gpu",
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
node4 := test.BuildTestNode("n4", 2000, 3000, 10, func(node *v1.Node) {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{
|
||||||
|
"datacenter": "east",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
node5 := test.BuildTestNode("n5", 2000, 3000, 10, func(node *v1.Node) {
|
||||||
|
node.Spec = v1.NodeSpec{
|
||||||
|
Unschedulable: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
node6 := test.BuildTestNode("n6", 200, 200, 10, nil)
|
||||||
|
|
||||||
|
p1 := test.BuildTestPod("p1", 100, 0, node1.Name, nil)
|
||||||
p1.Namespace = "dev"
|
p1.Namespace = "dev"
|
||||||
p2 := test.BuildTestPod("p2", 100, 0, node.Name)
|
p2 := test.BuildTestPod("p2", 100, 0, node1.Name, nil)
|
||||||
p2.Namespace = "dev"
|
p2.Namespace = "dev"
|
||||||
p3 := test.BuildTestPod("p3", 100, 0, node.Name)
|
p3 := test.BuildTestPod("p3", 100, 0, node1.Name, nil)
|
||||||
p3.Namespace = "dev"
|
p3.Namespace = "dev"
|
||||||
p4 := test.BuildTestPod("p4", 100, 0, node.Name)
|
p4 := test.BuildTestPod("p4", 100, 0, node1.Name, nil)
|
||||||
p5 := test.BuildTestPod("p5", 100, 0, node.Name)
|
p5 := test.BuildTestPod("p5", 100, 0, node1.Name, nil)
|
||||||
p6 := test.BuildTestPod("p6", 100, 0, node.Name)
|
p6 := test.BuildTestPod("p6", 100, 0, node1.Name, nil)
|
||||||
p7 := test.BuildTestPod("p7", 100, 0, node.Name)
|
p7 := test.BuildTestPod("p7", 100, 0, node1.Name, nil)
|
||||||
p7.Namespace = "kube-system"
|
p7.Namespace = "kube-system"
|
||||||
p8 := test.BuildTestPod("p8", 100, 0, node.Name)
|
p8 := test.BuildTestPod("p8", 100, 0, node1.Name, nil)
|
||||||
p8.Namespace = "test"
|
p8.Namespace = "test"
|
||||||
p9 := test.BuildTestPod("p9", 100, 0, node.Name)
|
p9 := test.BuildTestPod("p9", 100, 0, node1.Name, nil)
|
||||||
p9.Namespace = "test"
|
p9.Namespace = "test"
|
||||||
p10 := test.BuildTestPod("p10", 100, 0, node.Name)
|
p10 := test.BuildTestPod("p10", 100, 0, node1.Name, nil)
|
||||||
p10.Namespace = "test"
|
p10.Namespace = "test"
|
||||||
|
p11 := test.BuildTestPod("p11", 100, 0, node1.Name, nil)
|
||||||
|
p11.Namespace = "different-images"
|
||||||
|
p12 := test.BuildTestPod("p12", 100, 0, node1.Name, nil)
|
||||||
|
p12.Namespace = "different-images"
|
||||||
|
p13 := test.BuildTestPod("p13", 100, 0, node1.Name, nil)
|
||||||
|
p13.Namespace = "different-images"
|
||||||
|
p14 := test.BuildTestPod("p14", 100, 0, node1.Name, nil)
|
||||||
|
p14.Namespace = "different-images"
|
||||||
|
p15 := test.BuildTestPod("p15", 100, 0, node1.Name, nil)
|
||||||
|
p15.Namespace = "node-fit"
|
||||||
|
p16 := test.BuildTestPod("NOT1", 100, 0, node1.Name, nil)
|
||||||
|
p16.Namespace = "node-fit"
|
||||||
|
p17 := test.BuildTestPod("NOT2", 100, 0, node1.Name, nil)
|
||||||
|
p17.Namespace = "node-fit"
|
||||||
|
p18 := test.BuildTestPod("TARGET", 100, 0, node1.Name, nil)
|
||||||
|
p18.Namespace = "node-fit"
|
||||||
|
|
||||||
|
// This pod sits on node6 and is used to take up CPU requests on the node
|
||||||
|
p19 := test.BuildTestPod("CPU-eater", 150, 150, node6.Name, nil)
|
||||||
|
p19.Namespace = "test"
|
||||||
|
|
||||||
|
// Dummy pod for node6 used to do the opposite of p19
|
||||||
|
p20 := test.BuildTestPod("CPU-saver", 100, 150, node6.Name, nil)
|
||||||
|
p20.Namespace = "test"
|
||||||
|
|
||||||
// ### Evictable Pods ###
|
// ### Evictable Pods ###
|
||||||
|
|
||||||
@@ -88,59 +150,617 @@ func TestFindDuplicatePods(t *testing.T) {
|
|||||||
priority := utils.SystemCriticalPriority
|
priority := utils.SystemCriticalPriority
|
||||||
p7.Spec.Priority = &priority
|
p7.Spec.Priority = &priority
|
||||||
|
|
||||||
|
// Same owners, but different images
|
||||||
|
p11.Spec.Containers[0].Image = "foo"
|
||||||
|
p11.ObjectMeta.OwnerReferences = ownerRef1
|
||||||
|
p12.Spec.Containers[0].Image = "bar"
|
||||||
|
p12.ObjectMeta.OwnerReferences = ownerRef1
|
||||||
|
|
||||||
|
// Multiple containers
|
||||||
|
p13.ObjectMeta.OwnerReferences = ownerRef1
|
||||||
|
p13.Spec.Containers = append(p13.Spec.Containers, v1.Container{
|
||||||
|
Name: "foo",
|
||||||
|
Image: "foo",
|
||||||
|
})
|
||||||
|
|
||||||
|
// ### Pods Evictable Based On Node Fit ###
|
||||||
|
|
||||||
|
ownerRef3 := test.GetReplicaSetOwnerRefList()
|
||||||
|
p15.ObjectMeta.OwnerReferences = ownerRef3
|
||||||
|
p16.ObjectMeta.OwnerReferences = ownerRef3
|
||||||
|
p17.ObjectMeta.OwnerReferences = ownerRef3
|
||||||
|
p18.ObjectMeta.OwnerReferences = ownerRef3
|
||||||
|
|
||||||
|
p15.Spec.NodeSelector = map[string]string{
|
||||||
|
"datacenter": "west",
|
||||||
|
}
|
||||||
|
p16.Spec.NodeSelector = map[string]string{
|
||||||
|
"datacenter": "west",
|
||||||
|
}
|
||||||
|
p17.Spec.NodeSelector = map[string]string{
|
||||||
|
"datacenter": "west",
|
||||||
|
}
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
maxPodsToEvict int
|
pods []*v1.Pod
|
||||||
pods []v1.Pod
|
nodes []*v1.Node
|
||||||
expectedEvictedPodCount int
|
expectedEvictedPodCount uint
|
||||||
|
strategy api.DeschedulerStrategy
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "Three pods in the `dev` Namespace, bound to same ReplicaSet. 2 should be evicted.",
|
description: "Three pods in the `dev` Namespace, bound to same ReplicaSet. 1 should be evicted.",
|
||||||
maxPodsToEvict: 5,
|
pods: []*v1.Pod{p1, p2, p3},
|
||||||
pods: []v1.Pod{*p1, *p2, *p3},
|
nodes: []*v1.Node{node1, node2},
|
||||||
expectedEvictedPodCount: 2,
|
expectedEvictedPodCount: 1,
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Three Pods in the `test` Namespace, bound to same ReplicaSet. 2 should be evicted.",
|
description: "Three pods in the `dev` Namespace, bound to same ReplicaSet, but ReplicaSet kind is excluded. 0 should be evicted.",
|
||||||
maxPodsToEvict: 5,
|
pods: []*v1.Pod{p1, p2, p3},
|
||||||
pods: []v1.Pod{*p8, *p9, *p10},
|
nodes: []*v1.Node{node1, node2},
|
||||||
expectedEvictedPodCount: 2,
|
expectedEvictedPodCount: 0,
|
||||||
|
strategy: api.DeschedulerStrategy{Params: &api.StrategyParameters{RemoveDuplicates: &api.RemoveDuplicates{ExcludeOwnerKinds: []string{"ReplicaSet"}}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Three Pods in the `test` Namespace, bound to same ReplicaSet. 1 should be evicted.",
|
||||||
|
pods: []*v1.Pod{p8, p9, p10},
|
||||||
|
nodes: []*v1.Node{node1, node2},
|
||||||
|
expectedEvictedPodCount: 1,
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Three Pods in the `dev` Namespace, three Pods in the `test` Namespace. Bound to ReplicaSet with same name. 4 should be evicted.",
|
description: "Three Pods in the `dev` Namespace, three Pods in the `test` Namespace. Bound to ReplicaSet with same name. 4 should be evicted.",
|
||||||
maxPodsToEvict: 5,
|
pods: []*v1.Pod{p1, p2, p3, p8, p9, p10},
|
||||||
pods: []v1.Pod{*p1, *p2, *p3, *p8, *p9, *p10},
|
nodes: []*v1.Node{node1, node2},
|
||||||
expectedEvictedPodCount: 4,
|
expectedEvictedPodCount: 2,
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Pods are: part of DaemonSet, with local storage, mirror pod annotation, critical pod annotation - none should be evicted.",
|
description: "Pods are: part of DaemonSet, with local storage, mirror pod annotation, critical pod annotation - none should be evicted.",
|
||||||
maxPodsToEvict: 2,
|
pods: []*v1.Pod{p4, p5, p6, p7},
|
||||||
pods: []v1.Pod{*p4, *p5, *p6, *p7},
|
nodes: []*v1.Node{node1, node2},
|
||||||
expectedEvictedPodCount: 0,
|
expectedEvictedPodCount: 0,
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Test all Pods: 4 should be evicted.",
|
description: "Test all Pods: 4 should be evicted.",
|
||||||
maxPodsToEvict: 5,
|
pods: []*v1.Pod{p1, p2, p3, p4, p5, p6, p7, p8, p9, p10},
|
||||||
pods: []v1.Pod{*p1, *p2, *p3, *p4, *p5, *p6, *p7, *p8, *p9, *p10},
|
nodes: []*v1.Node{node1, node2},
|
||||||
expectedEvictedPodCount: 4,
|
expectedEvictedPodCount: 2,
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Pods with the same owner but different images should not be evicted",
|
||||||
|
pods: []*v1.Pod{p11, p12},
|
||||||
|
nodes: []*v1.Node{node1, node2},
|
||||||
|
expectedEvictedPodCount: 0,
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Pods with multiple containers should not match themselves",
|
||||||
|
pods: []*v1.Pod{p13},
|
||||||
|
nodes: []*v1.Node{node1, node2},
|
||||||
|
expectedEvictedPodCount: 0,
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Pods with matching ownerrefs and at not all matching image should not trigger an eviction",
|
||||||
|
pods: []*v1.Pod{p11, p13},
|
||||||
|
nodes: []*v1.Node{node1, node2},
|
||||||
|
expectedEvictedPodCount: 0,
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Three pods in the `dev` Namespace, bound to same ReplicaSet. Only node available has a taint, and nodeFit set to true. 0 should be evicted.",
|
||||||
|
pods: []*v1.Pod{p1, p2, p3},
|
||||||
|
nodes: []*v1.Node{node1, node3},
|
||||||
|
expectedEvictedPodCount: 0,
|
||||||
|
strategy: api.DeschedulerStrategy{Params: &api.StrategyParameters{NodeFit: true}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Three pods in the `node-fit` Namespace, bound to same ReplicaSet, all with a nodeSelector. Only node available has an incorrect node label, and nodeFit set to true. 0 should be evicted.",
|
||||||
|
pods: []*v1.Pod{p15, p16, p17},
|
||||||
|
nodes: []*v1.Node{node1, node4},
|
||||||
|
expectedEvictedPodCount: 0,
|
||||||
|
strategy: api.DeschedulerStrategy{Params: &api.StrategyParameters{NodeFit: true}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Three pods in the `node-fit` Namespace, bound to same ReplicaSet. Only node available is not schedulable, and nodeFit set to true. 0 should be evicted.",
|
||||||
|
pods: []*v1.Pod{p1, p2, p3},
|
||||||
|
nodes: []*v1.Node{node1, node5},
|
||||||
|
expectedEvictedPodCount: 0,
|
||||||
|
strategy: api.DeschedulerStrategy{Params: &api.StrategyParameters{NodeFit: true}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Three pods in the `node-fit` Namespace, bound to same ReplicaSet. Only node available does not have enough CPU, and nodeFit set to true. 0 should be evicted.",
|
||||||
|
pods: []*v1.Pod{p1, p2, p3, p19},
|
||||||
|
nodes: []*v1.Node{node1, node6},
|
||||||
|
expectedEvictedPodCount: 0,
|
||||||
|
strategy: api.DeschedulerStrategy{Params: &api.StrategyParameters{NodeFit: true}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Three pods in the `node-fit` Namespace, bound to same ReplicaSet. Only node available has enough CPU, and nodeFit set to true. 1 should be evicted.",
|
||||||
|
pods: []*v1.Pod{p1, p2, p3, p20},
|
||||||
|
nodes: []*v1.Node{node1, node6},
|
||||||
|
expectedEvictedPodCount: 1,
|
||||||
|
strategy: api.DeschedulerStrategy{Params: &api.StrategyParameters{NodeFit: true}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.description, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
npe := utils.NodePodEvictedCount{}
|
var objs []runtime.Object
|
||||||
npe[node] = 0
|
for _, node := range testCase.nodes {
|
||||||
fakeClient := &fake.Clientset{}
|
objs = append(objs, node)
|
||||||
fakeClient.Fake.AddReactor("list", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
}
|
||||||
return true, &v1.PodList{Items: testCase.pods}, nil
|
for _, pod := range testCase.pods {
|
||||||
|
objs = append(objs, pod)
|
||||||
|
}
|
||||||
|
fakeClient := fake.NewSimpleClientset(objs...)
|
||||||
|
|
||||||
|
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
podInformer := sharedInformerFactory.Core().V1().Pods()
|
||||||
|
|
||||||
|
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Build get pods assigned to node function error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedInformerFactory.Start(ctx.Done())
|
||||||
|
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||||
|
|
||||||
|
podEvictor := evictions.NewPodEvictor(
|
||||||
|
fakeClient,
|
||||||
|
"v1",
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
testCase.nodes,
|
||||||
|
getPodsAssignedToNode,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
RemoveDuplicatePods(ctx, fakeClient, testCase.strategy, testCase.nodes, podEvictor, getPodsAssignedToNode)
|
||||||
|
podsEvicted := podEvictor.TotalEvicted()
|
||||||
|
if podsEvicted != testCase.expectedEvictedPodCount {
|
||||||
|
t.Errorf("Test error for description: %s. Expected evicted pods count %v, got %v", testCase.description, testCase.expectedEvictedPodCount, podsEvicted)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
fakeClient.Fake.AddReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) {
|
|
||||||
return true, node, nil
|
|
||||||
})
|
|
||||||
podsEvicted := deleteDuplicatePods(fakeClient, "v1", []*v1.Node{node}, false, npe, testCase.maxPodsToEvict, false)
|
|
||||||
if podsEvicted != testCase.expectedEvictedPodCount {
|
|
||||||
t.Errorf("Test error for description: %s. Expected evicted pods count %v, got %v", testCase.description, testCase.expectedEvictedPodCount, podsEvicted)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoveDuplicatesUniformly(t *testing.T) {
|
||||||
|
setRSOwnerRef2 := func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
|
||||||
|
{Kind: "ReplicaSet", APIVersion: "v1", Name: "replicaset-2"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTwoRSOwnerRef := func(pod *v1.Pod) {
|
||||||
|
pod.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
|
||||||
|
{Kind: "ReplicaSet", APIVersion: "v1", Name: "replicaset-1"},
|
||||||
|
{Kind: "ReplicaSet", APIVersion: "v1", Name: "replicaset-2"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTolerationsK1 := func(pod *v1.Pod) {
|
||||||
|
test.SetRSOwnerRef(pod)
|
||||||
|
pod.Spec.Tolerations = []v1.Toleration{
|
||||||
|
{
|
||||||
|
Key: "k1",
|
||||||
|
Value: "v1",
|
||||||
|
Operator: v1.TolerationOpEqual,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTolerationsK2 := func(pod *v1.Pod) {
|
||||||
|
test.SetRSOwnerRef(pod)
|
||||||
|
pod.Spec.Tolerations = []v1.Toleration{
|
||||||
|
{
|
||||||
|
Key: "k2",
|
||||||
|
Value: "v2",
|
||||||
|
Operator: v1.TolerationOpEqual,
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMasterNoScheduleTaint := func(node *v1.Node) {
|
||||||
|
node.Spec.Taints = []v1.Taint{
|
||||||
|
{
|
||||||
|
Effect: v1.TaintEffectNoSchedule,
|
||||||
|
Key: "node-role.kubernetes.io/control-plane",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMasterNoScheduleLabel := func(node *v1.Node) {
|
||||||
|
if node.ObjectMeta.Labels == nil {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{}
|
||||||
|
}
|
||||||
|
node.ObjectMeta.Labels["node-role.kubernetes.io/control-plane"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkerLabel := func(node *v1.Node) {
|
||||||
|
if node.ObjectMeta.Labels == nil {
|
||||||
|
node.ObjectMeta.Labels = map[string]string{}
|
||||||
|
}
|
||||||
|
node.ObjectMeta.Labels["node-role.kubernetes.io/worker"] = "k1"
|
||||||
|
node.ObjectMeta.Labels["node-role.kubernetes.io/worker"] = "k2"
|
||||||
|
}
|
||||||
|
|
||||||
|
setNotMasterNodeSelectorK1 := func(pod *v1.Pod) {
|
||||||
|
test.SetRSOwnerRef(pod)
|
||||||
|
pod.Spec.Affinity = &v1.Affinity{
|
||||||
|
NodeAffinity: &v1.NodeAffinity{
|
||||||
|
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||||
|
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||||
|
{
|
||||||
|
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||||
|
{
|
||||||
|
Key: "node-role.kubernetes.io/control-plane",
|
||||||
|
Operator: v1.NodeSelectorOpDoesNotExist,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "k1",
|
||||||
|
Operator: v1.NodeSelectorOpDoesNotExist,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setNotMasterNodeSelectorK2 := func(pod *v1.Pod) {
|
||||||
|
test.SetRSOwnerRef(pod)
|
||||||
|
pod.Spec.Affinity = &v1.Affinity{
|
||||||
|
NodeAffinity: &v1.NodeAffinity{
|
||||||
|
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
|
||||||
|
NodeSelectorTerms: []v1.NodeSelectorTerm{
|
||||||
|
{
|
||||||
|
MatchExpressions: []v1.NodeSelectorRequirement{
|
||||||
|
{
|
||||||
|
Key: "node-role.kubernetes.io/control-plane",
|
||||||
|
Operator: v1.NodeSelectorOpDoesNotExist,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "k2",
|
||||||
|
Operator: v1.NodeSelectorOpDoesNotExist,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkerLabelSelectorK1 := func(pod *v1.Pod) {
|
||||||
|
test.SetRSOwnerRef(pod)
|
||||||
|
if pod.Spec.NodeSelector == nil {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{}
|
||||||
|
}
|
||||||
|
pod.Spec.NodeSelector["node-role.kubernetes.io/worker"] = "k1"
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkerLabelSelectorK2 := func(pod *v1.Pod) {
|
||||||
|
test.SetRSOwnerRef(pod)
|
||||||
|
if pod.Spec.NodeSelector == nil {
|
||||||
|
pod.Spec.NodeSelector = map[string]string{}
|
||||||
|
}
|
||||||
|
pod.Spec.NodeSelector["node-role.kubernetes.io/worker"] = "k2"
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
pods []*v1.Pod
|
||||||
|
nodes []*v1.Node
|
||||||
|
expectedEvictedPodCount uint
|
||||||
|
strategy api.DeschedulerStrategy
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "Evict pods uniformly",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (5,3,1) -> (3,3,3) -> 2 evictions
|
||||||
|
test.BuildTestPod("p1", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p2", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p3", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p4", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p5", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p6", 100, 0, "n2", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p7", 100, 0, "n2", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p8", 100, 0, "n2", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p9", 100, 0, "n3", test.SetRSOwnerRef),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 2,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("n1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n3", 2000, 3000, 10, nil),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods uniformly with one node left out",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (5,3,1) -> (4,4,1) -> 1 eviction
|
||||||
|
test.BuildTestPod("p1", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p2", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p3", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p4", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p5", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p6", 100, 0, "n2", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p7", 100, 0, "n2", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p8", 100, 0, "n2", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p9", 100, 0, "n3", test.SetRSOwnerRef),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 1,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("n1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n2", 2000, 3000, 10, nil),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods uniformly with two replica sets",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (5,3,1) -> (3,3,3) -> 2 evictions
|
||||||
|
test.BuildTestPod("p11", 100, 0, "n1", setTwoRSOwnerRef),
|
||||||
|
test.BuildTestPod("p12", 100, 0, "n1", setTwoRSOwnerRef),
|
||||||
|
test.BuildTestPod("p13", 100, 0, "n1", setTwoRSOwnerRef),
|
||||||
|
test.BuildTestPod("p14", 100, 0, "n1", setTwoRSOwnerRef),
|
||||||
|
test.BuildTestPod("p15", 100, 0, "n1", setTwoRSOwnerRef),
|
||||||
|
test.BuildTestPod("p16", 100, 0, "n2", setTwoRSOwnerRef),
|
||||||
|
test.BuildTestPod("p17", 100, 0, "n2", setTwoRSOwnerRef),
|
||||||
|
test.BuildTestPod("p18", 100, 0, "n2", setTwoRSOwnerRef),
|
||||||
|
test.BuildTestPod("p19", 100, 0, "n3", setTwoRSOwnerRef),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 4,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("n1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n3", 2000, 3000, 10, nil),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods uniformly with two owner references",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (5,3,1) -> (3,3,3) -> 2 evictions
|
||||||
|
test.BuildTestPod("p11", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p12", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p13", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p14", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p15", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p16", 100, 0, "n2", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p17", 100, 0, "n2", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p18", 100, 0, "n2", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p19", 100, 0, "n3", test.SetRSOwnerRef),
|
||||||
|
// (1,3,5) -> (3,3,3) -> 2 evictions
|
||||||
|
test.BuildTestPod("p21", 100, 0, "n1", setRSOwnerRef2),
|
||||||
|
test.BuildTestPod("p22", 100, 0, "n2", setRSOwnerRef2),
|
||||||
|
test.BuildTestPod("p23", 100, 0, "n2", setRSOwnerRef2),
|
||||||
|
test.BuildTestPod("p24", 100, 0, "n2", setRSOwnerRef2),
|
||||||
|
test.BuildTestPod("p25", 100, 0, "n3", setRSOwnerRef2),
|
||||||
|
test.BuildTestPod("p26", 100, 0, "n3", setRSOwnerRef2),
|
||||||
|
test.BuildTestPod("p27", 100, 0, "n3", setRSOwnerRef2),
|
||||||
|
test.BuildTestPod("p28", 100, 0, "n3", setRSOwnerRef2),
|
||||||
|
test.BuildTestPod("p29", 100, 0, "n3", setRSOwnerRef2),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 4,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("n1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n3", 2000, 3000, 10, nil),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods with number of pods less than nodes",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (2,0,0) -> (1,1,0) -> 1 eviction
|
||||||
|
test.BuildTestPod("p1", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
test.BuildTestPod("p2", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 1,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("n1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n3", 2000, 3000, 10, nil),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods with number of pods less than nodes, but ignore different pods with the same ownerref",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (1, 0, 0) for "bar","baz" images -> no eviction, even with a matching ownerKey
|
||||||
|
// (2, 0, 0) for "foo" image -> (1,1,0) - 1 eviction
|
||||||
|
// In this case the only "real" duplicates are p1 and p4, so one of those should be evicted
|
||||||
|
buildTestPodWithImage("p1", "n1", "foo"),
|
||||||
|
buildTestPodWithImage("p2", "n1", "bar"),
|
||||||
|
buildTestPodWithImage("p3", "n1", "baz"),
|
||||||
|
buildTestPodWithImage("p4", "n1", "foo"),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 1,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("n1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n3", 2000, 3000, 10, nil),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods with a single pod with three nodes",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (2,0,0) -> (1,1,0) -> 1 eviction
|
||||||
|
test.BuildTestPod("p1", 100, 0, "n1", test.SetRSOwnerRef),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 0,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("n1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("n3", 2000, 3000, 10, nil),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods uniformly respecting taints",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (5,3,1,0,0,0) -> (3,3,3,0,0,0) -> 2 evictions
|
||||||
|
test.BuildTestPod("p1", 100, 0, "worker1", setTolerationsK1),
|
||||||
|
test.BuildTestPod("p2", 100, 0, "worker1", setTolerationsK2),
|
||||||
|
test.BuildTestPod("p3", 100, 0, "worker1", setTolerationsK1),
|
||||||
|
test.BuildTestPod("p4", 100, 0, "worker1", setTolerationsK2),
|
||||||
|
test.BuildTestPod("p5", 100, 0, "worker1", setTolerationsK1),
|
||||||
|
test.BuildTestPod("p6", 100, 0, "worker2", setTolerationsK2),
|
||||||
|
test.BuildTestPod("p7", 100, 0, "worker2", setTolerationsK1),
|
||||||
|
test.BuildTestPod("p8", 100, 0, "worker2", setTolerationsK2),
|
||||||
|
test.BuildTestPod("p9", 100, 0, "worker3", setTolerationsK1),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 2,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("worker1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("worker2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("worker3", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("master1", 2000, 3000, 10, setMasterNoScheduleTaint),
|
||||||
|
test.BuildTestNode("master2", 2000, 3000, 10, setMasterNoScheduleTaint),
|
||||||
|
test.BuildTestNode("master3", 2000, 3000, 10, setMasterNoScheduleTaint),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods uniformly respecting RequiredDuringSchedulingIgnoredDuringExecution node affinity",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (5,3,1,0,0,0) -> (3,3,3,0,0,0) -> 2 evictions
|
||||||
|
test.BuildTestPod("p1", 100, 0, "worker1", setNotMasterNodeSelectorK1),
|
||||||
|
test.BuildTestPod("p2", 100, 0, "worker1", setNotMasterNodeSelectorK2),
|
||||||
|
test.BuildTestPod("p3", 100, 0, "worker1", setNotMasterNodeSelectorK1),
|
||||||
|
test.BuildTestPod("p4", 100, 0, "worker1", setNotMasterNodeSelectorK2),
|
||||||
|
test.BuildTestPod("p5", 100, 0, "worker1", setNotMasterNodeSelectorK1),
|
||||||
|
test.BuildTestPod("p6", 100, 0, "worker2", setNotMasterNodeSelectorK2),
|
||||||
|
test.BuildTestPod("p7", 100, 0, "worker2", setNotMasterNodeSelectorK1),
|
||||||
|
test.BuildTestPod("p8", 100, 0, "worker2", setNotMasterNodeSelectorK2),
|
||||||
|
test.BuildTestPod("p9", 100, 0, "worker3", setNotMasterNodeSelectorK1),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 2,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("worker1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("worker2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("worker3", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("master1", 2000, 3000, 10, setMasterNoScheduleLabel),
|
||||||
|
test.BuildTestNode("master2", 2000, 3000, 10, setMasterNoScheduleLabel),
|
||||||
|
test.BuildTestNode("master3", 2000, 3000, 10, setMasterNoScheduleLabel),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods uniformly respecting node selector",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (5,3,1,0,0,0) -> (3,3,3,0,0,0) -> 2 evictions
|
||||||
|
test.BuildTestPod("p1", 100, 0, "worker1", setWorkerLabelSelectorK1),
|
||||||
|
test.BuildTestPod("p2", 100, 0, "worker1", setWorkerLabelSelectorK2),
|
||||||
|
test.BuildTestPod("p3", 100, 0, "worker1", setWorkerLabelSelectorK1),
|
||||||
|
test.BuildTestPod("p4", 100, 0, "worker1", setWorkerLabelSelectorK2),
|
||||||
|
test.BuildTestPod("p5", 100, 0, "worker1", setWorkerLabelSelectorK1),
|
||||||
|
test.BuildTestPod("p6", 100, 0, "worker2", setWorkerLabelSelectorK2),
|
||||||
|
test.BuildTestPod("p7", 100, 0, "worker2", setWorkerLabelSelectorK1),
|
||||||
|
test.BuildTestPod("p8", 100, 0, "worker2", setWorkerLabelSelectorK2),
|
||||||
|
test.BuildTestPod("p9", 100, 0, "worker3", setWorkerLabelSelectorK1),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 2,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("worker1", 2000, 3000, 10, setWorkerLabel),
|
||||||
|
test.BuildTestNode("worker2", 2000, 3000, 10, setWorkerLabel),
|
||||||
|
test.BuildTestNode("worker3", 2000, 3000, 10, setWorkerLabel),
|
||||||
|
test.BuildTestNode("master1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("master2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("master3", 2000, 3000, 10, nil),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Evict pods uniformly respecting node selector with zero target nodes",
|
||||||
|
pods: []*v1.Pod{
|
||||||
|
// (5,3,1,0,0,0) -> (3,3,3,0,0,0) -> 2 evictions
|
||||||
|
test.BuildTestPod("p1", 100, 0, "worker1", setWorkerLabelSelectorK1),
|
||||||
|
test.BuildTestPod("p2", 100, 0, "worker1", setWorkerLabelSelectorK2),
|
||||||
|
test.BuildTestPod("p3", 100, 0, "worker1", setWorkerLabelSelectorK1),
|
||||||
|
test.BuildTestPod("p4", 100, 0, "worker1", setWorkerLabelSelectorK2),
|
||||||
|
test.BuildTestPod("p5", 100, 0, "worker1", setWorkerLabelSelectorK1),
|
||||||
|
test.BuildTestPod("p6", 100, 0, "worker2", setWorkerLabelSelectorK2),
|
||||||
|
test.BuildTestPod("p7", 100, 0, "worker2", setWorkerLabelSelectorK1),
|
||||||
|
test.BuildTestPod("p8", 100, 0, "worker2", setWorkerLabelSelectorK2),
|
||||||
|
test.BuildTestPod("p9", 100, 0, "worker3", setWorkerLabelSelectorK1),
|
||||||
|
},
|
||||||
|
expectedEvictedPodCount: 0,
|
||||||
|
nodes: []*v1.Node{
|
||||||
|
test.BuildTestNode("worker1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("worker2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("worker3", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("master1", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("master2", 2000, 3000, 10, nil),
|
||||||
|
test.BuildTestNode("master3", 2000, 3000, 10, nil),
|
||||||
|
},
|
||||||
|
strategy: api.DeschedulerStrategy{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.description, func(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var objs []runtime.Object
|
||||||
|
for _, node := range testCase.nodes {
|
||||||
|
objs = append(objs, node)
|
||||||
|
}
|
||||||
|
for _, pod := range testCase.pods {
|
||||||
|
objs = append(objs, pod)
|
||||||
|
}
|
||||||
|
fakeClient := fake.NewSimpleClientset(objs...)
|
||||||
|
|
||||||
|
sharedInformerFactory := informers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
podInformer := sharedInformerFactory.Core().V1().Pods()
|
||||||
|
|
||||||
|
getPodsAssignedToNode, err := podutil.BuildGetPodsAssignedToNodeFunc(podInformer)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Build get pods assigned to node function error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedInformerFactory.Start(ctx.Done())
|
||||||
|
sharedInformerFactory.WaitForCacheSync(ctx.Done())
|
||||||
|
|
||||||
|
podEvictor := evictions.NewPodEvictor(
|
||||||
|
fakeClient,
|
||||||
|
policyv1.SchemeGroupVersion.String(),
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
testCase.nodes,
|
||||||
|
getPodsAssignedToNode,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
RemoveDuplicatePods(ctx, fakeClient, testCase.strategy, testCase.nodes, podEvictor, getPodsAssignedToNode)
|
||||||
|
podsEvicted := podEvictor.TotalEvicted()
|
||||||
|
if podsEvicted != testCase.expectedEvictedPodCount {
|
||||||
|
t.Errorf("Test error for description: %s. Expected evicted pods count %v, got %v", testCase.description, testCase.expectedEvictedPodCount, podsEvicted)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
179
pkg/descheduler/strategies/failedpods.go
Normal file
179
pkg/descheduler/strategies/failedpods.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
package strategies
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
"sigs.k8s.io/descheduler/pkg/api"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/descheduler/evictions"
|
||||||
|
podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod"
|
||||||
|
"sigs.k8s.io/descheduler/pkg/descheduler/strategies/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// validatedFailedPodsStrategyParams contains validated strategy parameters
|
||||||
|
type validatedFailedPodsStrategyParams struct {
|
||||||
|
validation.ValidatedStrategyParams
|
||||||
|
includingInitContainers bool
|
||||||
|
reasons sets.String
|
||||||
|
excludeOwnerKinds sets.String
|
||||||
|
minPodLifetimeSeconds *uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFailedPods removes Pods that are in failed status phase.
|
||||||
|
func RemoveFailedPods(
|
||||||
|
ctx context.Context,
|
||||||
|
client clientset.Interface,
|
||||||
|
strategy api.DeschedulerStrategy,
|
||||||
|
nodes []*v1.Node,
|
||||||
|
podEvictor *evictions.PodEvictor,
|
||||||
|
getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc,
|
||||||
|
) {
|
||||||
|
strategyParams, err := validateAndParseRemoveFailedPodsParams(ctx, client, strategy.Params)
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "Invalid RemoveFailedPods parameters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
evictable := podEvictor.Evictable(
|
||||||
|
evictions.WithPriorityThreshold(strategyParams.ThresholdPriority),
|
||||||
|
evictions.WithNodeFit(strategyParams.NodeFit),
|
||||||
|
evictions.WithLabelSelector(strategyParams.LabelSelector),
|
||||||
|
)
|
||||||
|
|
||||||
|
var labelSelector *metav1.LabelSelector
|
||||||
|
if strategy.Params != nil {
|
||||||
|
labelSelector = strategy.Params.LabelSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
podFilter, err := podutil.NewOptions().
|
||||||
|
WithFilter(evictable.IsEvictable).
|
||||||
|
WithNamespaces(strategyParams.IncludedNamespaces).
|
||||||
|
WithoutNamespaces(strategyParams.ExcludedNamespaces).
|
||||||
|
WithLabelSelector(labelSelector).
|
||||||
|
BuildFilterFunc()
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "Error initializing pod filter function")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Only list failed pods
|
||||||
|
phaseFilter := func(pod *v1.Pod) bool { return pod.Status.Phase == v1.PodFailed }
|
||||||
|
podFilter = podutil.WrapFilterFuncs(phaseFilter, podFilter)
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
klog.V(1).InfoS("Processing node", "node", klog.KObj(node))
|
||||||
|
pods, err := podutil.ListAllPodsOnANode(node.Name, getPodsAssignedToNode, podFilter)
|
||||||
|
if err != nil {
|
||||||
|
klog.ErrorS(err, "Error listing a nodes failed pods", "node", klog.KObj(node))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, pod := range pods {
|
||||||
|
if err = validateFailedPodShouldEvict(pod, *strategyParams); err != nil {
|
||||||
|
klog.V(4).InfoS(fmt.Sprintf("ignoring pod for eviction due to: %s", err.Error()), "pod", klog.KObj(pod))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = podEvictor.EvictPod(ctx, pods[i], node, "FailedPod"); err != nil {
|
||||||
|
klog.ErrorS(err, "Error evicting pod", "pod", klog.KObj(pod))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAndParseRemoveFailedPodsParams(
|
||||||
|
ctx context.Context,
|
||||||
|
client clientset.Interface,
|
||||||
|
params *api.StrategyParameters,
|
||||||
|
) (*validatedFailedPodsStrategyParams, error) {
|
||||||
|
if params == nil {
|
||||||
|
return &validatedFailedPodsStrategyParams{
|
||||||
|
ValidatedStrategyParams: validation.DefaultValidatedStrategyParams(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
strategyParams, err := validation.ValidateAndParseStrategyParams(ctx, client, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var reasons, excludeOwnerKinds sets.String
|
||||||
|
var includingInitContainers bool
|
||||||
|
var minPodLifetimeSeconds *uint
|
||||||
|
if params.FailedPods != nil {
|
||||||
|
reasons = sets.NewString(params.FailedPods.Reasons...)
|
||||||
|
includingInitContainers = params.FailedPods.IncludingInitContainers
|
||||||
|
excludeOwnerKinds = sets.NewString(params.FailedPods.ExcludeOwnerKinds...)
|
||||||
|
minPodLifetimeSeconds = params.FailedPods.MinPodLifetimeSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
return &validatedFailedPodsStrategyParams{
|
||||||
|
ValidatedStrategyParams: *strategyParams,
|
||||||
|
includingInitContainers: includingInitContainers,
|
||||||
|
reasons: reasons,
|
||||||
|
excludeOwnerKinds: excludeOwnerKinds,
|
||||||
|
minPodLifetimeSeconds: minPodLifetimeSeconds,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateFailedPodShouldEvict looks at strategy params settings to see if the Pod
|
||||||
|
// should be evicted given the params in the PodFailed policy.
|
||||||
|
func validateFailedPodShouldEvict(pod *v1.Pod, strategyParams validatedFailedPodsStrategyParams) error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
if strategyParams.minPodLifetimeSeconds != nil {
|
||||||
|
podAgeSeconds := uint(metav1.Now().Sub(pod.GetCreationTimestamp().Local()).Seconds())
|
||||||
|
if podAgeSeconds < *strategyParams.minPodLifetimeSeconds {
|
||||||
|
errs = append(errs, fmt.Errorf("pod does not exceed the min age seconds of %d", *strategyParams.minPodLifetimeSeconds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strategyParams.excludeOwnerKinds) > 0 {
|
||||||
|
ownerRefList := podutil.OwnerRef(pod)
|
||||||
|
for _, owner := range ownerRefList {
|
||||||
|
if strategyParams.excludeOwnerKinds.Has(owner.Kind) {
|
||||||
|
errs = append(errs, fmt.Errorf("pod's owner kind of %s is excluded", owner.Kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strategyParams.reasons) > 0 {
|
||||||
|
reasons := getFailedContainerStatusReasons(pod.Status.ContainerStatuses)
|
||||||
|
|
||||||
|
if pod.Status.Phase == v1.PodFailed && pod.Status.Reason != "" {
|
||||||
|
reasons = append(reasons, pod.Status.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strategyParams.includingInitContainers {
|
||||||
|
reasons = append(reasons, getFailedContainerStatusReasons(pod.Status.InitContainerStatuses)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strategyParams.reasons.HasAny(reasons...) {
|
||||||
|
errs = append(errs, fmt.Errorf("pod does not match any of the reasons"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilerrors.NewAggregate(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFailedContainerStatusReasons(containerStatuses []v1.ContainerStatus) []string {
|
||||||
|
reasons := make([]string, 0)
|
||||||
|
|
||||||
|
for _, containerStatus := range containerStatuses {
|
||||||
|
if containerStatus.State.Waiting != nil && containerStatus.State.Waiting.Reason != "" {
|
||||||
|
reasons = append(reasons, containerStatus.State.Waiting.Reason)
|
||||||
|
}
|
||||||
|
if containerStatus.State.Terminated != nil && containerStatus.State.Terminated.Reason != "" {
|
||||||
|
reasons = append(reasons, containerStatus.State.Terminated.Reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reasons
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user