{"id":"l-dk-2-3","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"سمِّ images ‏notes-api وأعطها أرقام إصدارات وارفعها إلى registry بحيث يمكن تتبّع كل deploy، وثبّت الـ images بالـ digest، وابنِ لمعماريّتَي amd64 وarm64 حتى يستطيع الـ cluster تشغيل ما بناه جهازك.","html":"<p>في الحادية عشرة ليلًا من ليلة إطلاق، أعطى اثنان من الـ Pods التابعة للخدمة نفسها إجابتين مختلفتين عن الطلب نفسه. كلاهما كان يشغّل <code dir=\"ltr\">api:latest</code>. أحدهم أعاد دفع <code dir=\"ltr\">latest</code> بعد الظهر، فسحب أحد الـ nodes الـ image الجديدة بينما بقي الآخر يستخدم نسخته المخزّنة. قضينا ساعة نثبت أن الكود سليم قبل أن ينظر أحد إلى الـ image. الأسماء جزء من عملية النشر، فاجعلها دقيقة.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">تشريح مرجع الـ image</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Text</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code>ghcr.io/skylane/notes-api:1.4.0@sha256:3f1c9e…\n└──┬──┘ └──┬──┘ └───┬───┘ └─┬─┘ └─────┬─────┘\nregistry  namespace  repo    tag      digest</code></pre></div>\n<ul>\n<li>الـ <strong>registry</strong> (مستودع الـ images) هو الخادم الذي يخزّن الـ images: ‏Docker Hub (الافتراضي حين لا تذكره)، وGitHub Container Registry ‏(<code dir=\"ltr\">ghcr.io</code>)، وAmazon ECR، وGoogle Artifact Registry، وغيرها.</li>\n<li>الـ <strong>tag</strong> تسمية سهلة القراءة على البشر. وهو مؤشّر، والمؤشّرات يمكن أن تتحرّك.</li>\n<li>الـ <strong>digest</strong> (البصمة) هو hash من نوع SHA-256 للـ manifest الخاص بالـ image (أو لفهرس الـ image في حالة image متعدّدة المنصّات)، والـ manifest بدوره يسرد hash كل layer. غيّر بايتًا واحدًا في أي مكان فيتغيّر الـ digest. لا يمكن نقله.</li>\n</ul>\n<p>الـ image ‏<code dir=\"ltr\">notes-api:latest</code> ليست مميّزة. ‏<code dir=\"ltr\">latest</code> مجرد الـ tag الذي يستخدمه Docker حين لا تحدّد واحدًا. لا يعني «الأحدث»، ولا شيء يحدّثه ما لم يدفعه أحد.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">مخطّط tags يصمد</h2>\n<p>لكل بناء قد يُنشر، ادفع اثنين من الـ tags للـ image نفسها:</p>\n<ul>\n<li><strong>إصدار</strong> يتحدّث عنه الناس: <code dir=\"ltr\">1.4.0</code>.</li>\n<li>الـ <strong>commit</strong> الذي جاء منه: <code dir=\"ltr\">sha-9f2c1ab</code>.</li>\n</ul>\n<p>حين يُبلغ أحدهم عن خلل في «1.4.0»، يأخذك الـ tag الخاص بالـ SHA مباشرة إلى الكود. والقاعدة التي تمنع حادث ليلة الإطلاق: <strong>لا تدفع أبدًا محتوى مختلفًا تحت tag منشور فعلًا.</strong> إن أصلحت شيئًا، فهذا <code dir=\"ltr\">1.4.1</code>. كثير من الـ registries تستطيع فرض ذلك؛ فلـ Amazon ECR مثلًا إعداد لثبات الـ tags يرفض أي دفع إلى tag موجود.</p>\n<p>ابنِ مرة واحدة، ثم رقِّ (promote) الـ image نفسها من بيئة إلى أخرى مرات عديدة. الـ image التي تجتاز staging يجب أن تكون بالضبط الـ image التي تصل إلى الإنتاج، ويعرّفها الـ digest نفسه. لا تُعد البناء من الـ commit نفسه للإنتاج «احتياطًا»: إعادة البناء قد تسحب base image أحدث أو اعتمادية غير مباشرة مختلفة، وعندها يشغّل الإنتاج شيئًا لم يختبره أحد. الترقية تغيير في الـ digest الذي يشير إليه الـ manifest الخاص بكل بيئة، لا بناء جديد.</p>\n<p>حافظ على ترتيب الـ registry أيضًا. قواعد الاحتفاظ التي تحذف الـ images بلا tags وtags الـ SHA القديمة بعد بضعة أشهر تخفّض تكاليف التخزين، لكن استثنِ منها كل ما زال manifest يشير إليه، وإلا فسيفشل التراجع التالي بسبب image مفقودة.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">رفع الـ image إلى registry</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">echo</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">$GHCR_TOKEN</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> login</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ghcr.io</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -u</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> amara-skylane</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --password-stdin</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">GIT_SHA</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">$(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">git</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rev-parse</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --short</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> HEAD</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> buildx</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> build</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --platform</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> linux/amd64,linux/arm64</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  -t</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ghcr.io/skylane/notes-api:1.4.0</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  -t</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ghcr.io/skylane/notes-api:sha-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">$GIT_SHA </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">\\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --push</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> .</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">docker</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> buildx</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> imagetools</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> inspect</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> ghcr.io/skylane/notes-api:1.4.0</span></span></code></pre></div>\n<p>الخيار <code dir=\"ltr\">--password-stdin</code> يُبقي الـ token خارج سجلّ الأوامر في الـ shell. وفي الـ CI، استخدم الـ token قصير العمر الذي توفّره المنصّة بدلًا من token شخصي. والأمر الأخير يطبع الـ digest الخاص بالـ image والمنصّات التي تحتويها.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ images متعدّدة المنصّات</h2>\n<p>قد يكون حاسوبك arm64 ‏(Apple Silicon) بينما الـ nodes في الـ cluster من نوع amd64، أو العكس. الـ image المبنية لمعمارية معالج معيّنة تفشل على الأخرى برسالة مقتضبة: <code dir=\"ltr\">exec format error</code>. البناء متعدّد المنصّات يحلّ ذلك: ‏<code dir=\"ltr\">--platform linux/amd64,linux/arm64</code> يبني النسختين ويدفع <strong>image index</strong> (فهرس الـ image)، وهو قائمة صغيرة تربط كل منصّة بالـ manifest الخاص بها. حين يسحب node الـ image ‏<code dir=\"ltr\">notes-api:1.4.0</code>، يختار الـ runtime الـ manifest المناسب لمعماريّته.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 250\" role=\"img\" aria-labelledby=\"l-dk-2-3-t1\"><defs><marker id=\"arrow-l-dk-2-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-dk-2-3-t1\">الـ tag ‏1.4.0 يشير إلى digest لفهرس image. الفهرس يسرد manifest لـ amd64 وmanifest لـ arm64، وكل منهما يسرد الـ layers الخاصة به.</title>\n  <rect class=\"d-box-warn\" x=\"20\" y=\"100\" width=\"130\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"85\" y=\"130\" text-anchor=\"middle\">tag 1.4.0</text>\n  <path class=\"d-arrow\" d=\"M150 125 L208 125\" marker-end=\"url(#arrow-l-dk-2-3-1)\"/>\n  <rect class=\"d-box-primary\" x=\"210\" y=\"90\" width=\"170\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-label-strong\" x=\"295\" y=\"118\" text-anchor=\"middle\">image index</text>\n  <text class=\"d-code\" x=\"295\" y=\"142\" text-anchor=\"middle\">sha256:3f1c…</text>\n  <path class=\"d-arrow\" d=\"M380 110 L458 62\" marker-end=\"url(#arrow-l-dk-2-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M380 140 L458 188\" marker-end=\"url(#arrow-l-dk-2-3-1)\"/>\n  <rect class=\"d-box-accent\" x=\"460\" y=\"30\" width=\"220\" height=\"64\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"570\" y=\"56\" text-anchor=\"middle\">manifest لـ linux/amd64</text>\n  <text class=\"d-label-muted\" x=\"570\" y=\"80\" text-anchor=\"middle\">layers لـ nodes ‏x86</text>\n  <rect class=\"d-box-accent\" x=\"460\" y=\"156\" width=\"220\" height=\"64\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"570\" y=\"182\" text-anchor=\"middle\">manifest لـ linux/arm64</text>\n  <text class=\"d-label-muted\" x=\"570\" y=\"206\" text-anchor=\"middle\">layers لـ nodes ‏ARM</text>\n  <text class=\"d-label-muted\" x=\"85\" y=\"175\" text-anchor=\"middle\">قابل للنقل</text>\n  <text class=\"d-label-muted\" x=\"295\" y=\"185\" text-anchor=\"middle\">ثابت</text>\n</svg><figcaption>tag واحد يشير إلى فهرس، وكل node يسحب الـ manifest الخاص بمعالجه</figcaption></figure>\n<p>البناء لمعمارية غير معمارية جهازك يعمل تحت المحاكاة، وقد يكون بطيئًا مع خطوات <code dir=\"ltr\">npm ci</code> الثقيلة. الـ runners في الـ CI التي تملك أجهزة arm64 وamd64 أصلية أسرع حين يكبر البناء. ولكي تحتفظ بالـ images متعدّدة المنصّات محليًا، يحتاج Docker إلى مخزن الـ images الخاص بـ containerd ‏(containerd image store)، وهو الإعداد الافتراضي في تثبيتات Docker Desktop الجديدة؛ أما في الإعدادات الأقدم فتأكّد من أنه مفعّل.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">التثبيت بالـ digest</h2>\n<p>الـ digest يضمن أن كل node يشغّل بايتات متطابقة، مهما حدث للـ tag لاحقًا. تستطيع النشر بالاثنين معًا، والـ digest هو الذي يفوز:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">YAML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">image</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ghcr.io/skylane/notes-api:1.4.0@sha256:3f1c9e…</span></span></code></pre></div>\n<p>يبقى الـ tag في المرجع من أجل البشر؛ والـ runtime يسحب بالـ digest. افعل الشيء نفسه مع الـ base images في الـ Dockerfile، حتى لا تلتقط إعادة البناء في الشهر القادم نسخة مختلفة من <code dir=\"ltr\">node:24-slim</code> بصمت:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"dockerfile\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Dockerfile</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> node:24-slim@sha256:&#x3C;digest-from-imagetools-inspect> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> build</span></span></code></pre></div>\n<p>التثبيت يقايض الترقيعات التلقائية بإمكانية التنبّؤ، لذا اقرنه بأداة آلية مثل Dependabot أو Renovate تفتح pull request حين يُنشر digest جديد للـ base. فتحصل على التحديثات مراجَعة ومختبَرة، بدلًا من المفاجآت.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>التثبيت ثم النسيان</span></p>\n<p>الـ base المثبّتة بالـ digest لا تحصل على إصلاحات أمنية من تلقاء نفسها. رأيت images مثبّتة تعمل سنة كاملة على base فيها CVE حرجة معروفة، لأن أحدًا لم يكن مسؤولًا عن التحديث. ثبّت <em>وأتمِت</em> الترقية، أو لا تثبّت.</p>\n</aside>\n<p>الـ image لديك الآن مسمّاة، وذات رقم إصدار، ومرفوعة إلى الـ registry. وقبل أن ينشرها أحد، يجب أن تعرف ما بداخلها، وهذا موضوع الدرس التالي.</p>\n","toc":[{"id":"sec-1","text":"تشريح مرجع الـ image","level":2},{"id":"sec-2","text":"مخطّط tags يصمد","level":2},{"id":"sec-3","text":"رفع الـ image إلى registry","level":2},{"id":"sec-4","text":"الـ images متعدّدة المنصّات","level":2},{"id":"sec-5","text":"التثبيت بالـ digest","level":2}],"takeaways":["مرجع الـ image هو <code dir=\"ltr\">registry/namespace/repository:tag@digest</code>؛ الـ tag لافتة قابلة للنقل، والـ digest بصمة المحتوى.","الـ tag ‏<code dir=\"ltr\">latest</code> مجرد اسم الـ tag الافتراضي، لا أحدث بناء؛ انشر إصدارات صريحة مثل <code dir=\"ltr\">1.4.0</code> أو SHA من Git.","لا تدفع أبدًا محتوى مختلفًا تحت tag منشور فعلًا؛ وفعّل ثبات الـ tags (tag immutability) حيث يدعمه الـ registry لديك.","التثبيت بالـ digest ‏(<code dir=\"ltr\">@sha256:…</code>) يضمن أن كل node يشغّل البايتات نفسها، لتطبيقك وللـ base images.","ابنِ images متعدّدة المنصّات بـ <code dir=\"ltr\">docker buildx build --platform linux/amd64,linux/arm64</code> حين يستخدم المطوّرون والخوادم معالجات مختلفة."],"further":[{"title":"docker image tag","url":"https://docs.docker.com/reference/cli/docker/image/tag/"},{"title":"Multi-platform builds","url":"https://docs.docker.com/build/building/multi-platform/"},{"title":"Images in Kubernetes","url":"https://kubernetes.io/docs/concepts/containers/images/"}],"quiz":[{"q":"<p>اثنان من الـ Pods في الـ Deployment نفسه، وكلاهما يستخدم <code dir=\"ltr\">notes-api:latest</code>، يتصرّفان بشكل مختلف. ما التفسير الأرجح؟</p>","options":[{"text":"يختار Kubernetes عشوائيًا بين إصدارات الـ image المخزّنة.","why":"لا شيء عشوائي هنا. كل node سحب ما كان الـ tag يشير إليه لحظة السحب."},{"text":"الـ tag ‏<code dir=\"ltr\">latest</code> يُحلّ دائمًا إلى أحدث image مع كل بدء للـ container.","why":"يُحلّ إلى ما يشير إليه الـ tag حين يسحب الـ node. والـ node الذي لديه نسخة مخزّنة قد لا يسحب مرة أخرى أبدًا."},{"text":"أُعيد دفع الـ tag بمحتوى جديد، وسحبه كلٌّ من الـ nodes الاثنين في وقت مختلف.","why":"صحيح. الـ tags قابلة للتغيير، فالاسم نفسه دلّ على بايتات مختلفة على nodes مختلفة. استخدم tags ذات إصدارات أو digests."},{"text":"أحد الـ Pods يشغّل image تالفة.","why":"الـ registries تتحقّق من الـ layers بالـ digest، فالتلف يُفشل السحب بدلًا من أن يعمل بصمت."}],"answer":2},{"q":"<p>ماذا يحدّد digest مثل <code dir=\"ltr\">sha256:3f1c…</code>؟</p>","options":[{"text":"تاريخ بناء الـ image ووقته.","why":"وقت البناء بيانات وصفية داخل إعدادات الـ image، وليس هو الـ digest."},{"text":"محتوى الـ image بالضبط؛ وأي تغيير في الـ image ينتج digest مختلفًا.","why":"صحيح. إنه hash للـ manifest، الذي يسرد بدوره hashes الـ layers. الـ digest نفسه يعني البايتات نفسها، في كل مكان."},{"text":"الـ commit في Git الذي بُنيت منه الـ image.","why":"الـ SHA من Git يصلح <em>tag</em> جيدًا، لكن الـ digest يُحسب من الـ image نفسها."},{"text":"حساب الـ registry الذي دفع الـ image.","why":"الـ digest مستقل عمّن دفع الـ image وعن مكان تخزينها."}],"answer":1},{"q":"<p>يبني مطوّرٌ image ‏notes-api على حاسوب Apple Silicon ويدفعها. على الـ cluster ذي معمارية amd64 يكتب الـ Pod في سجلّه <code dir=\"ltr\">exec format error</code>. ما الحل؟</p>","options":[{"text":"أضف <code dir=\"ltr\">EXPOSE</code> لمعمارية الـ cluster.","why":"التعليمة <code dir=\"ltr\">EXPOSE</code> توثّق المنافذ؛ ولا تستطيع تغيير معمارية المعالج للملفات التنفيذية."},{"text":"غيّر الـ base image إلى Alpine.","why":"images ‏Alpine تأتي أيضًا لكل معمارية على حدة. والـ image المبنية على الحاسوب ما زالت arm64."},{"text":"اضبط <code dir=\"ltr\">imagePullPolicy</code> على <code dir=\"ltr\">Always</code> في الـ Deployment.","why":"السحب من جديد يجلب الـ image نفسها التي لا تدعم إلا arm64."},{"text":"ابنِ بـ <code dir=\"ltr\">docker buildx build --platform linux/amd64,linux/arm64</code> (ويفضَّل في الـ CI) حتى يحتوي فهرس الـ image على المعماريّتين.","why":"صحيح. الحاسوب بنى arm64 فقط. البناء متعدّد المنصّات يدفع tag واحدًا يشير فهرسه إلى manifest لكل معمارية."}],"answer":3},{"q":"<p>أي مخطّط للـ tags يعطي أفضل قابلية للتتبّع لإصدارات notes-api؟</p>","options":[{"text":"إصدار دلالي مع tag من SHA الخاص بـ Git، مثل <code dir=\"ltr\">1.4.0</code> و<code dir=\"ltr\">sha-9f2c1ab</code>، يُدفعان للبناء نفسه.","why":"صحيح. الإصدار هو ما يتحدّث عنه الناس؛ والـ SHA يخبرك بالضبط أي commit تقرأ حين ينكسر شيء."},{"text":"الـ tag ‏<code dir=\"ltr\">latest</code> فقط، يُعاد دفعه مع كل merge إلى <code dir=\"ltr\">main</code>.","why":"تفقد أي وسيلة لمعرفة أي بناء يعمل، أو للتراجع إلى بناء محدّد باسمه."},{"text":"تاريخ البناء، مثل <code dir=\"ltr\">2026-10-05</code>.","why":"بناءان في يوم واحد يتصادمان، والتاريخ لا يشير إلى الكود الذي أنتجه."},{"text":"اسم المطوّر، مثل <code dir=\"ltr\">amara-test</code>.","why":"يصلح لتجربة عابرة، ولا فائدة منه لمعرفة ما يعمل في الإنتاج."}],"answer":0}],"exercise":{"kind":"order","items":["build","test","push","digest","manifest","apply","verify"],"title":"اشحن الإصدار 1.4.0","prompt":"<p>رتّب خطوات إطلاق notes-api 1.4.0، من الكود المصدري إلى نشر متحقَّق منه يشغّل فيه كل node البايتات نفسها تمامًا.</p>\n","hints":["لا تستطيع اختبار image غير موجودة، ولا تستطيع قراءة digest لم يره الـ registry بعد.","يجب أن يشير الـ manifest إلى الـ digest، لذا يجب أن تعرف الـ digest قبل تعديله.","التحقّق يأتي في النهاية، بعد أن يحصل الـ cluster على المواصفات الجديدة."],"explanation":"<p>ابنِ الـ image، واختبرها، وادفعها مع tag الإصدار وtag الـ SHA، واقرأ الـ digest الذي عيّنه الـ registry، واكتب هذا الـ digest في الـ manifest الخاص بالـ Deployment، وطبّقه، ثم تحقّق من الـ rollout. الاختبار قبل الدفع يُبقي البناءات المعطوبة خارج الـ registry، والنشر بالـ digest يعني أن ما اختبرته هو بالضبط ما يعمل.</p>\n","checkLabels":{},"itemText":{"build":"<code dir=\"ltr\">docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/skylane/notes-api:1.4.0 -t ghcr.io/skylane/notes-api:sha-9f2c1ab .</code>","test":"شغّل مجموعة الاختبارات وفحصًا أمنيًا على الـ image المبنية","push":"ادفع كلا الـ tags إلى <code dir=\"ltr\">ghcr.io</code>","digest":"الأمر <code dir=\"ltr\">docker buildx imagetools inspect ghcr.io/skylane/notes-api:1.4.0</code> لقراءة الـ digest","manifest":"اضبط <code dir=\"ltr\">image: ghcr.io/skylane/notes-api:1.4.0@sha256:…</code> في الـ manifest الخاص بالـ Deployment","apply":"<code dir=\"ltr\">kubectl apply -f k8s/</code>","verify":"<code dir=\"ltr\">kubectl rollout status deployment/notes-api</code>"}},"words":737,"readMinutes":4}