<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://lavr.one/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lavr.one/" rel="alternate" type="text/html" /><updated>2026-10-10T10:46:47+00:00</updated><id>https://lavr.one/feed.xml</id><title type="html">Лавский пишет</title><subtitle>Личный блог Дмитрия Лаврухина. DevOps, жизнь в Черногории, DIY, игры.</subtitle><author><name>Dmitrii Lavrukhin</name></author><entry><title type="html">Pacific Drive — 90 часов в зоне аномалий</title><link href="https://lavr.one/2026/04/25/pacific-drive.html" rel="alternate" type="text/html" title="Pacific Drive — 90 часов в зоне аномалий" /><published>2026-04-25T00:00:00+00:00</published><updated>2026-04-25T00:00:00+00:00</updated><id>https://lavr.one/2026/04/25/pacific-drive</id><content type="html" xml:base="https://lavr.one/2026/04/25/pacific-drive.html"><![CDATA[<p><img src="/assets/images/pacific-drive.jpg" alt="Pacific Drive — гараж" /></p>

<p>Pacific Drive поначалу кажется хоррором. Оказываешься в непонятном мире, вроде какой-то полуостров или остров где-то на северо-западе США, заполненный аномалиями в стиле Стругацких. Тебе дают старенький разбитый универсал, и во взаимодействии с этой машиной вся суть игры. Это твой напарник, который оберегает тебя от этих аномалий и на котором ты будешь преодолевать зону. Сразу хочется начать исследование и разгадать причину этих аномалий.</p>

<p>Машина очень кастомизируется, можно сделать защищённый бронёй танк, но он не поможет если в тебя ударит молния или ты попадёшь в радиационный шторм. А ещё постоянно кончается топливо и разряжается аккумулятор. Но зона содержит кучу ресурсов, которые можно добыть с оставленных на обочине автомобилей или научных фургончиков на опушке. Есть какие-то якоря энергии, которые можно собирать, чтобы открывать телепорты в свой гараж.</p>

<p>Сперва ничего не понятно, но с тобой на связи учёные из прошлого, и картинка начинает потихоньку проясняться. Я поставил ИИ-перевод звука, но всё же следить за ходом повествования в то время когда ты бегаешь по зоне и спасаешься от наэлектризованных комков мусора или внезапно выстреливающих из-под земли столбов довольно сложно.</p>

<p>Кайфа добавляют периоды релакса в гараже, когда ты спокойно можешь починить машину, провести исследования, скрафтить более мощный двигатель, и даже поставить баскетбольное кольцо в свою лабораторию.</p>

<p>Я провёл в игре около 90 часов, и это было великолепно. Под конец немного надоело возвращаться в гараж и по новой доезжать до целей миссии, но по открывающимся автострадам это стало побыстрей.</p>

<p>Игра 10/10, рекомендую.</p>]]></content><author><name>Dmitrii Lavrukhin</name></author><category term="gaming" /><category term="indie" /><summary type="html"><![CDATA[Pacific Drive поначалу кажется хоррором. Оказываешься в непонятном мире, заполненном аномалиями в стиле Стругацких. Тебе дают старенький разбитый универсал, и во взаимодействии с этой машиной вся суть игры.]]></summary></entry><entry><title type="html">Shutdown script as systemd service</title><link href="https://lavr.one/2024/11/12/shutdown-script.html" rel="alternate" type="text/html" title="Shutdown script as systemd service" /><published>2024-11-12T00:00:00+00:00</published><updated>2024-11-12T00:00:00+00:00</updated><id>https://lavr.one/2024/11/12/shutdown-script</id><content type="html" xml:base="https://lavr.one/2024/11/12/shutdown-script.html"><![CDATA[<p>Недавно у меня возникла задача запускать один скрипт при выключении сервера. А именно нужно было удалять сервер из Zabbix при Rollout Instance group в GCP. Само по себе решение такого мониторинга Instance группы с помоью стандартного zabbix linux шаблона очень спорное, однако в этой статье я хочу рассказать про запуск скрипта при выключении сервера.
Первой мыслью было сделать скрипт, например /root/script.sh, добавить на него права исполнения (что поначалу я забыл сделать и словил на этом неприятный косяк, не понимая почему сервис не запускается).
Важно заметить, что в начале скрипта нужно коректно указать shebang, иначе systemd откажется исполнять ваш скрипт.</p>
<h4 id="скрипт-rootscriptsh">скрипт /root/script.sh</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nb">echo</span> <span class="si">$(</span><span class="nb">date</span><span class="si">)</span> <span class="o">&gt;&gt;</span> /var/log/script.log
</code></pre></div></div>
<p>дальше создаем файл с описанием systemd service, который будет запускаться при выключении, перезагрузке сервера.</p>
<h4 id="etcsystemdsystemmy-shutdown-serviceservice">/etc/systemd/system/my-shutdown-service.service</h4>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># my-shutdown-service.service</span>

<span class="o">[</span>Unit]
<span class="nv">Description</span><span class="o">=</span>Run some task at shutdown
<span class="nv">After</span><span class="o">=</span>syslog.service network.target

<span class="o">[</span>Service]
<span class="nv">Type</span><span class="o">=</span>oneshot
<span class="nv">RemainAfterExit</span><span class="o">=</span><span class="nb">yes
</span><span class="nv">ExecStop</span><span class="o">=</span>/root/script.sh
<span class="nv">Restart</span><span class="o">=</span>on-failure
<span class="nv">RestartSec</span><span class="o">=</span>1s

<span class="o">[</span>Install]
<span class="nv">WantedBy</span><span class="o">=</span>multi-user.target
</code></pre></div></div>
<p>Пояснение:
	•	Type=oneshot означает, что команда выполняется один раз. Обычно для сервиса с типом oneshot он завершает свою работу после выполнения команды ExecStart, но так как я не хочу ничего делать при старте сервиса, команды ExecStart здесь нет.
	•	Я использую RemainAfterExit=yes, что позволяет сервису оставаться запущенным, даже если команда ExecStart отсутствует.
	•	ExecStop используется для выполнения команды при завершении работы.</p>

