{"id":"l-dk-4-4","courseId":"docker-kubernetes","lang":"ar","kind":"lesson","summary":"انقل إعدادات notes-api إلى ConfigMap وبيانات اعتماده إلى Secret، وضع كل شيء في namespace اسمه <code dir=\"ltr\">notes</code>، وشغّل سير عمل apply وdiff قابلًا للمراجعة من مجلد k8s.","html":"<p>حاليًا، الـ Deployment الخاص بـ notes-api مكتوب فيه <code dir=\"ltr\">PORT</code>، والـ Deployment الخاص بقاعدة البيانات فيه كلمة المرور نصًّا صريحًا، وكل شيء يعيش في الـ namespace ‏<code dir=\"ltr\">default</code> بجوار كل ما يجرّبه الآخرون في هذا الـ cluster. هذه ثلاث طرق لإحداث حادث: غيّر إعدادًا فتضطر إلى تعديل حمل العمل؛ اقرأ المستودع فتحصل على كلمة المرور؛ شغّل <code dir=\"ltr\">kubectl delete</code> متهوّرًا في <code dir=\"ltr\">default</code> فتصيب عمل شخص آخر.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">‏namespace لـ notes-api</h2>\n<p>الـ <strong>namespace</strong> (نطاق الأسماء) نطاق للأسماء. يستطيع فريقان أن يملك كل منهما Deployment اسمه <code dir=\"ltr\">api</code> في namespaces مختلفة. والـ namespaces هي أيضًا حيث تربط الصلاحيات (RBAC) وحصص الموارد، ما يجعلها الوحدة الطبيعية لـ «تطبيق واحد» أو «فريق واحد».</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">k8s/namespace.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\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">v1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">Namespace</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span></code></pre></div>\n<p>أضف <code dir=\"ltr\">namespace: notes</code> إلى <code dir=\"ltr\">metadata</code> في كل كائن آخر في <code dir=\"ltr\">k8s/</code>، ثم اجعله الافتراضي لهذا الـ context حتى لا تكتب <code dir=\"ltr\">-n notes</code> في كل مرة:</p>\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:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> apply</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> k8s/namespace.yaml</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> config</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> set-context</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --current</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --namespace=notes</span></span></code></pre></div>\n<p>ما لا تفعله الـ namespaces هو عزل الشبكة. يستطيع Pod في <code dir=\"ltr\">notes</code> الاتصال بـ Service في أي namespace آخر ما لم تقل <strong>NetworkPolicy</strong> غير ذلك، وتفرضها إضافة الشبكة في الـ cluster لديك. لا تعامل الـ namespace كجدار أمني بمفرده.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ ConfigMaps للإعدادات</h2>\n<p>الـ <strong>ConfigMap</strong> يحفظ إعدادات مفتاح-قيمة غير حسّاسة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">k8s/configmap.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\">apiVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">v1</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">kind</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">ConfigMap</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">metadata</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api-config</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  namespace</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">data</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  PORT</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"3000\"</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  LOG_LEVEL</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">info</span></span></code></pre></div>\n<p>القيم نصوص دائمًا، ولهذا وُضعت <code dir=\"ltr\">&quot;3000&quot;</code> بين علامتَي تنصيص. الآن تستطيع image ‏notes-api نفسها العمل بسجلات <code dir=\"ltr\">info</code> في الإنتاج و<code dir=\"ltr\">debug</code> في staging، بتطبيق ConfigMap مختلف بدلًا من بناء image مختلفة.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ Secrets لبيانات الاعتماد</h2>\n<p>الـ <strong>Secret</strong> له شكل الـ ConfigMap نفسه، لكنه مخصّص للقيم الحسّاسة. أنشئه من سطر الأوامر (أو من مدير الأسرار لديك) بدلًا من ملف محفوظ في المستودع:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><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:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> generic</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-db</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --from-literal=POSTGRES_PASSWORD=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'dev-only-change-me'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --from-literal=DATABASE_URL=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'postgres://notes:dev-only-change-me@notes-db:5432/notes'</span></span></code></pre></div>\n<p>وهنا الجزء المزعج. انظر إلى ما هو مخزّن:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"4\"><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:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-db</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -o</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> jsonpath='{.data.POSTGRES_PASSWORD}'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> base64</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span></span></code></pre></div>\n<p>تعود القيمة مباشرة. بيانات الـ Secret <strong>مرمّزة بـ base64، لا مشفّرة</strong>. ما يجعل الـ Secrets أكثر أمانًا من الـ ConfigMaps هو كل ما يحيط بها: يستطيع RBAC منح صلاحية قراءة الـ ConfigMaps دون الـ Secrets، ويستطيع الـ cluster تشفير الـ Secrets أثناء تخزينها في etcd (والمزوّدون المُدارون يفعلون ذلك عادةً)، ولا تستلم الـ kubelets إلا الـ Secrets الخاصة بالـ Pods المُجدولة على الـ node الخاص بها.</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>ملفات Secret في Git</span></p>\n<p>ملف YAML لـ Secret بقيم base64 في مستودع هو كلمة مرور صريحة مع خطوة إضافية واحدة. دوّرت بيانات اعتماد لهذا السبب أكثر من أي سبب آخر. أبقِ القيم الحقيقية خارج Git: أنشئ الـ Secrets من مدير أسرار عبر أداة مثل External Secrets Operator، أو احفظ فقط صيغًا مشفّرة بأداة مبنية لذلك، واعتبر كل ما حُفظ سابقًا مسرّبًا.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">ربطها بـ notes-api</h2>\n<p>حدّث الـ container في <code dir=\"ltr\">k8s/deployment.yaml</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"yaml\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">k8s/deployment.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\">      containers</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">api</span></span>\n<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</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          ports</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">http</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              containerPort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3000</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          envFrom</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">configMapRef</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">                name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-api-config</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          env</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">            - </span><span style=\"color:#116329;--shiki-dark:#7EE787\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">DATABASE_URL</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">              valueFrom</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">                secretKeyRef</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">:</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">                  name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">notes-db</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">                  key</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">DATABASE_URL</span></span></code></pre></div>\n<p>الحقل <code dir=\"ltr\">envFrom</code> يحوّل كل مفتاح في الـ ConfigMap إلى متغيّر بيئة. و<code dir=\"ltr\">secretKeyRef</code> يسحب مفتاحًا واحدًا من الـ Secret. وفي <code dir=\"ltr\">k8s/db.yaml</code>، استبدل القيمة الحرفية لـ <code dir=\"ltr\">POSTGRES_PASSWORD</code> بـ <code dir=\"ltr\">secretKeyRef</code> إلى الـ Secret نفسه.</p>\n<p>إذا لم يكن المفتاح المُشار إليه موجودًا، فلن يبدأ الـ container أبدًا. يُظهر الـ Pod الحالة <code dir=\"ltr\">CreateContainerConfigError</code>، ويسمّي <code dir=\"ltr\">kubectl describe pod</code> المفتاح المفقود.</p>\n<p>يمكن أيضًا <strong>تركيب</strong> الـ Secrets والـ ConfigMaps <strong>كملفات</strong> عبر volume. الملفات المركّبة تتحدّث في الـ Pods العاملة بعد تأخير قصير؛ أما متغيّرات البيئة فثابتة منذ بدء الـ container. بعد تغيير إعدادات تصل عبر متغيّرات البيئة، أعِد تشغيل الـ Pods تدريجيًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"6\"><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:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> restart</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span></span></code></pre></div>\n<h2 id=\"sec-5\" tabindex=\"-1\">سير العمل اليومي</h2>\n<p>مجلد <code dir=\"ltr\">k8s/</code> لديك يحتوي الآن على <code dir=\"ltr\">namespace.yaml</code> و<code dir=\"ltr\">configmap.yaml</code> و<code dir=\"ltr\">db.yaml</code> و<code dir=\"ltr\">deployment.yaml</code> و<code dir=\"ltr\">service.yaml</code>. والحلقة لكل تغيير هي نفسها:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"7\"><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:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> diff</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> k8s/</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> apply</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> k8s/</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> all</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -l</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> app.kubernetes.io/name=notes-api</span></span></code></pre></div>\n<p>الأمر <code dir=\"ltr\">diff</code> هو خطوة المراجعة: يُظهر ما سيتغيّر على الـ cluster الحيّ، حقلًا بحقل، قبل أن تغيّره. اجعله عادة وستلتقط التعديل الشارد الذي كان سيعيد ضبط إصلاح طارئ أجراه أحدهم. وحين تحتاج إلى قيم مختلفة لكل بيئة، فإن Kustomize (المدمج في kubectl كـ <code dir=\"ltr\">kubectl apply -k</code>) يضع طبقات صغيرة (overlays) فوق هذه الملفات الأساسية؛ وHelm، في الدرس 5.3، هو الإجابة الشائعة الأخرى.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الـ Labels لغة الاستعلام لديك</span></p>\n<p>ضع <code dir=\"ltr\">app.kubernetes.io/name</code> على كل شيء، و<code dir=\"ltr\">app.kubernetes.io/part-of: notes</code> إذا كان التطبيق مكوّنًا من عدّة مكوّنات. عندها يعرض <code dir=\"ltr\">kubectl get all,cm,secret -l app.kubernetes.io/part-of=notes</code> النظام كاملًا بأمر واحد، ويصبح التنظيف على بُعد <code dir=\"ltr\">kubectl delete -l</code> واحد.</p>\n</aside>\n<p>يعمل notes-api الآن على Kubernetes بشبكة وإعدادات سليمة. لكنه ليس جاهزًا للإنتاج بعد: لا يعرف Kubernetes هل هو سليم، ولا كم يحتاج من المعالج، ولا كيف يحدّثه بأمان. القسم الخامس يغطّي الثلاثة.</p>\n","toc":[{"id":"sec-1","text":"‏namespace لـ notes-api","level":2},{"id":"sec-2","text":"الـ ConfigMaps للإعدادات","level":2},{"id":"sec-3","text":"الـ Secrets لبيانات الاعتماد","level":2},{"id":"sec-4","text":"ربطها بـ notes-api","level":2},{"id":"sec-5","text":"سير العمل اليومي","level":2}],"takeaways":["الـ ConfigMaps تحفظ الإعدادات غير الحسّاسة؛ والـ Secrets تحفظ بيانات الاعتماد، وتُحقن كمتغيّرات بيئة أو كملفات مركّبة.","قيم الـ Secret مرمّزة بـ base64 فقط، لا مشفّرة؛ احمِها بـ RBAC، وبالتشفير أثناء التخزين، وبإبقائها خارج Git.","متغيّرات البيئة القادمة من ConfigMap أو Secret تُقرأ مرة واحدة عند بدء الـ container، لذا أعِد تشغيل الـ Deployment بعد تغييرها.","الـ Namespaces تحدّد نطاق الأسماء والصلاحيات والحصص لفريق أو تطبيق، لكنها لا تعزل حركة الشبكة وحدها.","تشغيل <code dir=\"ltr\">kubectl diff -f k8s/</code> قبل <code dir=\"ltr\">kubectl apply -f k8s/</code> يُظهر بالضبط ما سيتغيّر، مثل pull request للـ cluster."],"further":[{"title":"ConfigMaps","url":"https://kubernetes.io/docs/concepts/configuration/configmap/"},{"title":"Secrets","url":"https://kubernetes.io/docs/concepts/configuration/secret/"},{"title":"Good practices for Kubernetes Secrets","url":"https://kubernetes.io/docs/concepts/security/secrets-good-practices/"},{"title":"Namespaces","url":"https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/"}],"quiz":[{"q":"<p>يحفظ زميلك في Git ملف manifest لـ Secret قيم <code dir=\"ltr\">data</code> فيه نصوص base64، قائلًا «لا بأس، إنها مرمّزة». ما المشكلة؟</p>","options":[{"text":"قيم base64 يجب أن تكون في <code dir=\"ltr\">stringData</code> لا في <code dir=\"ltr\">data</code>.","why":"العكس هو الصحيح؛ ‏<code dir=\"ltr\">data</code> يأخذ base64 و<code dir=\"ltr\">stringData</code> يأخذ نصًّا صريحًا. الصيغة ليست المشكلة."},{"text":"الـ base64 ترميز يستطيع أي شخص عكسه بـ <code dir=\"ltr\">base64 -d</code>، فبيانات الاعتماد الآن في سجلّ Git وكأنها نصّ صريح.","why":"صحيح. اعتبر السرّ مسرّبًا، ودوّره، وأبقِ قيم الـ Secrets خارج Git (استخدم مدير أسرار أو أدوات تشفير)."},{"text":"يرفض Kubernetes الـ Secrets المحفوظة في Git.","why":"لا يرى Kubernetes سجلّ Git لديك. سيقبل الـ manifest بكل سرور."},{"text":"الـ base64 يجعل القيم أطول من أن تصلح لمتغيّرات البيئة.","why":"يفكّ Kubernetes ترميز القيم قبل حقنها؛ والطول ليس المشكلة."}],"answer":1},{"q":"<p>تغيّر <code dir=\"ltr\">LOG_LEVEL</code> في الـ ConfigMap ‏<code dir=\"ltr\">notes-api-config</code> وتطبّقه. الـ Pods تستهلكه عبر <code dir=\"ltr\">envFrom</code>. ماذا يحدث؟</p>","options":[{"text":"يلتقط كل Pod القيمة الجديدة خلال دقيقة.","why":"الـ ConfigMap المركّب <em>كملفات</em> هو الذي يتحدّث مع الوقت؛ أما متغيّرات البيئة فلا تتغيّر أبدًا في container يعمل."},{"text":"ينشر الـ Deployment تلقائيًا Pods جديدة.","why":"تغيير الـ ConfigMap لا يمسّ قالب الـ Pod في الـ Deployment، فلا يبدأ أي rollout."},{"text":"لا شيء يتغيّر في الـ Pods العاملة حتى يُعاد تشغيلها؛ شغّل <code dir=\"ltr\">kubectl rollout restart deployment/notes-api</code>.","why":"صحيح. متغيّرات البيئة تُضبط عند بدء الـ container. والـ rollout restart يستبدل الـ Pods تدريجيًا بالقيم الجديدة."},{"text":"تنهار الـ Pods لأن بيئتها تغيّرت من تحتها.","why":"العمليات العاملة تحتفظ ببيئتها؛ ولا يُدفع إليها أي شيء."}],"answer":2},{"q":"<p>‏Pod جديد عالق بحالة <code dir=\"ltr\">CreateContainerConfigError</code>. ما السبب الأرجح؟</p>","options":[{"text":"الـ Pod يشير إلى مفتاح في Secret أو ConfigMap غير موجود.","why":"صحيح. لا يستطيع الـ kubelet بناء بيئة الـ container، فلا يبدأ أبدًا. والأمر <code dir=\"ltr\">kubectl describe pod</code> يسمّي المفتاح المفقود."},{"text":"لا يمكن سحب الـ image من الـ registry.","why":"هذا يظهر كـ <code dir=\"ltr\">ErrImagePull</code> أو <code dir=\"ltr\">ImagePullBackOff</code>."},{"text":"انهار التطبيق عند البدء.","why":"الانهيار بعد البدء يظهر كـ <code dir=\"ltr\">CrashLoopBackOff</code>، مع سجلات يمكن قراءتها."},{"text":"نفدت ذاكرة الـ node.","why":"الـ Pod الذي لا يمكن وضعه لنقص الموارد يبقى <code dir=\"ltr\">Pending</code> مع حدث جدولة."}],"answer":0},{"q":"<p>‏notes-api وخدمة مدفوعات يعملان في namespaces منفصلة على الـ cluster نفسه. هل يستطيع Pod من notes-api فتح اتصال بالـ Service الخاص بقاعدة بيانات المدفوعات؟</p>","options":[{"text":"لا، الـ namespaces تحجب كل الحركة بينها.","why":"الـ namespaces تحدّد نطاق الأسماء والصلاحيات. ودون NetworkPolicy تستطيع الـ Pods الوصول إلى الـ Services في أي namespace."},{"text":"فقط إذا كان لكلا الـ namespaces الـ labels نفسها.","why":"الـ labels الخاصة بالـ namespace تهمّ في selectors الـ NetworkPolicy، لكن دون سياسة لا يوجد أي تقييد."},{"text":"فقط عبر Service من نوع LoadBalancer.","why":"الـ Services من نوع ClusterIP يمكن الوصول إليها من كل namespace في الـ cluster افتراضيًا."},{"text":"نعم، افتراضيًا؛ استخدم NetworkPolicy (مع إضافة شبكة تفرضها) لتقييد ذلك.","why":"صحيح. الـ namespaces ليست حدًّا شبكيًا. والـ NetworkPolicies هي طريقة إضافة هذا الحدّ."}],"answer":3}],"exercise":{"kind":"fill","language":"bash","template":"# Create the namespace and make it the default for this context\nkubectl create {{0}} notes\nkubectl config set-context --current --{{1}}=notes\n\n# Store the database password as a Secret\nkubectl create secret {{2}} notes-db --from-literal=POSTGRES_PASSWORD=dev-only-change-me\n\n# Read it back (and see that it isn't encrypted)\nkubectl get secret notes-db -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 {{3}}\n\n# Pick up changed environment variables in running Pods\nkubectl rollout {{4}} deployment/notes-api","blanks":[{"answers":["namespace","ns"]},{"answers":["namespace"]},{"answers":["generic"]},{"answers":["-d","--decode","-D"]},{"answers":["restart"]}],"title":"‏Namespace، ثم Secret، ثم إعادة تشغيل","prompt":"<p>املأ الفراغات لإنشاء الـ namespace ‏<code dir=\"ltr\">notes</code> وجعله الافتراضي، وتخزين كلمة مرور قاعدة البيانات في Secret، وفكّ ترميزها لإثبات أنها base64 فقط، وإعادة تشغيل notes-api حتى يلتقط متغيّرات البيئة المتغيّرة.</p>\n","hints":["نوع الكائن الذي تنشئه أولًا هو أيضًا اسم الخيار في <code dir=\"ltr\">set-context</code>.","نوع الـ Secret المخصّص لأي أزواج مفتاح-قيمة تختارها اسمه بالإنجليزية عكس «specific» (محدّد).","يفكّ <code dir=\"ltr\">base64</code> الترميز بخيار قصير؛ و<code dir=\"ltr\">kubectl rollout</code> له أمر فرعي يستبدل الـ Pods دون تغيير القالب يدويًا."],"explanation":"<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><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:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> namespace</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> config</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> set-context</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --current</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --namespace=notes</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> generic</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-db</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --from-literal=POSTGRES_PASSWORD=dev-only-change-me</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> get</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> secret</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> notes-db</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -o</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> jsonpath='{.data.POSTGRES_PASSWORD}'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> base64</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> -d</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">kubectl</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rollout</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> restart</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> deployment/notes-api</span></span></code></pre></div>\n<p>‏<code dir=\"ltr\">generic</code> هو نوع الـ Secret للمفاتيح التي تختارها بنفسك (ومن الأنواع الأخرى <code dir=\"ltr\">tls</code> و<code dir=\"ltr\">docker-registry</code>). خطوة فكّ الترميز تُظهر لماذا يجب ألا تُحفظ ملفات الـ Secret في المستودع أبدًا. و<code dir=\"ltr\">rollout restart</code> يستبدل الـ Pods تدريجيًا، والجديدة منها تقرأ القيم الحالية للـ ConfigMap والـ Secret عند البدء.</p>\n","checkLabels":{}},"words":652,"readMinutes":4}