مهاجرت Pack بین نسخههای چارت هلم
مهاجرت Pack یا Pack Migration به نویسندهٔ چارت اجازه میدهد مانیفست پکهای موجود را هنگام تغییر ساختار values، vars یا نسخهٔ چارت تبدیل کند. با این قابلیت، انتخابهای کاربر در ارتقا حفظ میشوند و لازم نیست مانیفست هر Pack دستی بازنویسی شود.
این صفحه مرجع ساخت و آزمون pack-migrations.yaml است. برای آشنایی با سایر فایلها و قابلیتهای اختصاصی چارت، قابلیتهای Pack در چارت هلم را بخوانید.
تعریف مهاجرت با pack-migrations.yaml
فایل pack-migrations.yaml تبدیل مانیفست Pack را هنگام تغییر ساختار values، vars یا نسخهٔ چارت تعریف میکند. این فایل باید با همین نام در ریشهٔ چارت و کنار Chart.yaml قرار بگیرد.
Pack Operator فایل migration را از چارت مقصد میخواند. بنابراین هر نسخهٔ منتشرشده باید زنجیرهٔ کامل migration از همهٔ نسخههای قدیمی پشتیبانیشده تا نسخهٔ مقصد را در خود داشته باشد. کاربری که چند نسخه را رد کرده است نباید مجبور شود چارتهای میانی را جداگانه نصب کند.
migrations:
- repo: kubit-paas
chart: redis
from_version: '>=0.1.0, <0.2.0'
to_version: '~=0.2.0'
context:
metrics_default: true
steps:
- set:
values: !var pack.spec.values
values.metrics.enabled: !default-var metrics_default
- if: values.password
set:
values.auth.password: !pop values.password
- reorder:
values:
- auth
- metrics
| فیلد | الزامی | کاربرد |
|---|---|---|
repo | بله | نام منبع repository در spec.chart.repository.name |
chart | بله | نام چارت در spec.chart.name |
from_version | بله | نسخه یا محدودیت نسخهٔ مبدأ |
to_version | بله | محدودیت نسخهای که روی مانیفست نتیجه نوشته میشود |
steps | بله | فهرست مرتبشدهٔ تبدیلها؛ در صورت نبود تبدیل، فهرست خالی |
context | خیر | متغیرهای موقت اولیه برای stepها و عبارتهای Jinja |
مقدار repo نام منبع repository است، نه URL آن. تطبیق repo و chart دقیق است و wildcard ندارد. اگر مانیفست Pack فیلد spec.chart.version نداشته باشد، هیچ migration با آن منطبق نمیشود.
انتخاب و زنجیرهکردن نسخهها
from_version از مقایسههای semantic version پشتیبانی میکند:
| نمونه | نسخههای منطبق |
|---|---|
'<2.0.0' | نسخههای کوچکتر از 2.0.0 |
'>=2.0.0, <2.3.0' | نسخههای 2.0.0 تا قبل از 2.3.0 |
'~=2.3.0' | نسخههای 2.3.x؛ از 2.3.0 تا قبل از 2.4.0 |
'<v1.19.4' | نسخههای کوچکتر از 1.19.4 با پیشوند v |
عبارت نسخه را داخل کوتیشن بنویسید، بهویژه وقتی با < یا > شروع میشود. چند شرط جداشده با ویرگول همزمان اعمال میشوند.
پیش از اجرای هر migration، اپراتور spec.chart.version را برابر to_version میکند. سپس migrationهای بعدی با نسخهٔ تازه بررسی میشوند؛ در نتیجه چند entry میتوانند یک زنجیرهٔ ارتقا بسازند. برای نمونه:
migrations:
- repo: kubit-paas
chart: redis
from_version: '<0.3.0'
to_version: '~=0.3.0'
steps: []
- repo: kubit-paas
chart: redis
from_version: '>=0.3.0, <0.5.0'
to_version: '~=0.5.0'
steps: []
در این نمونه، Pack نسخهٔ 0.2.0 ابتدا به شاخهٔ 0.3 و سپس به شاخهٔ 0.5 میرسد. to_version یک entry نباید دوباره با from_version همان entry منطبق شود؛ وگرنه ممکن است در اجرای بعدی دوباره اعمال شود.
ترتیب ظاهری entryها در فایل معیار قطعی اجرا نیست. اپراتور آنها را بر اساس نام repository، نام چارت و شکل پاکسازیشدهٔ from_version مرتب میکند. در زنجیرههایی مانند 1.9 و 1.10، ترتیب مؤثر را با اجرای واقعی migration بررسی کنید.
مسیرها و context مهاجرت
مسیرها با نقطه از هم جدا میشوند؛ برای نمونه pack.spec.values.service.port. context هر entry مستقل است و این مقدارها را دارد:
| نام | مقدار |
|---|---|
pack | مانیفست کامل و قابل تغییر Pack |
true | مقدار boolean برابر true |
false | مقدار boolean برابر false |
null | مقدار تهی |
کلیدهای context | متغیرهای موقت تعریفشده در همان entry |
برای سادهکردن مسیرهای طولانی، در ابتدای migration alias بسازید:
- set:
chart: !var pack.spec.chart
values: !var pack.spec.values
chart.autoUpgrade: !default-var true
Mapping یا list خواندهشده با !var به همان دادهٔ اصلی اشاره میکند؛ بنابراین تغییر values.service.port در نمونهٔ بالا داخل pack.spec.values باقی میماند. متغیر موقت سطح اول مانند old_port در خروجی Pack نوشته نمیشود و میان entryها نیز باقی نمیماند.
خواندن مسیر ناموجود مقدار تهی برمیگرداند. نوشتن یک مسیر، mappingهای میانی ناموجود را میسازد؛ اما نوشتن مقدار تهی نادیده گرفته میشود.
اگر کلید mapping نقطهٔ واقعی دارد، نقطه را با \. escape و کل مسیر را با کوتیشن تکی بنویسید:
- set:
copied: !var 'values.annotations.example\.com/key'
'values.annotations.example\.com/key': enabled
بخش عددی مسیر، index یک list نیست. برای خواندن یا تغییر عضو list از item_in_list استفاده کنید.
عملیات migration
هر عضو steps یک step است. در یک step، عملیات همیشه با ترتیب زیر اجرا میشوند؛ حتی اگر ترتیب کلیدها در YAML متفاوت باشد:
- شرط
ifیا شاخهٔelse - یافتن عضو با
item_in_list - مقداردهی با
set - اجرای
stepsتو در تو - مرتبسازی با
reorder
برای خوانایی، هر step را کوچک نگه دارید. ترکیب چند عملیات زمانی مفید است که به همین ترتیب ثابت نیاز داشته باشند؛ برای نمونه ابتدا عضو list پیدا شود و سپس set آن را تغییر دهد.
tagهای set
| tag | رفتار |
|---|---|
!default | فقط در صورت نبودن کلید مقصد، مقدار ثابت را مینویسد |
!default-var | فقط در صورت نبودن مقصد، مقدار یک مسیر از context را مینویسد |
!var | مقدار یک مسیر از context را بدون حذف منبع میخواند |
!pop | مقدار مسیر مبدأ را حذف و به مقصد منتقل میکند |
!pop-empty | فقط مبدأ falsey را حذف میکند و برای پاکسازی mappingهای خالی مناسب است |
!merge | mapping منبع را بهصورت بازگشتی با mapping مقصد ادغام میکند |
!jinja | مقدار را با context migration رندر میکند و نوع بومی را نگه میدارد |
مقدار بدون tag مقصد را بازنویسی میکند. برای حفظ انتخاب کاربر، کلید قدیمی را با !pop بخوانید و مقصد را با !default-var مقدار دهید. از !default برای پیشفرض تازهای استفاده کنید که نباید مقدار موجود را بازنویسی کند؛ حتی اگر مقدار موجود false، صفر یا تهی باشد.
!pop-empty ابزار انتقال داده نیست. این tag فقط مقدار falsey مانند mapping یا list خالی را حذف میکند و وقتی چیزی حذف نشود، مقدار false در مقصد میگذارد. آن را با نام موقت استفاده و پاکسازی را از عمیقترین مسیر به والدها انجام دهید:
- set:
_: !pop values.deprecatedOption
_1: !pop-empty values.legacy.child
_2: !pop-empty values.legacy
برای !merge، منبع و مقصد باید mapping باشند. اگر ممکن است مقصد وجود نداشته باشد، ابتدا آن را با !default بسازید.
!jinja میتواند boolean، عدد، list یا mapping تولید کند. برای مسیر اختیاری از filter default یا شکل کوتاه آن، d، استفاده کنید؛ دسترسی مستقیم به متغیر تعریفنشده migration را با خطا متوقف میکند.
شرطها و stepهای تو در تو
شرط ساده، مقدار یک مسیر را به boolean تبدیل میکند. !not نتیجهٔ مسیر را معکوس میکند و !jinja برای مقایسه یا شرط ترکیبی است:
- if: values.ingress.enabled
set:
values.ingress.className: !default nginx
- if: !not values.persistence.enabled
set:
_: !pop values.persistence
- if: !jinja '{{ values.mode | d("standard") == "legacy" }}'
steps:
- set:
values.mode: standard
else:
- set:
values.mode: !default standard
else باید کنار if باشد و فهرستی از stepها بگیرد. وقتی شرط برقرار نباشد، فقط stepهای else اجرا میشوند و سایر عملیات همان step بیرونی نادیده گرفته میشوند.
تغییر اعضای list با item_in_list
item_in_list یک mapping را در list با key و value یا با index پیدا میکند. عضو پیداشده یا ساختهشده در متغیر موقت var قرار میگیرد:
- set:
values.service.ports: !default []
- item_in_list:
list: values.service.ports
key: name
value: http
create: true
var: http_port
set:
http_port.port: !default 6379
http_port.protocol: !default TCP
پیش از item_in_list خود list را با !default [] بسازید. در غیر این صورت، عضو تازه ممکن است به list موقتی افزوده شود که به Pack متصل نیست.
key و index همزمان مجاز نیستند. index خارج از محدوده خطا میدهد. بدون create: true، اگر عضوی پیدا نشود var مقدار نمیگیرد؛ بنابراین stepهای بعدی را با if محافظت کنید. مقدار value فقط میتواند ثابت، !var یا !pop باشد.
محل مجاز tagها
هر tag در همهٔ بخشها معتبر نیست:
| مقدار یا tag | مقدار set | شرط if | item_in_list.value |
|---|---|---|---|
| مقدار سادهٔ YAML | بله | فقط مسیر | بله |
!var | بله | خیر | بله |
!pop | بله | خیر | بله |
!pop-empty | بله | خیر | خیر |
!default | بله | خیر | خیر |
!default-var | بله | خیر | خیر |
!merge | بله | خیر | خیر |
!jinja | بله | بله | خیر |
!not | خیر | بله | خیر |
مرتبکردن خروجی با reorder
reorder کلیدهای نامبرده را با ترتیب تعیینشده به ابتدای یک mapping موجود میآورد. سایر کلیدها بعد از آنها باقی میمانند. این عملیات فقط خوانایی YAML خروجی را تغییر میدهد و روی رفتار Helm اثر ندارد.
- reorder:
pack.spec.values:
- global
- service
- ingress
pack.spec.values.service:
- enabled
- type
- ports
خطاهای رایج در pack-migrations.yaml
| خطا | نتیجه یا روش درست |
|---|---|
قراردادن URL مخزن در repo | migration منطبق نمیشود؛ نام spec.chart.repository.name را بنویسید |
| نگهداشتن فقط آخرین migration افزایشی | Packهایی که چند نسخه را رد کردهاند مسیر کامل ندارند |
تنظیم دوبارهٔ spec.chart.version در step | زائد است؛ اپراتور پیش از stepها to_version را اعمال میکند |
استفاده از !pop برای پیشفرض تازه | نبود مبدأ چیزی تولید نمیکند؛ از !default یا !default-var استفاده کنید |
استفاده از !pop-empty برای انتقال | مقدار truthy باقی میماند؛ این tag را فقط برای پاکسازی استفاده کنید |
استفاده از Jinja بدون !jinja | عبارت Jinja بهصورت متن ساده ذخیره میشود |
| فرضکردن ترتیب فایل بهعنوان ترتیب اجرا | rangeها و زنجیره را با موتور واقعی migration آزمایش کنید |
آزمون migration پیش از انتشار
kubit pack migrate -f redis.pack.yaml \
--local-chartpath ./redis
خروجی، مانیفست مهاجرتیافته را در stdout نشان میدهد. آن را با ورودی مقایسه و مطمئن شوید فقط نسخه و مسیرهای مورد انتظار تغییر کردهاند. خروجی نرمالسازی میشود و ممکن است spec.managed: true را حذف کند؛ نبودن spec.managed معادل true است.
برای هر مسیر migration دستکم این حالتها را بررسی کنید:
- قدیمیترین نسخهٔ پشتیبانیشده و مرز ابتدا و انتهای هر
from_version - یک Pack پیش از هر migration میانی و یک Pack که کل زنجیره را طی میکند
- مقدارهای سفارشی کاربر،
false، صفر، مقدار خالی و فیلد اختیاری ناموجود - عضو موجود و ناموجود list و index نامعتبر
- اجرای دوبارهٔ migration؛ اجرای دوم نباید تغییر اختصاصی تازهای ایجاد کند
- رندر یا diff چارت مقصد با مانیفست مهاجرتیافته
علاوه بر diff متنی YAML، تغییر معنایی values را نیز بازبینی کنید. مرتبسازی فیلدها و پاکسازی metadata ممکن است در خروجی دیده شود، حتی اگر مستقیماً در stepها تعریف نشده باشد.
پس از بازبینی میتوانید نتیجه را روی فایل بنویسید:
kubit pack migrate -f redis.pack.yaml \
--local-chartpath ./redis \
--inline
--inline فایل ورودی را تغییر میدهد. نسخهٔ قبلی را در گیت نگه دارید و پیش از اجرا مطمئن شوید مسیر فایل درست است. برای انتخاب نسخهٔ مقصد یا اجرای migration روی Pack موجود در کلاستر، مرجع دستور pack migrate را ببینید.
در ارتقای خودکار، Pack Operator ابتدا migration منطبق را بررسی میکند. اگر migration تغییر معتبر ایجاد کند، مانیفست Pack با نتیجه جایگزین میشود. اگر migration منطبق نباشد یا اجرای آن خطا بدهد، مسیر عادی ارتقای خودکار ادامه پیدا میکند؛ بنابراین خطای migration تضمین نمیکند که ارتقا متوقف شود.