<p>Директива After=syslog.service network.target гарантирует, что сервис не будет запускаться до того, как заработает сервис syslog и сеть. Более того, так как systemd останавливает сервисы в обратном порядке их запуска, это также гарантирует, что syslog и сеть будут работать, когда будет выполняться команда ExecStop.</p>

<p>Хотя существует множество различных сервисов для работы с логами (syslog), большинство из них используют псевдоним syslog, поэтому After=syslog.service будет работать независимо от используемого сервиса (например, если используется rsyslog, это всё равно работает, так как rsyslog объявляет syslog как псевдоним).</p>
<h4 id="установка">Установка</h4>
<p>Сперва я добавил startup-script, это <a href="https://cloud.google.com/compute/docs/instances/startup-scripts/linux">штатная возможность</a> в GCP:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gsutil <span class="nb">cp </span>gs://my-project/my-bucket/script.sh /root/script.sh
<span class="nb">chmod</span> +x /root/script.sh
<span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> &gt; /etc/systemd/system/my-shutdown-service.service

# my-shutdown-service.service
[Unit]
Description=Run some task at shutdown
After=syslog.service network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStop=/root/script.sh
Restart=on-failure
RestartSec=1s

[Install]
WantedBy=multi-user.target

# EOF
# systemctl daemon-reload
# systemctl enable --now remove_zabbix_host.service
</span></code></pre></div></div>
<p>И это замечательно работало. Однако потом я обнаружил что в GCP есть штатная возможность добавлять <a href="https://cloud.google.com/compute/docs/shutdownscript">shutdown-script</a> прямо на этапе конфигурирования ВМ. Это тоже работает нормально, поскольку основано на acpi-сигналах.</p>

<h4 id="ps">P.S.</h4>
<p>Я всячески не приветствую использование shutdown-scripts. Поскольку это очень тонкий момент. При выключении сервера время на выполнение скрипта ограничено. И еще, если при выполнении скрипта вам нужна сеть, даже если вы указали зависимость от network, не во всех дистрибутивах это будет работать. Но Если надо временно подложить соломку, то вполне сойдет.</p>]]></content><author><name>Dmitrii Lavrukhin</name></author><category term="cloud" /><category term="gcp" /><category term="systemd" /><category term="linux" /><summary type="html"><![CDATA[Недавно у меня возникла задача запускать один скрипт при выключении сервера. А именно нужно было удалять сервер из Zabbix при Rollout Instance group в GCP. Само по себе решение такого мониторинга Instance группы с помоью стандартного zabbix linux шаблона очень спорное, однако в этой статье я хочу рассказать про запуск скрипта при выключении сервера. Первой мыслью было сделать скрипт, например /root/script.sh, добавить на него права исполнения (что поначалу я забыл сделать и словил на этом неприятный косяк, не понимая почему сервис не запускается). Важно заметить, что в начале скрипта нужно коректно указать shebang, иначе systemd откажется исполнять ваш скрипт. скрипт /root/script.sh #!/bin/bash echo $(date) &gt;&gt; /var/log/script.log дальше создаем файл с описанием systemd service, который будет запускаться при выключении, перезагрузке сервера. /etc/systemd/system/my-shutdown-service.service ```bash my-shutdown-service.service]]></summary></entry><entry><title type="html">Лимиты inode в xfs</title><link href="https://lavr.one/2024/06/01/xfs-inodes-limits.html" rel="alternate" type="text/html" title="Лимиты inode в xfs" /><published>2024-06-01T00:00:00+00:00</published><updated>2024-06-01T00:00:00+00:00</updated><id>https://lavr.one/2024/06/01/xfs-inodes-limits</id><content type="html" xml:base="https://lavr.one/2024/06/01/xfs-inodes-limits.html"><![CDATA[<p>XFS является очень популярной файловой системой. Она стала такой популярной после того как RedHat <a href="https://access.redhat.com/documentation/ru-ru/red_hat_enterprise_linux/7/html/storage_administration_guide/ch-xfs#:~:text=XFS%20is%20the%20default%20file%20system%20for%20Red%20Hat%20Enterprise%20Linux%207.&amp;text=XFS%20supports%20metadata%20journaling%2C%20which,restore%20utilities%20specific%20to%20XFS.">сделал ее фс по умолчанию</a> для redhat 7. Среди преимуществ xfs выделяют динамическое выделение inode, поддержку дисковых квот, ну и остальные преимущества журналируемых фс. Про псевдо Copy-on-write можно почитать <a href="https://lwn.net/Articles/747633/">тут</a>. Однако это не обычный CoW, все немного сложнее.</p>
<h3 id="b-tree">B-Tree</h3>
<p>Структура xfs основывается на B-деревьях, точнее их разновидности <a href="https://en.wikipedia.org/wiki/B%2B_tree">B+Tree</a>. Если вы хотите познакомиться с теорией B-деревьев то рекомендую замечательное <a href="https://www.youtube.com/watch?v=WXXetwePSRk">видео Володи Моженкова</a>. У него вобще довольно много интересных видео про структуры данных и аглоритмы.</p>

<p>А зачем B-деревья вообще используют в файловых системах? Эти структуры данных обладают характеристиками, которые делают их идеально подходящими для организации и управления большими объемами данных, особенно на дисковых устройствах. Это позволяет эффективнее использовать дисковое пространство, иметь быстрый доступ к данным, снижать количество операций ввода/вывода, удобно обновлять и удалять данные.</p>

<p>Среди преимуществ XFS выделяют ускорение производительности благодаря Allocation Groups, онлайн дефрагментация, расширение на лету. Конечно, есть и минусы: нельзя уменьшить xfs; медленные операции с метаданными по сравнению с фс вышедшими позднее, проблема с временными метками на пороге <a href="https://en.wikipedia.org/wiki/Year_2038_problem">2038 года</a> (которая была решена введением 64 битных таймштампов для inode в версии ядра 5.10 в 2020 году)</p>

<p>Если вы хотите залезть под капот XFS, и посмотреть как в реальности выглядят эти структуры на диске, из чего состоит суперблок, как выглядят inode на диске, то рекомендую к прочтению <a href="https://righteousit.com/2018/05/23/xfs-part-2-inodes/">цикл статей Hal Pomeranz</a> либо <a href="https://ftp.ntu.edu.tw/linux/utils/fs/xfs/docs/xfs_filesystem_structure.pdf">гигантский труд Silicon Graphics</a> на 184 страницах. Там все очень, очень подробно.</p>

<p>Среди файловых систем не только XFS использует B-деревья. Другие фс также основаны на них: NTFS (New Technology File System): Использует B+-tree для управления индексами файлов и метаданными. HFS+ (Hierarchical File System Plus): Файловая система, используемая в macOS, также применяет B+-tree для хранения каталогов и файлов. ReiserFS: Файловая система, использующая сбалансированные деревья для эффективного управления файловой структурой. Ну и самая свежая <a href="https://btrfs.readthedocs.io/en/latest/">BTRFS</a>, которая является фс по умолчанию для Fedora Workstation.</p>
<h3 id="inode">inode</h3>
<p><a href="https://en.wikipedia.org/wiki/Inode">inode</a>, или его еще называют индексным дескриптором - важная часть файловой системы. Это структура данных, которая хранит в себе информацию о файле или директории, включая имена. Каждый inode содержит в себе атрибуты а также указания на дисковые блоки, в которых хранятся данные файла.</p>
<blockquote>
  <p>Тут я немного задумался, а какой род у inode в русском языке. Это он или она ? “Структура данных” - она. “Индексный дескриптор” - он. “Айнод или инод” - он. Будем считать что и так и так правильно.</p>
</blockquote>

<p>Однако, меня сильно заинтересовала возможность динамического выделения inode, которые, как говорит реклама, должны выделяться по запросу, когда inode подходят к концу. У систем семейства ext, к примеру количество inode является фиксированным, и зависит от общего объема файловой системы. Хочешь получить больше inode - увеличь размер файловой системы. Но все ли так гладко и могут ли inodes выделяться бесконечно ? Конечно нет. У любой системы есть свои пределы. XFS не исключение.</p>
<h3 id="inode-options">inode options</h3>
<p>При создании XFS с помощью утилиты <a href="https://linux.die.net/man/8/mkfs.xfs">mkfs.xfs</a> можно указать параметр <strong>-i inode_options</strong>. Далее я приведу свой перевод официального man от mkfs.xfs с моими комментариями.</p>
<h4 id="размер-inode">Размер inode</h4>
<p>Тут можно указать размер переменной части inode. Поясню, inode состоит из фиксированной по размеру части, и переменной по размеру части, (размер которой можно указать) которая может содержать данные каталога для небольших каталогов; данные атрибутов для небольших наборов атрибутов; данные символических ссылок для небольших символических ссылок; список экстентов файла, для файлов с небольшим количеством экстентов; и корень дерева, описывающий расположение экстентов файла, для файлов с большим количеством экстентов.</p>

<p>Опции которые можно указать для inode:<br />
<code class="language-plaintext highlighter-rouge">size=value | log=value | perblock=value</code><br />
Размер inode указывается либо при помощи <em>size</em> - это размер в байтах, либо как логарифм или основываясь на блоке фс. Подробней можно почитать в <a href="https://linux.die.net/man/8/mkfs.xfs">man mkfs.xfs</a>.</p>

<p>Минимальное значение (и значение по умолчанию) составляет 256 байт без CRC(контрольной суммы), либо 512 байт с включенной CRC. Максимальное значение — 2048 байт (2 Кбайт) с учетом ограничения: размер inode не может превышать половину размера блока файловой системы.</p>

<p>XFS внутренне использует 64-битные номера индексных дескрипторов; однако на количество значащих битов в номере индексного дескриптора влияет геометрия файловой системы. На практике преобладающими факторами являются размер файловой системы и размер индексного дескриптора. Ядро Linux (на 32-разрядных аппаратных платформах) и большинство приложений в настоящее время не могут обрабатывать номера индексных дескрипторов, превышающие 32 значащих бита, поэтому, если в командной строке не указан размер индексных дескрипторов, mkfs.xfs попытается выбрать такой размер, чтобы номера индексных дескрипторов были &lt; 32 бит. Если указан размер индексного дескриптора или если файловая система достаточно велика, mkfs.xfs выдаст предупреждение, если это приведет к созданию номеров индексных дескрипторов &gt; 32 значащих битов.</p>
<h4 id="maxpct">maxpct</h4>
<p>Этот параметр определяет максимальный процент пространства в файловой системе, который может быть выделен для индексных дескрипторов. Значение по умолчанию составляет 25% для файловых систем размером менее 1 ТБ, 5% для файловых систем размером от 1 Тб до 50 ТБ и 1% для файловых систем размером более 50 ТБ.</p>

<p>В режиме выделения индексных дескрипторов по умолчанию блоки индексных дескрипторов выбираются таким образом, чтобы номера индексных дескрипторов не превышали 32 бита, что ограничивает блоки индексных дескрипторов нижней частью файловой системы. Распределитель блоков данных будет избегать этих нижних блоков, чтобы соответствовать указанному maxpct, поэтому высокое значение может привести к тому, что файловая система будет содержать только индексные дескрипторы в значительной части нижних блоков файловой системы. (Это ограничение отсутствует, если файловая система смонтирована с опцией inode64 на 64-битных платформах).</p>

<blockquote>
  <p>Тут у меня есть некоторые претензии к официальному man утилиты mkfs.xfs, поскольку 64-битные системы уже повсеместно распространены, и начиная с версии ядра 3.7 опция inode64 является <a href="https://man7.org/linux/man-pages/man5/xfs.5.html">опцией по умолчанию</a> для 64-битных систем. Так что можно не переживать за эти ограничения, если вы не используете старые linux системы и 32-битную архитектуру процессора. В этом можно убедиться посмотрев опции монтирования через команду <em>mount</em> уже подключенной xfs.
пример:</p>
  <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@server:~# mount |grep /dev/vdi
/dev/vdi on /temp/testmount type xfs (rw,relatime,nouuid,attr2,inode64,logbufs=8,logbsize=32k,noquota)
</code></pre></div>  </div>
</blockquote>

<p>Установка значения <em>maxpct=0</em> означает, что практически вся файловая система может стать блоками индексных дескрипторов.
Это значение можно изменить с помощью xfs_growfs.</p>

<hr />

<p>Параметры <em>maxpct</em> и <em>isize</em> можно посмотреть с помощью утилит <strong>xfs_info</strong> или <strong>xfs_db</strong>, также они пишутся при создании фс с помощью <strong>mkfs.xfs</strong>.
К примеру возьмем один диск размером примерно 1 Гб и создадим на нем xfs:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@jumpbox:~# mkfs.xfs /dev/vdc
meta-data<span class="o">=</span>/dev/vdc               <span class="nv">isize</span><span class="o">=</span>512   <span class="nv">agcount</span><span class="o">=</span>4, <span class="nv">agsize</span><span class="o">=</span>65536 blks
         <span class="o">=</span>                       <span class="nv">sectsz</span><span class="o">=</span>512   <span class="nv">attr</span><span class="o">=</span>2, <span class="nv">projid32bit</span><span class="o">=</span>1
         <span class="o">=</span>                       <span class="nv">crc</span><span class="o">=</span>1        <span class="nv">finobt</span><span class="o">=</span>1, <span class="nv">sparse</span><span class="o">=</span>1, <span class="nv">rmapbt</span><span class="o">=</span>0
         <span class="o">=</span>                       <span class="nv">reflink</span><span class="o">=</span>1    <span class="nv">bigtime</span><span class="o">=</span>1 <span class="nv">inobtcount</span><span class="o">=</span>1 <span class="nv">nrext64</span><span class="o">=</span>0
data     <span class="o">=</span>                       <span class="nv">bsize</span><span class="o">=</span>4096   <span class="nv">blocks</span><span class="o">=</span>262144, <span class="nv">imaxpct</span><span class="o">=</span>25
         <span class="o">=</span>                       <span class="nv">sunit</span><span class="o">=</span>0      <span class="nv">swidth</span><span class="o">=</span>0 blks
naming   <span class="o">=</span>version 2              <span class="nv">bsize</span><span class="o">=</span>4096   ascii-ci<span class="o">=</span>0, <span class="nv">ftype</span><span class="o">=</span>1
log      <span class="o">=</span>internal log           <span class="nv">bsize</span><span class="o">=</span>4096   <span class="nv">blocks</span><span class="o">=</span>16384, <span class="nv">version</span><span class="o">=</span>2
         <span class="o">=</span>                       <span class="nv">sectsz</span><span class="o">=</span>512   <span class="nv">sunit</span><span class="o">=</span>0 blks, lazy-count<span class="o">=</span>1
realtime <span class="o">=</span>none                   <span class="nv">extsz</span><span class="o">=</span>4096   <span class="nv">blocks</span><span class="o">=</span>0, <span class="nv">rtextents</span><span class="o">=</span>0
Discarding blocks...Done.
</code></pre></div></div>
<p>Как видно, создалась фс с размером inode в 512 байт (<strong>isize</strong>), и inode максимально могут занять 25% общего объема файловой системы (<strong>imaxpct</strong>). Но как же увидеть сколько сейчас выделено inode?
Это можно посмотреть утилитой <strong>df</strong>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@jumpbox:/# <span class="nb">df</span> <span class="nt">-i</span> /test
Filesystem     Inodes IUsed  IFree IUse% Mounted on
/dev/vdc       524288     3 524285    1% /test
</code></pre></div></div>
<p>Всего inode выделилось 524288, а занято из них 3. Посчитаем, какой объем от объема файловой системы займут inode если заполнить все выделенные inode. Умножаем количество inode на размер isize:
524288 * 512 = 268435456 байт что соответствует 256 Мб. Это и есть примерно 25% от всей файловой системы размером около 1 Гб:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@jumpbox:/# <span class="nb">df</span> <span class="nt">-k</span> /dev/vdc
Filesystem     1K-blocks  Used Available Use% Mounted on
/dev/vdc          983040 39908    943132   5% /test
</code></pre></div></div>
<p>размер системы 983040 кб, это примерно 960 Мб.</p>

<p>Таким образом система сразу выделила пространство размером примерно 25% объема файловой системы для inode.</p>
<blockquote>
  <p>На самом деле в рассчете получилось даже больше, примерно 26,6%. Вероятно из-за того что df -k показывает размер фс в 1 Кб блоках.</p>
</blockquote>

<p>Давайте посмотрим что будет если заполнить диск файлами до максимума inode. Для быстрого создания файлов используем утилиту parallel.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Создание 500000 файлов параллельно</span>
<span class="nb">seq </span>1 500000 | parallel <span class="nt">-j</span> 10 <span class="s1">'touch $directory_path/file_{}'</span>
</code></pre></div></div>
<p>после того как осталось 32 свободных inode, XFS отказывается создавать новые файлы:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@jumpbox:/test# <span class="nb">df</span> <span class="nt">-i</span> /dev/vdc
Filesystem     Inodes  IUsed IFree IUse% Mounted on
/dev/vdc       524288 524256    32  100% /test
root@jumpbox:/test# <span class="nb">touch </span>32131
<span class="nb">touch</span>: cannot <span class="nb">touch</span> <span class="s1">'32131'</span>: No space left on device
</code></pre></div></div>
<p>Используя xfs_growfs увеличим параметр imaxpct, и проверим что количество inode динамически увеличилось:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@jumpbox:/test# xfs_growfs <span class="nt">-m</span> 30 /dev/vdc
meta-data<span class="o">=</span>/dev/vdc               <span class="nv">isize</span><span class="o">=</span>512    <span class="nv">agcount</span><span class="o">=</span>4, <span class="nv">agsize</span><span class="o">=</span>65536 blks
         <span class="o">=</span>                       <span class="nv">sectsz</span><span class="o">=</span>512   <span class="nv">attr</span><span class="o">=</span>2, <span class="nv">projid32bit</span><span class="o">=</span>1
         <span class="o">=</span>                       <span class="nv">crc</span><span class="o">=</span>1        <span class="nv">finobt</span><span class="o">=</span>1, <span class="nv">sparse</span><span class="o">=</span>1, <span class="nv">rmapbt</span><span class="o">=</span>0
         <span class="o">=</span>                       <span class="nv">reflink</span><span class="o">=</span>1    <span class="nv">bigtime</span><span class="o">=</span>1 <span class="nv">inobtcount</span><span class="o">=</span>1 <span class="nv">nrext64</span><span class="o">=</span>0
data     <span class="o">=</span>                       <span class="nv">bsize</span><span class="o">=</span>4096   <span class="nv">blocks</span><span class="o">=</span>262144, <span class="nv">imaxpct</span><span class="o">=</span>25
         <span class="o">=</span>                       <span class="nv">sunit</span><span class="o">=</span>0      <span class="nv">swidth</span><span class="o">=</span>0 blks
naming   <span class="o">=</span>version 2              <span class="nv">bsize</span><span class="o">=</span>4096   ascii-ci<span class="o">=</span>0, <span class="nv">ftype</span><span class="o">=</span>1
log      <span class="o">=</span>internal log           <span class="nv">bsize</span><span class="o">=</span>4096   <span class="nv">blocks</span><span class="o">=</span>16384, <span class="nv">version</span><span class="o">=</span>2
         <span class="o">=</span>                       <span class="nv">sectsz</span><span class="o">=</span>512   <span class="nv">sunit</span><span class="o">=</span>0 blks, lazy-count<span class="o">=</span>1
realtime <span class="o">=</span>none                   <span class="nv">extsz</span><span class="o">=</span>4096   <span class="nv">blocks</span><span class="o">=</span>0, <span class="nv">rtextents</span><span class="o">=</span>0
inode max percent changed from 25 to 30
root@jumpbox:/test# <span class="nb">df</span> <span class="nt">-i</span> /dev/vdc
Filesystem     Inodes  IUsed  IFree IUse% Mounted on
/dev/vdc       629144 524256 104888   84% /test
root@jumpbox:/test# <span class="nb">touch </span>321123
</code></pre></div></div>
<p>Как видим, количество выделеных inode действительно выросло до 629144, что составляет 30% от объема файловой системы, и теперь занято 84% от их количества. Увеличивать imaxpct можно онлайн на смонтированной файловой системе. Отдавать все пространство файловой системы под inode не очень разумно. Здесь нужен расчет исходя из объема и количество файлов предполагаемых к хранению на XFS.</p>

<p>P.S. единственный момент который для меня оказался неясен в этом маленьком расследовании, всегда ли XFS выделяет сразу весь объем inode который соответствует imaxpct. Возможно, на больших объемах будут выделяться не сразу все inodes, но в документации я не нашел указаний на этот счет. Если мне удастся найти возможность протестировать это на больших дисках, то я дополню эту статью.</p>]]></content><author><name>Dmitrii Lavrukhin</name></author><category term="xfs" /><category term="inode" /><summary type="html"><![CDATA[XFS является очень популярной файловой системой. Она стала такой популярной после того как RedHat сделал ее фс по умолчанию для redhat 7. Среди преимуществ xfs выделяют динамическое выделение inode, поддержку дисковых квот, ну и остальные преимущества журналируемых фс. Про псевдо Copy-on-write можно почитать тут. Однако это не обычный CoW, все немного сложнее. B-Tree Структура xfs основывается на B-деревьях, точнее их разновидности B+Tree. Если вы хотите познакомиться с теорией B-деревьев то рекомендую замечательное видео Володи Моженкова. У него вобще довольно много интересных видео про структуры данных и аглоритмы.]]></summary></entry><entry><title type="html">JWT в контексте kubernetes</title><link href="https://lavr.one/2024/05/01/JWT.html" rel="alternate" type="text/html" title="JWT в контексте kubernetes" /><published>2024-05-01T00:00:00+00:00</published><updated>2024-05-01T00:00:00+00:00</updated><id>https://lavr.one/2024/05/01/JWT</id><content type="html" xml:base="https://lavr.one/2024/05/01/JWT.html"><![CDATA[<p>Многие Devops инженеры, впервые устанавливая kubernetes (обычно minikube или чтонибудь в облаках), и заходя на dashboard впадают в ступор, видя приглашение для ввода:
<img src="/assets/images/kubernetes-dashboard.png" alt="dashboard" />
Так, ну с конфигом понятно, думает инженер - его мне сгенерил сам кубер при установке, а что за токен ? Где нормальные привычные логин и пароль ?</p>

<p>Тут будет лирическое отсупление и поговорим мы немного об аутентификации в Kubernetes. Точнее аж два отступления. Сперва напомню, что <strong>Аутентификация</strong> и <strong>Авторизация</strong> - это разные понятия. Аутентификация - это процесс проверки подлинности пользователей, а Авторизация - это определение прав доступа (сюда как раз относится <a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/">RBAC</a>). Так вот, Аутентификация в кубере, если кратко и на пальцах, устроена следующим образом: Все юзеры кубера делятся на два типа: обычные человеки и сервисы.</p>

<p><em>Обычными пользователями</em> обычно (но не всегда) должен управлять какой-то внешний сервис типа Службы каталогов, который хранит информацию о юзерах, выдает секретные ключи и т.д. Но можно обойтись и без внешнего сервиса. У кубера есть свой центр сертификации, и если юзеру выдать <a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certificates">подписаный этим центром сертификат X509</a>, то при Аутентификации с таким сертификатом кубер вычитывает из этого сертификата имя пользователя и считает его валидным, и дальше обычно RBAC уже определяет что может делать пользователь в кластере. Еще можно создать <a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#static-token-file">статичный файл с токенами</a> и скормить kube-api серверу.</p>

<p><em>Сервисные учетные записи</em> управляются kubernetes api, создаются автоматически или вручную, привязаны к определенным namespaces, и хранят креды в секретах. Сервисная учетка создается командой <code class="language-plaintext highlighter-rouge">kubectl create serviceaccount jenkins</code> а токен для нее либо создается кратковременный командой <code class="language-plaintext highlighter-rouge">kubectl create token jenkins</code> либо <a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-long-lived-api-token-for-a-serviceaccount">длительный токен</a> создается вручную с помощью объекта Secret типа <code class="language-plaintext highlighter-rouge">kubernetes.io/service-account-token</code>
Тут мы и сталкиваемся с JWT токеном. Его выдает команда создания токена. Так что же это такое ?</p>

<p>JWT токен - это ключ доступа, стандартизованного в 2015 году.
<img src="/assets/images/jwt.jpeg" alt="jwt token" />
Аббревиатура расшифровываетя как Json Web Token. JWT состоит из трех частей, разделенных точками:</p>
<ul>
  <li>Header (заголовок): Содержит тип токена (обычно “JWT”) и тип алгоритма подписи (например, HMAC SHA256 или RSA).</li>
  <li>Payload (нагрузка): Это самая важная часть токена, содержащая утверждения о пользователе или субъекте (claims), такие как идентификатор пользователя, срок действия токена, роли пользователя и другие пользовательские данные. Полезная нагрузка может быть закодирована в формате JSON.</li>
  <li>Signature (подпись): Подпись состоит из заголовка, полезной нагрузки и секретного ключа, который используется для создания подписи. Подпись используется для проверки целостности токена и подтверждения его аутентичности.</li>
</ul>

<p>JWT используется для аутентификации и передачи информации между двумя сторонами в формате, который может быть легко проверен и декодирован.</p>

<p>Давайте посмотрим на реальный кратковременный токен, созданый командой <code class="language-plaintext highlighter-rouge">kubectl -n kubernetes-dashboard create token kubernetes-dashboard --duration 7000h</code>
Я сразу раскодировал его, для удобства просмотра:</p>
<h6 id="header">HEADER</h6>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"alg"</span><span class="p">:</span><span class="w"> </span><span class="s2">"RS256"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"rDSeVpZWDMwMTh9WhH2pnRnENIEIwT2nDMggLMhg9hE"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Эта часть содержит метаданные о том, как токен был подписан. В данном случае, используется алгоритм подписи RS256 (RSA с SHA-256) и ключ идентификатора (kid), который может использоваться для поиска соответствующего открытого ключа для проверки подписи.</p>
<h6 id="payload">PAYLOAD</h6>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"aud"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"https://kubernetes.default.svc.cluster.local"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"exp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1740052294</span><span class="p">,</span><span class="w">
  </span><span class="nl">"iat"</span><span class="p">:</span><span class="w"> </span><span class="mi">1714852294</span><span class="p">,</span><span class="w">
  </span><span class="nl">"iss"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://kubernetes.default.svc.cluster.local"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kubernetes.io"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"namespace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"kubernetes-dashboard"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"serviceaccount"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"kubernetes-dashboard"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"uid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"c9e6e6b2-701e-4339-b72a-03f3e8e3ed7b"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"nbf"</span><span class="p">:</span><span class="w"> </span><span class="mi">1714852294</span><span class="p">,</span><span class="w">
  </span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"system:serviceaccount:kubernetes-dashboard:kubernetes-dashboard"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Эта часть содержит утверждения (claims) о пользователе или субъекте, а также дополнительные метаданные. В данном примере, iss (Issuer) указывает на URL сервера, который создал токен, sub (Subject) указывает на идентификатор субъекта, exp (Expiration Time) указывает на время истечения токена, а iat (Issued At) указывает на время создания токена.
—
Теперь создадим долгосрочный токен с помощью секрета.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% kubectl create  serviceaccount build-robot
serviceaccount/build-robot created
% kubectl apply <span class="nt">-f</span> - <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh">
apiVersion: v1
kind: Secret
metadata:
  name: build-robot-secret
  annotations:
    kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token
</span><span class="no">EOF
</span>secret/build-robot-secret created
% kubectl describe secrets/build-robot-secret
Name:         build-robot-secret
Namespace:    default
Labels:       &lt;none&gt;
Annotations:  kubernetes.io/service-account.name: build-robot
              kubernetes.io/service-account.uid: 606587a2-89e8-4750-a458-21ec7d4e9e3c

Type:  kubernetes.io/service-account-token

Data
<span class="o">====</span>
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6InJEU2VWcFpXRE13TVRoOVdoSDJwblJuRU5JRUl3VDJuRE1nZ0xNaGc5aEUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImJ1aWxkLXJvYm90LXNlY3JldCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJidWlsZC1yb2JvdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjYwNjU4N2EyLTg5ZTgtNDc1MC1hNDU4LTIxZWM3ZDRlOWUzYyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmJ1aWxkLXJvYm90In0.yzxwwKKsDom8ERVJD4mwVJZOA6mz79xu7yThR0t-wR6olOEwUH0IFrpC8rQcodHFqhdJiel8T7t_JGRB1ly7brOhama6kitfmSZlJLiJjYWeSxGAEtQvO1AGOMfUq3kBEZn3VABJo9LibFMlY5RZdexNtNMHQCCikx2eE4yLVTsb97vOh9XfZnAAZKkyH_6dSgYOjgpNCLft06LEr73ZadwtSYjuy_ImnnrANF9AjBrRE0vXEDff0xY02JCBfo_NT1sD2LLFQYW8NkEAsY0frV_g_J8MtOwAUOPY7b4sRplUfsUnQw0l3g5ml3R3iJbT_tVqqqbJM0Z9i5KeCs-9-Q
ca.crt:     1111 bytes
namespace:  7 bytes
</code></pre></div></div>
<p>Теперь берем токен, и либо по отдельности раскодируем Header и Payload или идем на <a href="https://jwt.io">jwt.io</a> и раскодируем целиком (<em>ни в коем случае не раскодируйте там ключи от Production!</em>)</p>
<h6 id="header-1">HEADER</h6>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"alg"</span><span class="p">:</span><span class="w"> </span><span class="s2">"RS256"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"rDSeVpZWDMwMTh9WhH2pnRnENIEIwT2nDMggLMhg9hE"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h6 id="payload-1">PAYLOAD</h6>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"iss"</span><span class="p">:</span><span class="w"> </span><span class="s2">"kubernetes/serviceaccount"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kubernetes.io/serviceaccount/namespace"</span><span class="p">:</span><span class="w"> </span><span class="s2">"default"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kubernetes.io/serviceaccount/secret.name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build-robot-secret"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kubernetes.io/serviceaccount/service-account.name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"build-robot"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kubernetes.io/serviceaccount/service-account.uid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"606587a2-89e8-4750-a458-21ec7d4e9e3c"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"system:serviceaccount:default:build-robot"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Как видим, токен в отличие от своего предшественника не содержит информацию о сроке действия.</p>
<h3 id="openid-connect">OpenID Connect</h3>
<p>Уделим немного внимания также сторонним сервисам аутентификации, распространенных при использовании kubernetes. Много где используются службы каталогов, такие как ldap. И для того чтобы Kubernetes научить аутентифицировать пользователей в этих внешних службах, нужен посредник. Сам Kubernetes <a href="https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens">умеет работать с OpenID Connect протоколом</a>, который является расширением OAuth2, и его необходимо настроить для связи с нашим посредником, который и будет заниматься аутентификацией пользователя во внешней службе и выдачей токенов. Чаще всего такими посредниками для ldap являются <a href="https://dexidp.io/">dex</a> или <a href="https://www.keycloak.org/">keycloak</a>. Вообще, внешние сервисы аутентификации заслуживают отдельной статьи, и я не буду углубляться в подробности. Приведу общую схему работы с посредником OIDC.
<img src="/assets/images/oidc.jpg" alt="oidc" />
Вот краткое описание работы OIDC в Kubernetes:</p>

<ul>
  <li>Настройка OIDC провайдера:
Администратор настраивает OIDC провайдера (например, Google, Azure AD) для аутентификации пользователей в Kubernetes.</li>
  <li>Интеграция с API сервером Kubernetes:
API сервер Kubernetes (kube-apiserver) настраивается для использования OIDC провайдера для аутентификации запросов от пользователей.</li>
  <li>Получение токена аутентификации:
Пользователь аутентифицируется через OIDC провайдера и получает токен аутентификации (ID токен) в формате JWT.</li>
  <li>Проверка подлинности токена:
API сервер Kubernetes проверяет подлинность токена аутентификации с помощью открытого ключа OIDC провайдера.</li>
  <li>Извлечение утверждений (claims):
После успешной проверки подлинности токена, API сервер Kubernetes извлекает утверждения (claims) из токена, такие как идентификатор пользователя, роли и другие атрибуты.</li>
  <li>Авторизация доступа:
На основе извлеченных утверждений (claims), Kubernetes применяет механизмы авторизации (например, RBAC) для определения прав доступа пользователя к ресурсам в кластере.</li>
</ul>

<p>Давайте для примера рассмотрим скрипт, который после авторизации в Dex выдает сервис <a href="https://github.com/vmware-archive/gangway">Gangway</a>. Этот скрипт настраивает кластер Kubernetes для аутентификации через OIDC провайдера, используя предоставленные учетные данные (client ID, client secret, refresh token и ID token). Кроме того, создается контекст, который позволяет использовать учетные данные для работы с кластером.</p>

<ol>
  <li>Создание файла ca.pem с корневым сертификатом для проверки сертификатов сервера Kubernetes:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"-----BEGIN CERTIFICATE-----
MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
blablahblahndsjakndjasndjak
0a4=
-----END CERTIFICATE-----
"</span> <span class="se">\ </span><span class="o">&gt;</span> ca.pem
</code></pre></div>    </div>
  </li>
  <li>Настройка кластера Kubernetes Production:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl config set-cluster Production <span class="nt">--server</span><span class="o">=</span>https://kuber-prod.company.local:8383 <span class="nt">--certificate-authority</span><span class="o">=</span>ca.pem <span class="nt">--embed-certs</span>
</code></pre></div>    </div>
  </li>
  <li>Настройка учетных данных (credentials) для аутентификации OIDC:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl config set-credentials admin@company.local  <span class="se">\</span>
 <span class="nt">--auth-provider</span><span class="o">=</span>oidc  <span class="se">\</span>
 <span class="nt">--auth-provider-arg</span><span class="o">=</span><span class="s1">'idp-issuer-url=https://kuber-prod-auth.company.local'</span>  <span class="se">\</span>
 <span class="nt">--auth-provider-arg</span><span class="o">=</span><span class="s1">'client-id=oidc-auth-client'</span>  <span class="se">\</span>
 <span class="nt">--auth-provider-arg</span><span class="o">=</span><span class="s1">'client-secret=nfdjsanfjdsanfjasknfjdsaknfjasndjfk'</span> <span class="se">\</span>
 <span class="nt">--auth-provider-arg</span><span class="o">=</span><span class="s1">'refresh-token=ndnsjaNJAKncsjakcndjsncjsdkncjksdncjdsncjksncjdsk'</span> <span class="se">\</span>
 <span class="nt">--auth-provider-arg</span><span class="o">=</span><span class="s1">'id-token=eyJhbGciOiJIUzUxMiIsImtpZCI6IjU2ZmM3Zjg0YmQ2OTdmMGRkNTRiNTM2Njc5NWFiODBiMzU0NTZlMmEifQ.eyJpc3MiOiJodHRwczovL2t1YmVyLXByb2QtYXV0aC5jb21wYW55LmxvY2FsIiwic3ViIjoiU2pkc25hamRrbnNhamRrbmphc25kamthbmpza2EiLCJhdWQiOiJvaWRjLWF1dGgtY2xpZW50IiwiZXhwIjoxNzE1MDE3NTY5LCJpYXQiOjE3MTQ5MzExNjksImF0X2hhc2giOiJ6Y3lFUkNFWjhtM0ZJdVdhb0d6dGRRIiwiY19oYXNoIjoidEZ6c3lFUk5nOVNZU3ZMOTZyeEN6ZyIsImVtYWlsIjoiYWRtaW4iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZ3JvdXBzIjpbIlVzZXJzIiwiQWRtaW5zIl0sIm5hbWUiOiJTdXBlciBBZG1pbiJ9.ttGyo4aAEi5V1AG7E8v'</span>
</code></pre></div>    </div>
  </li>
  <li>Настройка контекста (context) для кластера:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl config set-context Production--cluster<span class="o">=</span>Production <span class="nt">--user</span><span class="o">=</span>admin@company.local
</code></pre></div>    </div>
  </li>
  <li>Использование настроенного контекста для работы с кластером:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl config use-context Production
</code></pre></div>    </div>
  </li>
  <li>Удаление временного файла ca.pem:
    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm </span>ca.pem
</code></pre></div>    </div>
  </li>
</ol>

<p>Тут как раз в шаге 3 фигурирует id-token, который выдает Dex, и который является JWT. Давайте расшифруем и посмотрим внутрь:</p>
<h6 id="header-2">HEADER</h6>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"alg"</span><span class="p">:</span><span class="w"> </span><span class="s2">"HS512"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"kid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"56fc7f84bd697f0dd54b5366795ab80b35456e2a"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>в заголовке ничего необычного, алгоритм и ключ иденификатора</p>
<h6 id="payload-2">PAYLOAD</h6>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"iss"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://kuber-prod-auth.company.local"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"sub"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sjdsnajdknsajdknjasndjkanjska"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"aud"</span><span class="p">:</span><span class="w"> </span><span class="s2">"oidc-auth-client"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"exp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1715017569</span><span class="p">,</span><span class="w">
  </span><span class="nl">"iat"</span><span class="p">:</span><span class="w"> </span><span class="mi">1714931169</span><span class="p">,</span><span class="w">
  </span><span class="nl">"at_hash"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zcyERCEZ8m3FIuWaoGztdQ"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"c_hash"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tFzsyERNg9SYSvL96rxCzg"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"email_verified"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"groups"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"Users"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"Admins"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Super Admin"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Разберем подробно:</p>
<ul>
  <li>iss (Issuer): Идентификатор провайдера, который выдал токен. В данном случае, https://kuber-prod-auth.company.local.</li>
  <li>sub (Subject): Уникальный идентификатор пользователя. В данном случае, Sjdsnajdknsajdknjasndjkanjska.</li>
  <li>aud (Audience): Аудитория, для которой токен предназначен. В данном случае, oidc-auth-client.</li>
  <li>exp (Expiration Time): Время, после которого токен становится недействительным (в формате Unix time).</li>
  <li>iat (Issued At): Время, когда токен был выдан (в формате Unix time).</li>
  <li>at_hash (Access Token Hash): Хеш-значение для access token.</li>
  <li>c_hash (Authorization Code Hash): Хеш-значение для authorization code.</li>
  <li>email: Email пользователя, в данном случае admin.</li>
  <li>email_verified: Подтверждение верности email.</li>
  <li>groups: Группы, к которым принадлежит пользователь.</li>
  <li>name: Имя пользователя, в данном случае Super Admin.</li>
</ul>

<p>Данный токен будет добавляться к запросу каждый раз при обращении к серверу kubernetes-api.</p>

<p>Теперь вернемся назад, и обратим внимание, что Dex также выдал и <a href="https://dexidp.io/docs/configuration/tokens/">refresh-token</a>. Это специальный токен, который позволяет продлить основной токен, если он уже протух. Kubectl занимается этим автоматически. При продлении основного токена в ответ будет выдан новый основой токен, а также будет выдан новый refresh токен. Это называется ротацией токенов.</p>

<p>JWT прикольная штука, которая используется не только в Kubernetes, а во многих веб-приложениях и api.</p>]]></content><author><name>Dmitrii Lavrukhin</name></author><category term="jwt" /><category term="kubernetes" /><summary type="html"><![CDATA[Многие Devops инженеры, впервые устанавливая kubernetes (обычно minikube или чтонибудь в облаках), и заходя на dashboard впадают в ступор, видя приглашение для ввода: Так, ну с конфигом понятно, думает инженер - его мне сгенерил сам кубер при установке, а что за токен ? Где нормальные привычные логин и пароль ?]]></summary></entry><entry><title type="html">Welcome page</title><link href="https://lavr.one/2024/04/17/Hello-World.html" rel="alternate" type="text/html" title="Welcome page" /><published>2024-04-17T00:00:00+00:00</published><updated>2024-04-17T00:00:00+00:00</updated><id>https://lavr.one/2024/04/17/Hello-World</id><content type="html" xml:base="https://lavr.one/2024/04/17/Hello-World.html"><![CDATA[<p>Добро пожаловать на мой персональный сайт! Здесь вы окунетесь в увлекательный мир автоматизации и разработки, который я исследую и преображаю вместе с вами.</p>

<p>Меня зовут Дмитрий, и я DevOps Engineer с большим опытом работы в области создания инфраструктуры, развертывания облачных сервисов и оптимизации процессов разработки.</p>

<p>На этом сайте я делюсь своими знаниями, опытом и идеями о DevOps. Вы найдете здесь статьи, обзоры технологий, лучшие практики и многое другое, что поможет вам погрузиться в мир DevOps и сделать вашу работу более эффективной.</p>

<p>Не стесняйтесь изучать мои публикации и делиться своими мыслями. Вместе мы можем сделать мир IT лучше и более инновационным!</p>

<p>С уважением, Дмитрий Лаврухин.</p>]]></content><author><name>Dmitrii Lavrukhin</name></author><category term="blog" /><summary type="html"><![CDATA[Добро пожаловать на мой персональный сайт! Здесь вы окунетесь в увлекательный мир автоматизации и разработки, который я исследую и преображаю вместе с вами.]]></summary></entry></feed>