<?xml version="1.0"?>
<rss version="2.0"><channel><title>DevOps: &#x625;&#x62F;&#x627;&#x631;&#x629; &#x627;&#x644;&#x625;&#x639;&#x62F;&#x627;&#x62F;&#x627;&#x62A; &#x648;&#x627;&#x644;&#x646;&#x634;&#x631;</title><link>https://academy.hsoub.com/devops/deployment/?d=4</link><description>DevOps: &#x625;&#x62F;&#x627;&#x631;&#x629; &#x627;&#x644;&#x625;&#x639;&#x62F;&#x627;&#x62F;&#x627;&#x62A; &#x648;&#x627;&#x644;&#x646;&#x634;&#x631;</description><language>ar</language><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x628;&#x627;&#x64A;&#x62B;&#x648;&#x646; &#x648;&#x646;&#x634;&#x631;&#x647; &#x639;&#x644;&#x649; &#x643;&#x648;&#x628;&#x631;&#x646;&#x62A;&#x633; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x645;&#x646;&#x635;&#x629; Okteto</title><link>https://academy.hsoub.com/devops/deployment/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%88%D9%86%D8%B4%D8%B1%D9%87-%D8%B9%D9%84%D9%89-%D9%83%D9%88%D8%A8%D8%B1%D9%86%D8%AA%D8%B3-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D9%86%D8%B5%D8%A9-okteto-r838/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_03/Okteto_.png.11d9c7336953f797d2fc08c15a232db0.png" /></p>
<p>
	نشرح في هذا المقال إنشاء تطبيق مكتوب بلغة بايثون Python على كوبرنتس Kubernetes باستخدام منصة أوكتيتو Okteto التي تسهل عملية تطوير تطبيقات كوبرنتس Kubernetes، فهي تسمح للمطورين ببناء التطبيقات واختبارها مباشرة في <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%AA%D8%B9%D9%84%D9%85-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-kubernetes-r468/" rel="">عناقيد كوبرنتس Kubernetes clusters</a> دون الحاجة لإعداد بيئات تطوير معقدة، كما توّفر <a href="https://www.okteto.com/" rel="external nofollow">منصة Okteto</a> ميزة التحديثات المباشرة live updates للتطبيقات التي تعمل في عناقيد كوبرنتس مما يسمح للمطورين رؤية التعديلات على الشيفرة في الوقت الفعلي دون الحاجة إلى إعادة بناء التطبيقات أو إعادة نشرها.
</p>

<h2 id="">
	المتطلبات الأساسية
</h2>

<p>
	لمتابعة خطوات هذا المقال، سنحتاج إلى ما يلي:
</p>

<ul>
	<li>
		عنقود كوبرنتس النسخة 1.28، وسنستخدم في مقالنا عنقود من <a href="https://docs.digitalocean.com/products/kubernetes/how-to/create-clusters/" rel="external nofollow">منصة DigitalOcean</a>، يحتوي على 3 عقد على الأقل
	</li>
	<li>
		تثبيت واجهة سطر الأوامر <code>kubectl</code> وضبطها للتواصل مع عنقود كوبرنتس
	</li>
	<li>
		حساب على <a href="https://hub.docker.com/" rel="external nofollow"> مستودع دوكر هب</a>
	</li>
	<li>
		تثبيت <a href="https://academy.hsoub.com/devops/cloud-computing/docker/" rel="">دوكر Docker</a> على جهازنا المحلي
	</li>
	<li>
		مفتاح ترخيص لاستخدام منصة Okteto، ويمكن <a href="https://www.okteto.com/free-trial/" rel="external nofollow">التسجيل في الإصدار التجريبي المجاني</a>.
	</li>
	<li>
		تثبيت مدير الحزم Helm لتطوير التطبيقات ضمن عناقيد Kubernetes، باتباع الخطوة الأولى من <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA-%D8%B6%D9%85%D9%86-%D8%B9%D9%86%D8%A7%D9%82%D9%8A%D8%AF-kubernetes-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D8%AF%D9%8A%D8%B1-%D8%A7%D9%84%D8%AD%D8%B2%D9%85-helm-r653/" rel="">المقال التالي</a>.
	</li>
	<li>
		نطاق Domain مسجَّل ومربوط مع Load Balancer لإدارة حركة مرور التطبيقات، وإنشاء سجل A record باسم <code>*</code> وعنوان IP موازن الحِمل.
	</li>
</ul>

<h2 id="-1">
	إنشاء تطبيق بايثون
</h2>

<p>
	علينا التأكد أولًا بأن بايثون مثبّت على جهاز يعمل بنظام التشغيل أوبنتو بكتابة الأمر التالي في الطرفية:
</p>

<pre class="ipsCode">python3 --version
</pre>

<p>
	إذا كانت حزم بايثون مثبّتة على الجهاز فسيعرضالأمر السابق نسختها، وإلّا نستخدم الأمر التالي لتثبيتها:
</p>

<pre class="ipsCode">sudo apt install python3 python3-venv python3-pip -y
</pre>

<p>
	بعدها، ننشئ مجلد لتخزين شيفرة البرنامج وإعداداته:
</p>

<pre class="ipsCode">mkdir my-app
</pre>

<p>
	ثم ننشئ بيئة افتراضية داخل مجلد التطبيق لوضع اعتماديات المشروع فيها:
</p>

<pre class="ipsCode">cd my-app
python3 -m venv python-env
</pre>

<p>
	والآن نفعّل البيئة الافتراضية باستخدام الأمر التالي:
</p>

<pre class="ipsCode">source python-env/bin/activate
</pre>

<p>
	ثم ننشئ ملف بايثون لكتابة شيفرة التطبيق فيه:
</p>

<pre class="ipsCode">nano app.py
</pre>

<p>
	ونضيف له الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_3095_15" style=""><span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">

app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> hello</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"Hello, This is a simple Python App!"</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">'__main__'</span><span class="pun">:</span><span class="pln">
    app</span><span class="pun">.</span><span class="pln">run</span><span class="pun">(</span><span class="pln">debug</span><span class="pun">=</span><span class="kwd">True</span><span class="pun">,</span><span class="pln"> host</span><span class="pun">=</span><span class="str">'0.0.0.0'</span><span class="pun">)</span></pre>

<p>
	ثم نثبّت إطار عمل فلاسك Flask:
</p>

<pre class="ipsCode">pip install flask
</pre>

<p>
	نشغّل التطبيق الآن باستخدام الأمر التالي:
</p>

<pre class="ipsCode">python3 app.py 
</pre>

<p>
	سنحصل على هذا الخرج:
</p>

<pre class="ipsCode">Output

* Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://172.20.10.2:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 311-959-468
</pre>

<p>
	أصبح إطار فلاسك Flask يعمل على جهازنا المحلي، نتأكّد من ذلك باستخدام الأمر <code>curl</code>:
</p>

<pre class="ipsCode">curl -X GET -H "Content-Type: application/json" http://localhost:5000
</pre>

<p>
	سنحصل على الرد التالي من تطبيق فلاسك:
</p>

<pre class="ipsCode">Output
Hello, This is a simple Python App!
</pre>

<p>
	يدل هذا الخرج على أن تطبيق فلاسك قد نُفّذ بنجاح دون أخطاء.
</p>

<p>
	يمكن إيقاف تفعيل البيئة الافتراضية عند الانتهاء من العمل على مشروعنا، باستخدام الأمر التالي:
</p>

<pre class="ipsCode">deactivate
</pre>

<p>
	وبهذا تكون أنشأنا تطبيقنا واختبرنا عمله محليًا، وحان وقت الخطوة الثانية لتشغيل هذا التطبيق داخل دوكر Docker.
</p>

<h2 id="docker">
	تشغيل تطبيق بايثون داخل دوكر Docker
</h2>

<p>
	يتطلب تشغيل تطبيق بايثون داخل <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r641/" rel="">حاوية دوكر</a> إنشاء صورة دوكر Docker image تحتوي على بيئة بايثون والاعتماديات اللازمة لتشغيل التطبيق. ننشئ أولًا ملفًا باسم <code>Dockerfile</code> في المجلد الأساسي للتطبيق:
</p>

<pre class="ipsCode">nano Dockerfile
</pre>

<p>
	ثم نضيف عليه التعليمات التالية:
</p>

<pre class="ipsCode"># Use an official Python runtime as a parent image
FROM python:3.8-slim

# Set the working directory in the container
WORKDIR /app

# Copy the current directory contents into the container at /app
ADD . /app

# Install any needed dependencies specified in requirements.txt
RUN pip install flask

# Make port 5000 available to the world outside this container
EXPOSE 5000

# Define environment variable
ENV NAME DockerizedPythonApp

# Run app.py when the container launches
CMD ["python3", "./app.py"]
</pre>

<p>
	فيما يلي شرح تعليمات ملف دوكر السابقة:
</p>

<ul>
	<li>
		تحدد التعليمة <code>FROM python:3.8-slim </code>الصورة الأساسية التي نحتاجها
	</li>
	<li>
		تضبط <code>WORKDIR /app</code> المجلد <code>‎/app</code> مجلدًا للعمل داخل الحاوية container
	</li>
	<li>
		تنسخ التعليمة <code>ADD . /app</code> محتوى المجلد الحالي لمجلد حاوية التطبيق <code>‎/app</code>
	</li>
	<li>
		تثبّت <code>RUN pip install flask </code>إطار العمل فلاسك Flask
	</li>
	<li>
		تستمع التعليمة <code>EXPOSE 5000 </code> على المنفذ 5000 للسماح بالاتصال
	</li>
	<li>
		يحدد <code>CMD ["python3", "app.py"] </code>الأمر الذي سينفذ عند تشغيل الحاوية وهو هنا الأمر <code>app.py</code>
	</li>
</ul>

<p>
	بعدها سنشغّل الأمر التالي لبناء صورة دوكر وفقًا للتعليمات السابقة:
</p>

<pre class="ipsCode">docker build -t my-app .
</pre>

<p>
	نستطيع تشغيل التطبيق الآن في الحاوية باستخدام الصورة التي أنشأناها:
</p>

<pre class="ipsCode">docker run -dit -p 5000:5000 my-app:latest
</pre>

<p>
	يُشَغّل الأمر السابق حاوية من الصورة <code>my-app</code> ويضبط منفذ الحاوية على المنفذ المضيف <code>5000</code>. نكتب الأمر التالي للتأكد من عمل الحاوية:
</p>

<pre class="ipsCode">docker ps
</pre>

<p>
	وسنحصل على الخرج التالي:
</p>

<pre class="ipsCode">Output
CONTAINER ID   IMAGE           COMMAND              CREATED         STATUS         PORTS                                       NAMES
761c54c77411   my-app:latest   "python3 ./app.py"   3 seconds ago   Up 3 seconds   0.0.0.0:5000-&gt;5000/tcp, :::5000-&gt;5000/tcp   pedantic_wescoff
</pre>

<p>
	نفتح متصفح الويب أو نستعمل موجّه الأوامر <code>curl</code> للوصول إلى التطبيق باستخدام العنوان <code>‎http://your-server-ip:5000/</code>، وستظهر رسالة Hello, This is a simple Python App!‎ التي تشير لأن التطبيق يعمل داخل حاوية دوكر. وبهذا نكون شغّلنا تطبيق بايثون في حاوية دوكر بنجاح.
</p>

<h2 id="dockerhub">
	رفع صورة تطبيق بايثون إلى سجل DockerHub
</h2>

<p>
	لتنفيذ هذه الخطوة يجب أن يكون لدينا حساب على موقع <a href="https://hub.docker.com/" rel="external nofollow">دوكر هب DockerHub</a>، بعدها نستخدم الأمر <code>docker login</code> لتسجيل الدخول، وسيُطلب منا إدخال اسم المستخدم وكلمة المرور.
</p>

<pre class="ipsCode">docker login 
</pre>

<p>
	سنحصل على الخرج التالي:
</p>

<pre class="ipsCode">Output
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: username@gmail.com
Password: 
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
</pre>

<p>
	يجب وسم الصورة باسم حسابنا ومجلد الاعتمادية المطلوب قبل رفعها push لمستودع دوكر هب:
</p>

<pre class="ipsCode">docker tag my-app yourusername/my-app:latest
</pre>

<p>
	يمكن الآن رفع الصورة إلى دوكر هب باستخدام الأمر <code>docker push</code>:
</p>

<pre class="ipsCode">docker push yourusername/my-app:latest
</pre>

<p>
	نتأكّد بعدها أن الصورة أصبحت موجودة بالبحث عنها باستخدام طرفية دوكر هب Docker Hub CLI:
</p>

<pre class="ipsCode">docker search yourusername/my-app
</pre>

<p>
	وهكذا أصبحت صورة تطبيقنا مرفوعةً على منصة دوكر هب ويمكن للآخرين سحبها أو نشرها في عدّة بيئات.
</p>

<h2 id="kubernetesmanifests">
	إنشاء ملف Kubernetes Manifests لنشر التطبيق
</h2>

<p>
	علينا الآن إنشاء ملف Kubernetes Manifests باستخدام منصة Okteto لضبط عملية التطوير والخدمات وموارد Ingressللتطبيق. نكتب الأمر التالي لإنشاء الملف:
</p>

<pre class="ipsCode">nano k8s.yaml
</pre>

<p>
	ثم نضيف الإعدادات التالية إليه:
</p>

<pre class="ipsCode">apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - image: yourusername/my-app
        name: my-app

---

apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: ClusterIP
  ports:
  - name: "my-app"
    port: 5000
  selector:
    app: my-app

---

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    dev.okteto.com/generate-host: my-app
spec:
  rules:
    - http:
        paths:
          - backend:
              service:
                name: my-app
                port:
                  number: 5000
            path: /
            pathType: ImplementationSpecific
</pre>

<p>
	سينشر الملف السابق تطبيقنا بالاسم <code>my-app</code> باستخدام منصة Okteto، وسينشره داخليًا عبر خدمة ClusterIP على المنفذ 5000، ثم سيُعدّ مورد Ingress لتوجيه حركة مرور HTTP إلى التطبيق. وهنا استخدمنا التعليقات التوضيحية الخاصة بـمنصة Okteto لتفعيل بعض الميزات التي تقدمها مثل إنشاء اسم المضيف hostname تلقائيًا.
</p>

<h2 id="oktetohelm">
	تثبيت Okteto باستخدام مدير الحزم Helm
</h2>

<p>
	لتثبيت Okteto باستخدام مدير الحزم Helm علينا أولًا إضافة مستودع Okteto Helm إلى عميل Helm:
</p>

<pre class="ipsCode">helm repo add okteto https://charts.okteto.com
</pre>

<p>
	وبعد ذلك علينا تحديث مستودعات مدير الحزم Helm لنضمن أن لدينا معلومات حديثة عن المخططات أو الحزم التي تحتوي على تعريفات موارد Kubernetes اللازمة لتشغيل التطبيق:
</p>

<pre class="ipsCode">helm repo update
</pre>

<p>
	ثم ننشئ ملف <code>config.yaml</code>:
</p>

<pre class="ipsCode">nano config.yaml
</pre>

<p>
	ونضيف بعدها مفتاح رخصة استخدام Okteto الخاص بنا، والنطاق الفرعي والإعدادات، كما هو موضح أدناه:
</p>

<pre class="ipsCode">license: INSERTYOURKEYHERE
subdomain: okteto.example.com

buildkit:
  persistence:
    enabled: true

registry:
  storage:
    filesystem:
      persistence:
        enabled: true
</pre>

<p>
	ثم نثبّت أحدث نسخة من Okteto باستخدام ملف الإعدادات <code>config.yaml</code>:
</p>

<pre class="ipsCode">helm install okteto okteto/okteto -f config.yaml --namespace okteto --create-namespace
</pre>

<p>
	سنحصل على الخرج التالي عند انتهاء التثبيت:
</p>

<pre class="ipsCode">Output
NAME: okteto
LAST DEPLOYED: Tue Mar 12 20:27:21 2024
NAMESPACE: okteto
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Congratulations! Okteto is successfully installed!

Follow these steps to complete your domain configuration:

1. Create a wildcard A record "*.okteto.example.com", pointing it to the Okteto NGINX ingress controller External-IP:

   $ kubectl get service -l=app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/component=controller --namespace=okteto

2. Access your Okteto instance at `https://okteto.okteto.example.com/login#token=88f8cc11`
</pre>

<p>
	<strong>ملاحظة:</strong> علينا الاحتفاظ برمز الوصول الشخصي Personal Access Tokens ذو القيمة <code>88f8cc11</code> من الشيفرة أعلاه لأننا سنحتاجه لتوثيق حسابنا في Okteto. ننتظر قليلًا ثم نجلب عنوان IP لخادم NGINX Ingress:
</p>

<pre class="ipsCode">kubectl get service -l=app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/component=controller --namespace=okteto
</pre>

<p>
	سنحصل على الخرج التالي:
</p>

<pre class="ipsCode">Output
NAME                              TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)                      AGE
okteto-ingress-nginx-controller   LoadBalancer   10.101.31.239   45.76.14.191   80:31150/TCP,443:31793/TCP   3m21s
</pre>

<p>
	علينا إضافة العنوان الخارجي <code>EXTERNAL-IP</code> إلى سجل <code>A</code> تحت الاسم <code>*</code> في إعدادات بروتوكول DNS. والآن، نفتح المتصفح للوصول إلى Okteto باستخدام الرابط التالي: <a href="https://okteto.okteto.example.com/login#token=88f8cc11." ipsnoembed="true" rel="external nofollow">https://okteto.okteto.example.com/login#token=88f8cc11.</a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="168974" href="https://academy.hsoub.com/uploads/monthly_2025_03/okteto-dashboard.png.16ba6123f2a05284679de0e357b722a1.png" rel=""><img alt="okteto dashboard" class="ipsImage ipsImage_thumbnailed" data-fileid="168974" data-unique="csw3hqokr" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_03/okteto-dashboard.thumb.png.98da596f57f007a5d0ed3fe2e8f9183f.png"> </a>
</p>

<h2 id="okteto-1">
	تثبيت وضبط إعدادات واجهة أوامر Okteto
</h2>

<p>
	تُتيح لنا واجهة أوامر Okteto مفتوحة المصدر تطوير التطبيقات مباشرة على Kubernetes. بإمكاننا تثبيت واجهة الأوامر على نظامي لينوكس Linux وماك MacOS باستخدام الأمر <code>curl</code> التالي:
</p>

<pre class="ipsCode">sudo curl https://get.okteto.com -sSfL | sh
</pre>

<p>
	نتأكد من تثبيت واجهة أوامر Okteto باستخدام الأمر التالي الذي سيعرض تانسخة المثبّتة على جهازنا
</p>

<pre class="ipsCode">Output
okteto version 2.25.2 
</pre>

<p>
	والآن علينا استخدام رمز الوصول الشخصي Personal Access Tokens للمصادقة:
</p>

<pre class="ipsCode">okteto context use https://okteto.okteto.example.com --token 88f8cc11 --insecure-skip-tls-verify
</pre>

<p>
	وسنحصل على الخرج التالي:
</p>

<pre class="ipsCode">Output
✓  Using okteto-admin @ okteto.okteto.example.com
</pre>

<p>
	نشغّل الأمر التالي للتأكد من إعدادات واجهة الأوامر:
</p>

<pre class="ipsCode">okteto context list
</pre>

<p>
	وسنحصل على الخرج التالي:
</p>

<pre class="ipsCode">Output
Name                                      Namespace     Builder                                 Registry
https://okteto.okteto.example.com *      okteto-admin  tcp://buildkit.okteto.example.com:443  registry.okteto.example.com
vke-4b7aaaa6-78fa-4a19-9fb3-cf7b8c1ec678  default       docker                                  -
</pre>

<h2 id="oktetomanifest">
	إنشاء Okteto Manifest
</h2>

<p>
	علينا إنشاء ملف Okteto Manifest وضبط إعدادات بيئة التطوير لنتمكن من إنشاء تطبيقات بلغة بايثون. أولًا، ننشئ ملف <code>okteto.yaml</code> مخصص لتطبيق بايثون بسيط.
</p>

<pre class="ipsCode">nano okteto.yaml
</pre>

<p>
	ثم نضيف الإعدادات التالية:
</p>

<pre class="ipsCode">deploy:
  - kubectl apply -f k8s.yaml

dev:
  my-app:
    command: bash
    environment:
      - FLASK_ENV=development
    sync:
      - .:/app
    reverse:
      - 9000:9000
    volumes:
      - /root/.cache/pip
</pre>

<p>
	فيما يلي شرح الأوامر السابقة:
</p>

<p>
	يتضمن القسم <code>deploy </code>إعدادات النشر. وعند تشغيل الأمر <code>okteto up</code> أو <code>okteto deploy</code> يُنَفَّذ الأمر <code>kubectl apply -f k8s.yaml</code> الذي ينشر موارد Kubernetes الموجودة في الملف <code>k8s.yaml</code>، ويُتيح ذلك فصل إعدادات النشر عن إعدادات التطوير.
</p>

<p>
	يُحدد <code>command: bash </code>الأمر الذي يجب تشغيله عند بدء تشغيل بيئة التطوير ويحدد <code>environment </code>متغيرات البيئة التي يجب أن تكون في بيئة التطوير. وهنا صرّحنا أن متغيرات فلاسك <code>FLASK_ENV</code> يجب أن تكون في بيئة التطوير <code>FLASK_ENV=development</code>.
</p>

<p>
	يحدد القسم <code>sync </code>القسم الملفات التي يجب مزامنتها من جهازنا المحلي مع ملفات بيئة التطوير، إذ يجب مزامنة المجلد <code>(.)</code> مع المجلد <code>/app‎</code> من بيئة التطوير. ويوّضح  <code>reverse</code> قواعد توجيه المنافذ بين بيئة التطوير وجهازك المحلي. وفي مثالنا، يوجّه المنفذ 9000 من بيئة التطوير إلى المنفذ 9000 على جهازنا المحلي.
</p>

<p>
	أخيرًا يوّضح <code>v</code> وحدات التخزين الإضافية التي ستُضاف إلى بيئة التطوير. وهنا سنضيف المجلد <code>/root/.cache/pip</code>، لنستخدمه لتخزين حزم <code>pip</code> في بيئة التطوير مؤقتًا.
</p>

<p>
	الآن علينا نشر تطبيق بايثون إلى عنقود كوبرنيتس باستخدام الأمر التالي:
</p>

<pre class="ipsCode">okteto deploy
</pre>

<p>
	وسنحصل على الخرج التالي عند نجاح عملية النشر:
</p>

<pre class="ipsCode">Output
deployment.apps/my-app created
service/my-app created
ingress.networking.k8s.io/my-app created
 i  There are no available endpoints for 'Okteto'.
    Follow this link to know more about how to create public endpoints for your application:
    https://www.okteto.com/docs/cloud/ssl/
 ✓  Development environment 'Okteto' successfully deployed
 i  Run 'okteto up' to activate your development container
</pre>

<p>
	ثم نحدّث لوحة تحكم Okteto من المتصفح لتعاين التطبيق بعد نشره:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="168976" href="https://academy.hsoub.com/uploads/monthly_2025_03/python-app-on-okteto-dashboard.png.0c3fc917bb6f8f14a3983c4bf69c0307.png" rel=""><img alt="python app on okteto dashboard" class="ipsImage ipsImage_thumbnailed" data-fileid="168976" data-unique="v6y5n6k85" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_03/python-app-on-okteto-dashboard.thumb.png.55a8244801db73159f440f8327a6a0c2.png"> </a>
</p>

<p>
	كما يمكننا الوصول إلى التطبيق من الرابط التالي: <a href="https://my-app-okteto-admin.okteto.example.com." ipsnoembed="true" rel="external nofollow">https://my-app-okteto-admin.okteto.example.com.</a>
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="168975" href="https://academy.hsoub.com/uploads/monthly_2025_03/python-application.png.a85a7e4a9a3a04f4947a003c3672e050.png" rel=""><img alt="python application" class="ipsImage ipsImage_thumbnailed" data-fileid="168975" data-unique="9wnj953cd" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_03/python-application.thumb.png.8cc834b4197455df0fd918b654186eda.png"> </a>
</p>

<h2 id="-2">
	تطوير تطبيق بايثون في منصة كوبرنتس مباشرة
</h2>

<p>
	سنستخدم الأمر <code>okteto up</code> لنشر التطبيق مباشرةً على كوبرنتس، إذ يزامن هذا الأمر الشيفرة المحلية مع بيئة التطوير. وبإمكاننا تعديل الشيفرة باستخدام أي بيئة تطوير أو <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AD%D8%B1%D8%B1-%D8%A3%D9%83%D9%88%D8%A7%D8%AF-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86/" rel="">محرر أكواد</a> على جهازنا، وستجري مزامنة التغييرات تلقائيًا مع بيئة التطوير في <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%AA%D8%B9%D8%B1%D9%91%D9%81-%D8%B9%D9%84%D9%89-%D9%83%D9%88%D8%A8%D8%B1%D9%86%D9%8A%D8%AA%D8%B3-kubernetes-r815/" rel="">كوبرنيتس</a>.
</p>

<p>
	علينا أولًا تشغيل بيئة التطوير باستخدام Okteto:
</p>

<pre class="ipsCode">okteto up
</pre>

<p>
	يُنشئ هذا الأمر بيئة تطوير وفقًا للإعدادات الموجودة في ملف <code>okteto.yaml</code>:
</p>

<pre class="ipsCode">Output
i  Using okteto-admin @ okteto.okteto.example.com as context
 i  'Okteto' was already deployed. To redeploy run 'okteto deploy' or 'okteto up --deploy'
 i  Build section is not defined in your okteto manifest
 ✓  Persistent volume successfully attached
 ✓  Images successfully pulled
 ✓  Files synchronized
    Context:   okteto.okteto.example.com
    Namespace: okteto-admin
    Name:      my-app
    Reverse:   9000 &lt;- 9000
root@my-app-okteto-7767588c8d-ndzj7:/app# 
</pre>

<p>
	نشغّل بعدها تطبيق بايثون:
</p>

<pre class="ipsCode">python3 app.py 
</pre>

<p>
	وسنحصل على الخرج التالي:
</p>

<pre class="ipsCode">* Serving Flask app 'app'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://10.244.97.92:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 126-620-675
</pre>

<p>
	ثم نعدّل ملف التطبيق <code>app.py</code>:
</p>

<pre class="ipsCode">nano app.py
</pre>

<p>
	وعدّل السطر التالي:
</p>

<pre class="ipsCode">   return "Hello, This is a simple Python App Deployed on Kubernetes"
</pre>

<p>
	نحفظ الملف ثم نغلقه، وستٌطبَق التعديلات على الشيفرة البرمجية تلقائيًا في كوبرنتس. ثم نعيد تحميل صفحة التطبيق من متصفح الويب لتعاين التطبيق بعد التعديل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="168973" href="https://academy.hsoub.com/uploads/monthly_2025_03/modified-python-application.png.70f7cf5d0ef8b4e969cb495f602b9173.png" rel=""><img alt="modified python application" class="ipsImage ipsImage_thumbnailed" data-fileid="168973" data-unique="7mlfepqvc" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2025_03/modified-python-application.thumb.png.c48fa82613a4fc4f9ce0b9abfaa0b1f2.png"> </a>
</p>

<h2 id="-3">
	الخاتمة
</h2>

<p>
	تعلمنا في هذا المقال كيفية إنشاء تطبيق بايثون بسيط وكيفية نشرته ضمن عناقيد كوبرنتس باستخدام منصة Okteto التي تتيح خاصية مزامنة التعديلات على الشيفرة المحلية مع بيئة التطوير، كما تسمح لنا بالتركيز على بناء تطبيقات بايثون عالية الجودة دون القلق حيال بنية كوبرنتس المعقّدة.
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-deploy-python-application-on-kubernetes-with-okteto" rel="external nofollow">How to Deploy Python Application on Kubernetes with Okteto</a> لكاتبيه hitjethva وEasha Abid.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D9%85%D9%86-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1622/" rel="">إنشاء تطبيق ويب باستخدام إطار عمل فلاسك Flask من لغة بايثون</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A2%D9%85%D9%86-%D9%88%D9%82%D8%A7%D8%A8%D9%84-%D9%84%D9%84%D8%AA%D9%88%D8%B3%D9%8A%D8%B9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D8%AA%D8%B3-kubernetes-r662/" rel="">نشر تطبيق جانغو آمن وقابل للتوسيع باستخدام كوبيرنتس Kubernetes</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87%D8%A7-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r714/" rel="">تثبيت بايثون 3 وإعداد بيئتها البرمجية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D8%AA%D8%B9%D8%B1%D9%91%D9%81-%D8%B9%D9%84%D9%89-%D9%83%D9%88%D8%A8%D8%B1%D9%86%D9%8A%D8%AA%D8%B3-kubernetes-r815/" rel="">تعرّف على كوبرنيتس Kubernetes</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">838</guid><pubDate>Sat, 01 Mar 2025 12:00:00 +0000</pubDate></item><item><title>&#x623;&#x62A;&#x645;&#x62A;&#x629; &#x646;&#x634;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x627;&#x644;&#x648;&#x64A;&#x628; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; GitHub Webhooks</title><link>https://academy.hsoub.com/devops/deployment/%D8%A3%D8%AA%D9%85%D8%AA%D8%A9-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-github-webhooks-r836/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2025_02/webhooksGitHub_.png.6fa651ee0affcf3c402960b2eb27c539.png" /></p>
<p>
	تعد عملية نشر نسخ instances من تطبيق ويب يدويًا على خادم أو أكثر عملية رتيبة وتستغرق الكثير من الوقت، لكن ببذل جهد بسيط يمكنك جعل عملية نشر تطبيق الويب مؤتمتة دون الحاجة لتدخل يدوي. سنسلط الضوء في هذا المقال على طريقة بسيطة لأتمتة نشر تطبيقات الويب باستخدام كل من <a href="https://academy.hsoub.com/questions/3480-%D9%85%D8%A7-%D9%87%D9%88-web-hook-%D9%88%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D8%A7%D8%AA%D9%87%D8%9F/" rel="">خطافات الويب</a> webhooks وحزم البناء buildpacks وملفات Procfiles.
</p>

<p>
	لطالما كانت عملية تشغيل تطبيقات الويب على الخادم ودفع push أية تحديثات لاحقة إليه عملية مضجرة وشاقة لمطوري الويب، مما يدفعهم للاستعانة بأحد مزودي المنصة كخدمة Platform as a service أو PaaS اختصارًا فهذه المنصات توفر لهم بيئة متكاملة لتطوير وتشغيل التطبيقات دون الحاجة إلى إدارة البنية التحتية، كما تُسهّل نشر التطبيقات دون أن يضطر المطوّر للاهتمام بإجراءات حجز الخادم وإعداده مقابل زيادة طفيفة في التكلفة ونقص بسيط في المرونة.
</p>

<p>
	لكن قد يحتاج المطور أو <a href="https://academy.hsoub.com/devops/general/%D8%AA%D8%B9%D9%84%D9%85-devops/" rel="">مسؤول DevOps</a> لنشر التطبيقات على خوادم مدارة دون الاستعانة بطرف ثالث. وفي هذه الحالة يفضل أن يبتكر أداة بسيطة لأتمتة عملية النشر، وهو أمر غير معقّد، ويغدو أسهل كلما كانت المتطلبات أبسط، لذا سنتعلم في مقال اليوم كيف نطور هذه الأداة وندعها تنفذ الأجزاء الروتينية والمملة من عمليات نشر تطبيقات الويب نيابة عنا ونوفر وقتنا وجهدنا.
</p>

<p>
	<strong>ملاحظة</strong>: تختلف خطوات أتمتة نشر موقع مبني بلغة PHP عن خطوات نشر تطبيق Node.js. كما توجد حلول جاهزة أشمل تدعى حزم البناء buildpacks مثل دوكو Dokku تناسب مجموعة أوسع من التقنيات. سنشرح في هذا المقال أداة بسيطة لأتمتة عمليات نشر تطبيقات الويب الخاصة بنا، وذلك باستخدام خطافات الويب webhooks وحزم البناء buildpacks وملفات Procfiles في غيت هب GitHub. ويمكن الاطلاع على الشيفرة المصدرية source code للنموذج الأولي منها على <a href="https://github.com/hjr265/toptal-hopper" rel="external nofollow">موقع غيت هب GitHub</a>. <strong>ملاحظة</strong>: تستخدم حزم البناء buildpacks لتحديد كيفية تكوين بيئة التطبيق قبل نشره، وتحدد ملفات Procfiles النصية أنواع العمليات في التطبيق، مما يسهل تشغيلها تلقائياً. وسنشرحها مفصلًا في الفقرات التالية.
</p>

<h2 id="">
	بناء تطبيق الويب
</h2>

<p>
	ستكون أولى خطواتنا كتابة برنامج بسيط <a href="https://academy.hsoub.com/files/41-%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A8%D9%84%D8%BA%D8%A9-go/" rel="">بلغة غو</a> Go، لا تقلق إذا كنت لا تألف العمل بلغة غو GO فبنية الشيفرة البرمجية المستخدمة بسيطة نسبيًا وستجدها سهلة الفهم. ويمكن كتابة البرنامج باللغة التي تناسبنا. قبل البدء لنتأكد من تثبيت Go distribution على نظام التشغيل باتباع الخطوات المذكورة <a href="https://go.dev/doc/install" rel="external nofollow">الموقع الرسمي</a>، بعدها يفضل تنزيل الشيفرة المصدرية لأداتنا باستنساخ clone <a href="https://github.com/hjr265/toptal-hopper" rel="external nofollow">مستودع غيت هب</a> GitHub repository، فهذا سيسهّل متابعة تتمة الخطوات، فمقاطع الشيفرة البرمجية في المستودع مسمّاة بنفس أسماء الملفات التي سنحددها في المقال. إن استخدام برنامج غو Go دونًا عن غيره لكتابة برنامجنا سيحدّ من حاجتنا للاستعانة باعتماديات dependencies خارجية، ففي حالتنا لن نحتاج لتشغيل برنامج غو على الخادم إلا لتثبيت غيت Git وباش <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%8A-bash%D8%9F-r794/" rel="">Bash</a>، كما أن البرنامج لا يستهلك الكثير من موارد الجهاز مثل الذاكرة رام RAM والمعالج CPU عندما نضبطه بطريقة صحيحة.
</p>

<h2 id="webhooks">
	ما هي خطافات الويب Webhooks في غيت هب
</h2>

<p>
	تمكّننا خطافات الويب في غيت هب من ضبط المستودع GitHub repository ليصدّر الأحداث events كلما طرأ تغيير ضمن المستودع أو أجرى مستخدم إجراء معينًا في المستودع المستضاف، وهذا يتيح للمستخدمين التسجيل subscribe في هذه الأحداث وتلقي إشعارات بمختلف الأحداث التي تجري في مستودعنا وما يتعلق به من خلال استدعاءات الروابط URL invocations حيث سيستدعى عنوان URL معين تلقائيًا عندما يقع حدث معين في المستودع.
</p>

<h3 id="webhooks-1">
	خطوات إنشاء Webhooks في غيت هب
</h3>

<p>
	يعد إنشاء خطاف ويب Webhooks عملية بسيطة سنلخصها في الخطوات التالية:
</p>

<ol>
	<li>
		نفتح صفحة الإعدادات settings في مستودعنا
	</li>
	<li>
		نضغط على خطافات الويب والخدمات Webhooks &amp; Services في قائمة الخيارات
	</li>
	<li>
		نضغط على زر إضافة خطاف ويب Add webhook.
	</li>
	<li>
		ندخل رابط، ونضيف إن شئنا رمزًا سريًا secret يتيح للمستقبل التحقق من حمولة البيانات الواردة payload
	</li>
	<li>
		نحدد الاختيارات الأخرى في الصفحة حسب متطلباتنا
	</li>
	<li>
		أخيرًا، نضغط على الزر الأخضر Add webhook.
	</li>
</ol>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="167016" href="https://academy.hsoub.com/uploads/monthly_2025_02/001--webhook.png.27e240bca19e9c50ee7cc042261abca6.png" rel=""><img alt="001 إضافة webhook" class="ipsImage ipsImage_thumbnailed" data-fileid="167016" data-ratio="88.00" data-unique="gjdz9a653" style="width: 400px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2025_02/001--webhook.png.27e240bca19e9c50ee7cc042261abca6.png"> </a>
</p>

<p>
	يقدم غيت هب توثيقًا مستفيضًا <a href="https://developer.github.com/webhooks/" rel="external nofollow">لخطافات الويب</a> وجميع التفاصيل المرتبطة بها، لكن الجزء الذي سنحتاج إليه في مقالنا هذا هو <a href="https://developer.github.com/v3/activity/events/types/#pushevent" rel="external nofollow">حدث الدفع</a> push event الذي يُصدَّر كلما أجرى مستخدم عملية دفع إلى أي فرع branch في المستودع repository.
</p>

<h2 id="buildpacks">
	حزم البناء Buildpacks
</h2>

<p>
	أصبحت حزم البناء جزءًا لا يتجزأ من عملية نشر التطبيقات في وقتنا هذا، ويستخدمها العديد من مزودي المنصات كخدمة PaaS. حيث تتيح لنا حزم البناء تحديد كيفية ضبط مكدس التقنيات stack التي سنحتاجها لتشغيل تطبيقنا قبل أن ننشره، وكتابتها بغاية السهولة، ففي معظم الأحيان لن نحتاج حتى لكتابتها بنفسنا حيث تستطيع بإجراء بحث سريع على الإنترنت إيجاد حزم بناء جاهزة يمكننا استخدامها في عملية نشر تطبيقنا دون الحاجة إلى تعديلها. فمثلًا تقدم منصة Heroku <a href="https://devcenter.heroku.com/articles/buildpack-api" rel="external nofollow">توثيقًا شاملًا عن بنية حزم البناء</a> و<a href="https://devcenter.heroku.com/articles/buildpacks#default-buildpacks" rel="external nofollow">قائمة تضم أكثر حزم البناء المستخدمة وأفضلها بنيةً</a>. تستخدم أداة الأتمتة التي نبنيها سكربت تصريف compile script لتجهيز التطبيق قبل تشغيله. ولا بد أن ننوه أننا لن نستفيض في كتابة حزم البناء، وإنما سنفترض أن سكربتات حزم البناء buildpack scripts الجاهزة تتوافق مع بيئة سطر الأوامر باش <a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%A7-%D9%87%D9%8A-bash%D8%9F-r794/" rel="">Bash</a>، وأنها ستُشغَّل على نظام تشغيل أبونتو Ubuntu مثُبّت حديثًا ولم يطرأ عليه أي تعديل، ويمكن لاحقًا التوسّع أكثر فيها حسب متطلبات عملنا.
</p>

<h2 id="procfiles">
	ملفات Procfiles
</h2>

<p>
	هي ملفات نصية بسيطة تمكّننا من تحديد مختلف أنواع الإجرائيات processes المستخدمة في تطبيقنا، وتُستخدم عادةً في معظم التطبيقات البسيطة إجرائية ويب web واحدة فقط تعالج طلبات بروتوكول HTTP. لكتابة هذه الملفات ما علينا سوى تحديد نوع إجرائية واحد في كل سطر وذلك بكتابة اسمها متبوعًا بنقطة مزدوجة <code>:</code> متبوعة بالأمر الذي سيولّد spawn هذه الإجرائية كمل يلي:
</p>

<pre class="ipsCode">&lt;type&gt;: &lt;command&gt;
</pre>

<p>
	على سبيل المثال، إذا كان تطبيقنا مكتوب بنود جي إس Node.js، علينا تنفيذ الأمر <code>node index.js</code> لتشغيل خادم الويب. يمكن إذًا أن ننشئ ملف Procfile في المجلد الأساسي للشيفرة البرمجية ونسميه Procfile ونكتب ما يلي ضمنه:
</p>

<pre class="ipsCode">web: node index.js
</pre>

<p>
	نلزم تطبيقاتنا بتحديد أنواع الإجرائيات في ملفات Procfiles لنتمكن من تشغيلها تلقائيًا بعد سحب pulling الشيفرة البرمجية.
</p>

<h2 id="-1">
	معالجة الأحداث
</h2>

<p>
	نحتاج في برنامجنا إلى إضافة خادم HTTP مهمته استقبال طلبات POST الواردة من غيت هب، لذا علينا أن نخصص مسار رابط URL path لمعالجة هذه الطلبات. ونقدم في النموذج التالي مثالًا عن بنية الدالة function التي ستعالج الحمولات payloads الواردة من هذا النوع:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7582_10" style=""><span class="com">// hook.go</span><span class="pln">
type </span><span class="typ">HookOptions</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="typ">App</span><span class="pln">    </span><span class="pun">*</span><span class="typ">App</span><span class="pln">
    </span><span class="typ">Secret</span><span class="pln"> string
</span><span class="pun">}</span><span class="pln">


func </span><span class="typ">NewHookHandler</span><span class="pun">(</span><span class="pln">o </span><span class="pun">*</span><span class="typ">HookOptions</span><span class="pun">)</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">Handler</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">HandlerFunc</span><span class="pun">(</span><span class="pln">func</span><span class="pun">(</span><span class="pln">w http</span><span class="pun">.</span><span class="typ">ResponseWriter</span><span class="pun">,</span><span class="pln"> r </span><span class="pun">*</span><span class="pln">http</span><span class="pun">.</span><span class="typ">Request</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        evName </span><span class="pun">:=</span><span class="pln"> r</span><span class="pun">.</span><span class="typ">Header</span><span class="pun">.</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"X-Github-Event"</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> evName </span><span class="pun">!=</span><span class="pln"> </span><span class="str">"push"</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            log</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Ignoring '%s' event"</span><span class="pun">,</span><span class="pln"> evName</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">


        body</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> ioutil</span><span class="pun">.</span><span class="typ">ReadAll</span><span class="pun">(</span><span class="pln">r</span><span class="pun">.</span><span class="typ">Body</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            http</span><span class="pun">.</span><span class="typ">Error</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Internal Server Error"</span><span class="pun">,</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">StatusInternalServerError</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">


        </span><span class="kwd">if</span><span class="pln"> o</span><span class="pun">.</span><span class="typ">Secret</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> </span><span class="str">""</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            ok </span><span class="pun">:=</span><span class="pln"> </span><span class="kwd">false</span><span class="pln">
            </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> sig </span><span class="pun">:=</span><span class="pln"> range strings</span><span class="pun">.</span><span class="typ">Fields</span><span class="pun">(</span><span class="pln">r</span><span class="pun">.</span><span class="typ">Header</span><span class="pun">.</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"X-Hub-Signature"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">strings</span><span class="pun">.</span><span class="typ">HasPrefix</span><span class="pun">(</span><span class="pln">sig</span><span class="pun">,</span><span class="pln"> </span><span class="str">"sha1="</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    </span><span class="kwd">continue</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
                sig </span><span class="pun">=</span><span class="pln"> strings</span><span class="pun">.</span><span class="typ">TrimPrefix</span><span class="pun">(</span><span class="pln">sig</span><span class="pun">,</span><span class="pln"> </span><span class="str">"sha1="</span><span class="pun">)</span><span class="pln">
                mac </span><span class="pun">:=</span><span class="pln"> hmac</span><span class="pun">.</span><span class="typ">New</span><span class="pun">(</span><span class="pln">sha1</span><span class="pun">.</span><span class="typ">New</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[]</span><span class="pln">byte</span><span class="pun">(</span><span class="pln">o</span><span class="pun">.</span><span class="typ">Secret</span><span class="pun">))</span><span class="pln">
                mac</span><span class="pun">.</span><span class="typ">Write</span><span class="pun">(</span><span class="pln">body</span><span class="pun">)</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> sig </span><span class="pun">==</span><span class="pln"> hex</span><span class="pun">.</span><span class="typ">EncodeToString</span><span class="pun">(</span><span class="pln">mac</span><span class="pun">.</span><span class="typ">Sum</span><span class="pun">(</span><span class="pln">nil</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    ok </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
                    </span><span class="kwd">break</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
            </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">ok </span><span class="pun">{</span><span class="pln">
                log</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Ignoring '%s' event with incorrect signature"</span><span class="pun">,</span><span class="pln"> evName</span><span class="pun">)</span><span class="pln">
                </span><span class="kwd">return</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">


        ev </span><span class="pun">:=</span><span class="pln"> github</span><span class="pun">.</span><span class="typ">PushEvent</span><span class="pun">{}</span><span class="pln">
        err </span><span class="pun">=</span><span class="pln"> json</span><span class="pun">.</span><span class="typ">Unmarshal</span><span class="pun">(</span><span class="pln">body</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">ev</span><span class="pun">)</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            log</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Ignoring '%s' event with invalid payload"</span><span class="pun">,</span><span class="pln"> evName</span><span class="pun">)</span><span class="pln">
            http</span><span class="pun">.</span><span class="typ">Error</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Bad Request"</span><span class="pun">,</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">StatusBadRequest</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">


        </span><span class="kwd">if</span><span class="pln"> ev</span><span class="pun">.</span><span class="typ">Repo</span><span class="pun">.</span><span class="typ">FullName</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> nil </span><span class="pun">||</span><span class="pln"> </span><span class="pun">*</span><span class="pln">ev</span><span class="pun">.</span><span class="typ">Repo</span><span class="pun">.</span><span class="typ">FullName</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> o</span><span class="pun">.</span><span class="typ">App</span><span class="pun">.</span><span class="typ">Repo</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            log</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Ignoring '%s' event with incorrect repository name"</span><span class="pun">,</span><span class="pln"> evName</span><span class="pun">)</span><span class="pln">
            http</span><span class="pun">.</span><span class="typ">Error</span><span class="pun">(</span><span class="pln">w</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Bad Request"</span><span class="pun">,</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">StatusBadRequest</span><span class="pun">)</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">


        log</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Handling '%s' event for %s"</span><span class="pun">,</span><span class="pln"> evName</span><span class="pun">,</span><span class="pln"> o</span><span class="pun">.</span><span class="typ">App</span><span class="pun">.</span><span class="typ">Repo</span><span class="pun">)</span><span class="pln">


        err </span><span class="pun">=</span><span class="pln"> o</span><span class="pun">.</span><span class="typ">App</span><span class="pun">.</span><span class="typ">Update</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نبدأ بالتحقق من نوع الحدث event الذي ولّد هذه الحمولة payload، وبما أن حدث الدفع push هو الحدث الوحيد الذي يهمنا يمكننا تجاهل بقية الأحداث. لكن حتى لو ضبطنا خطاف الويب ليصدّر أحداث الدفع فقط، لا بد أن نتوقع استقبال نوع آخر على الأقل من الأحداث على نقطة اتصال خطافنا hook endpoint التي تمثل الرابط الذي تُرسَل إليه البيانات وهو الحدث <code>Ping</code>.
</p>

<p>
	الغاية من هذا الحدث التأكد من ضبط خطاف الويب بطريقة صحيحة على غيت هب. الخطوة التالية هي قراءة محتوى الطلب الوارد بأكمله، ثم حساب قيمة التشفير باستخدام خوارزمية التشفير HMAC-SHA1 والرمز السري secret ذاته الذي حددناه عند ضبط خطاف الويب، ومقارنتها بالبصمة signature المتضمَّنة في ترويسة الطلب للتحقق من صلاحية الحمولة payload الواردة، وفي حالتنا اخترنا تجاهل خطوة التحقق هذه إذا لم يكن الرمز السري محدَّدًا. 
</p>

<p>
	<strong>ملاحظة</strong>: ننصح بوضع حد أقصى لحجم البيانات الذي يمكن التعامل معه قبل قراءة محتوى الطلب كاملًا، لكننا لن نتطرق إلى هذا في مقالنا وسنركز على الجوانب الأساسية للأداة فقط. بعدها، نستخدم هيكل بيانات struct من مكتبة <a href="https://github.com/google/go-github" rel="external nofollow"> GitHub client library for Go</a> لتفريغ أو تحويل unmarshal حمولة البيانات الواردة فيها، وبما أننا نعلم أنه حدث دفع push فيمكننا استخدام <a href="https://godoc.org/github.com/google/go-github/github#PushEvent" rel="external nofollow">بنية حدث الدفع</a>. ثم نستخدم مكتبة التشفير القياسية بصيغة Json لتفريغ الحمولة إلى نسخة من البنية، ستنفذ بعدها بعض عمليات التحقق من الصحة وفي حال خلوها من أية مشكلة ستُستدعى الدالة التي تبدأ بتحديث تطبيقنا.
</p>

<h2 id="-2">
	تحديث التطبيق
</h2>

<p>
	فور وصول إشعار بالحدث إلى طرفية خطاف الويب لدينا يمكننا البدء بتحديث تطبيقنا. سنتناول في هذا المقال تطبيق بسيط نوعًا ما لهذه الآلية الهدف منه التعرف على أساسيات إجراء عمليات أتمتة النشر، ويمكنك دومًا التوسّع فيها في حال رغبتك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="167017" href="https://academy.hsoub.com/uploads/monthly_2025_02/003----.png.b343b6d831ea50f61841cbc5d7db72b8.png" rel=""><img alt="003 التحقق من جاهزية المستودع" class="ipsImage ipsImage_thumbnailed" data-fileid="167017" data-ratio="83.25" data-unique="tcfynt3qt" style="width: 400px; height: auto;" width="400" src="https://academy.hsoub.com/uploads/monthly_2025_02/003----.png.b343b6d831ea50f61841cbc5d7db72b8.png"> </a>
</p>

<h2 id="-3">
	تهيئة المستودع المحلي
</h2>

<p>
	تبدأ هذه العملية بإجراء تحقق بسيط لنحدد إذا كانت هذه المرة الأولى التي نحاول نشر التطبيق فيها، وذلك بالتحقق من وجود مجلد المستودع المحلي local repository، فإذا لم يكن موجودًا نبدأ أولى خطواتنا بتهيئة initialize المستودع المحلي لدينا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7582_13" style=""><span class="com">// app.go</span><span class="pln">


func </span><span class="pun">(</span><span class="pln">a </span><span class="pun">*</span><span class="typ">App</span><span class="pun">)</span><span class="pln"> initRepo</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">.</span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Initializing repository"</span><span class="pun">)</span><span class="pln">


    err </span><span class="pun">:=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">MkdirAll</span><span class="pun">(</span><span class="pln">a</span><span class="pun">.</span><span class="pln">repoDir</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0755</span><span class="pun">)</span><span class="pln">
    </span><span class="com">// Check err</span><span class="pln">


    cmd </span><span class="pun">:=</span><span class="pln"> exec</span><span class="pun">.</span><span class="typ">Command</span><span class="pun">(</span><span class="str">"git"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--git-dir="</span><span class="pun">+</span><span class="pln">a</span><span class="pun">.</span><span class="pln">repoDir</span><span class="pun">,</span><span class="pln"> </span><span class="str">"init"</span><span class="pun">)</span><span class="pln">
    cmd</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Run</span><span class="pun">()</span><span class="pln">
    </span><span class="com">// Check err</span><span class="pln">


    cmd </span><span class="pun">=</span><span class="pln"> exec</span><span class="pun">.</span><span class="typ">Command</span><span class="pun">(</span><span class="str">"git"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--git-dir="</span><span class="pun">+</span><span class="pln">a</span><span class="pun">.</span><span class="pln">repoDir</span><span class="pun">,</span><span class="pln"> </span><span class="str">"remote"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"add"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"origin"</span><span class="pun">,</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"git@github.com:%s.git"</span><span class="pun">,</span><span class="pln"> a</span><span class="pun">.</span><span class="typ">Repo</span><span class="pun">))</span><span class="pln">
    cmd</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Run</span><span class="pun">()</span><span class="pln">
    </span><span class="com">// Check err</span><span class="pln">


    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span></pre>

<p>
	يمكننا تهيئة المستودع المحلي لتطبيقنا بالخطوات التالية:
</p>

<ol>
	<li>
		ننشئ مجلدًا للمستودع المحلي local repository في حال عدم وجوده.
	</li>
	<li>
		نستخدم بالأمر <code>git init</code> لإنشاء مستودع فارغ bare repository
	</li>
	<li>
		نضيف رابطًا للمستودع البعيد remote repository إلى مستودعنا المحلي ونسميه <code>origin</code>. بعد تهيئة المستودع سيكون جلب التغييرات عملية بسيطة.
	</li>
</ol>

<h2 id="-4">
	جلب التغييرات
</h2>

<p>
	لجلب التغييرات من المستودع البعيد لن نحتاج سوى لاستدعاء أمر واحد، وذلك وفق ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7582_15" style=""><span class="com">// app.go</span><span class="pln">


func </span><span class="pun">(</span><span class="pln">a </span><span class="pun">*</span><span class="typ">App</span><span class="pun">)</span><span class="pln"> fetchChanges</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">.</span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Fetching changes"</span><span class="pun">)</span><span class="pln">


    cmd </span><span class="pun">:=</span><span class="pln"> exec</span><span class="pun">.</span><span class="typ">Command</span><span class="pun">(</span><span class="str">"git"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--git-dir="</span><span class="pun">+</span><span class="pln">a</span><span class="pun">.</span><span class="pln">repoDir</span><span class="pun">,</span><span class="pln"> </span><span class="str">"fetch"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-f"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"origin"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"master:master"</span><span class="pun">)</span><span class="pln">
    cmd</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Run</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إن تنفيذ أمر جلب التغييرات <code>git fetch</code> إلى مستودعنا المحلي بهذه الطريقة يجنبنا مشاكل عجز Git عن التقدم السريع fast-forward في بعض الحالات. وصحيح أننا لا ننصح بالاعتماد على عمليات الجلب الإجبارية، لكن إن احتجنا إلى تنفيذ عملية دفع إجبارية إلى مستودعنا البعيد يمكن اتباع هذه الطريقة.
</p>

<h2 id="-5">
	تصريف التطبيق
</h2>

<p>
	بما أننا نستخدم سكربتات من حزم البناء buildpacks لتصريف compile تطبيقاتنا التي ننشرها، تتمثل مهمتنا بتنفيذ الخطوات البسيطة التالية من أجل تحضير وتصريف التطبيق بشكل آلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7582_17" style=""><span class="com">// app.go</span><span class="pln">


func </span><span class="pun">(</span><span class="pln">a </span><span class="pun">*</span><span class="typ">App</span><span class="pun">)</span><span class="pln"> compileApp</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">.</span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Compiling application"</span><span class="pun">)</span><span class="pln">


    _</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stat</span><span class="pun">(</span><span class="pln">a</span><span class="pun">.</span><span class="pln">appDir</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">os</span><span class="pun">.</span><span class="typ">IsNotExist</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        err </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">RemoveAll</span><span class="pun">(</span><span class="pln">a</span><span class="pun">.</span><span class="pln">appDir</span><span class="pun">)</span><span class="pln">
        </span><span class="com">// Check err</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">MkdirAll</span><span class="pun">(</span><span class="pln">a</span><span class="pun">.</span><span class="pln">appDir</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0755</span><span class="pun">)</span><span class="pln">
    </span><span class="com">// Check err</span><span class="pln">
    cmd </span><span class="pun">:=</span><span class="pln"> exec</span><span class="pun">.</span><span class="typ">Command</span><span class="pun">(</span><span class="str">"git"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--git-dir="</span><span class="pun">+</span><span class="pln">a</span><span class="pun">.</span><span class="pln">repoDir</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--work-tree="</span><span class="pun">+</span><span class="pln">a</span><span class="pun">.</span><span class="pln">appDir</span><span class="pun">,</span><span class="pln"> </span><span class="str">"checkout"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-f"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"master"</span><span class="pun">)</span><span class="pln">
    cmd</span><span class="pun">.</span><span class="typ">Dir</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">appDir
    cmd</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Run</span><span class="pun">()</span><span class="pln">
    </span><span class="com">// Check err</span><span class="pln">


    buildpackDir</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> filepath</span><span class="pun">.</span><span class="typ">Abs</span><span class="pun">(</span><span class="str">"buildpack"</span><span class="pun">)</span><span class="pln">
    </span><span class="com">// Check err</span><span class="pln">


    cmd </span><span class="pun">=</span><span class="pln"> exec</span><span class="pun">.</span><span class="typ">Command</span><span class="pun">(</span><span class="str">"bash"</span><span class="pun">,</span><span class="pln"> filepath</span><span class="pun">.</span><span class="typ">Join</span><span class="pun">(</span><span class="pln">buildpackDir</span><span class="pun">,</span><span class="pln"> </span><span class="str">"bin"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"detect"</span><span class="pun">),</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">appDir</span><span class="pun">)</span><span class="pln">
    cmd</span><span class="pun">.</span><span class="typ">Dir</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> buildpackDir
    cmd</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Run</span><span class="pun">()</span><span class="pln">
    </span><span class="com">// Check err</span><span class="pln">


    cacheDir</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> filepath</span><span class="pun">.</span><span class="typ">Abs</span><span class="pun">(</span><span class="str">"cache"</span><span class="pun">)</span><span class="pln">
    </span><span class="com">// Check err</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">MkdirAll</span><span class="pun">(</span><span class="pln">cacheDir</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0755</span><span class="pun">)</span><span class="pln">
    </span><span class="com">// Check err</span><span class="pln">


    cmd </span><span class="pun">=</span><span class="pln"> exec</span><span class="pun">.</span><span class="typ">Command</span><span class="pun">(</span><span class="str">"bash"</span><span class="pun">,</span><span class="pln"> filepath</span><span class="pun">.</span><span class="typ">Join</span><span class="pun">(</span><span class="pln">buildpackDir</span><span class="pun">,</span><span class="pln"> </span><span class="str">"bin"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"compile"</span><span class="pun">),</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">appDir</span><span class="pun">,</span><span class="pln"> cacheDir</span><span class="pun">)</span><span class="pln">
    cmd</span><span class="pun">.</span><span class="typ">Dir</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">appDir
    cmd</span><span class="pun">.</span><span class="typ">Stdout</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stdout</span><span class="pln">
    cmd</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Run</span><span class="pun">()</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لنشرح الكود أعلاه خطوة بخطوة:
</p>

<ul>
	<li>
		نبدأ بحذف المجلد السابق الخاص بتطبيقنا في حال وجوده، ثم ننشئ مجلدًا جديدًا ونسحب checkout محتويات الفرع الرئيسي master branch إليه
	</li>
	<li>
		نستخدم بعد ذلك سكريبت detect الموجود ضمن حزمة البناء التي ضبطناها لنحدد إذا كان باستطاعتنا معالجة التطبيق
	</li>
	<li>
		ننشئ مجلد لذاكرة التخزين المؤقتة ونسميه cache من أجل عملية تصريف حزمة البناء إذا دعت الحاجة لذلك، علمًا أن المجلد قد يكون موجودًا مسبقًا في حال أجرينا عمليات تصريف سابقة
	</li>
	<li>
		يمكننا في هذه المرحلة استدعاء سكربت التصريف المسمى compile من حزمة البناء وجعله يجهّز كل ما يحتاجه التطبيق قبل تشغيله عندما تُشغَّل حزم البناء بطريقة صحيحة فستتكمن من التعامل مع التخزين المؤقت caching وإعادة استخدام الموارد مسبقة التخزين
	</li>
</ul>

<h2 id="-6">
	إعادة تشغيل التطبيق
</h2>

<p>
	سنعمل في تطبيق أتمتة النشر الذي نعدّه وفق نهج إيقاف الإجرائيات القديمة قبل تشغيل عملية التصريف، وتشغيل الإجرائيات الجديدة عند اكتمالها، ونجد هذا في الشيفرة التالية. في حال رغبنا بتطوير هذا النموذج الأولي يمكن تعديله بحيث نضمن عدم حدوث أي فترة انقطاع downtime خلال عمليات التحديث.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7582_19" style=""><span class="com">// app.go</span><span class="pln">


func </span><span class="pun">(</span><span class="pln">a </span><span class="pun">*</span><span class="typ">App</span><span class="pun">)</span><span class="pln"> stopProcs</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">.</span><span class="typ">Print</span><span class="pun">(</span><span class="str">".. stopping processes"</span><span class="pun">)</span><span class="pln">


    </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> n </span><span class="pun">:=</span><span class="pln"> range a</span><span class="pun">.</span><span class="pln">nodes </span><span class="pun">{</span><span class="pln">
        err </span><span class="pun">:=</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">Stop</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> err
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">


    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span><span class="pln">


func </span><span class="pun">(</span><span class="pln">a </span><span class="pun">*</span><span class="typ">App</span><span class="pun">)</span><span class="pln"> startProcs</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">.</span><span class="typ">Print</span><span class="pun">(</span><span class="str">"Starting processes"</span><span class="pun">)</span><span class="pln">


    err </span><span class="pun">:=</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">readProcfile</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> err
    </span><span class="pun">}</span><span class="pln">


    </span><span class="kwd">for</span><span class="pln"> _</span><span class="pun">,</span><span class="pln"> n </span><span class="pun">:=</span><span class="pln"> range a</span><span class="pun">.</span><span class="pln">nodes </span><span class="pun">{</span><span class="pln">
        err </span><span class="pun">=</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">Start</span><span class="pun">()</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">return</span><span class="pln"> err
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">


    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span></pre>

<p>
	في هذا النموذج من برنامجنا نوقف مختلف الإجرائيات ونشغلها بالمرور على مصفوفة من العقد nodes، تكون فيها كل عقدة عبارة عن إجرائية تقابل إحدى نسخ التطبيق وفق الإعدادات المضبوطة قبل تشغيل أداة الأتمتة على الخادم. نراقب في أداتنا الحالة الأساسية لإجرائية كل عقدة، كما نحتفظ بملفات سجل log files عنها. قبل تشغيل جميع العقد يُسنَد إلى كل منها رقم منفذ فريد، بدءًا من رقم نحدده وفق ما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7582_21" style=""><span class="com">// node.go</span><span class="pln">


func </span><span class="typ">NewNode</span><span class="pun">(</span><span class="pln">app </span><span class="pun">*</span><span class="typ">App</span><span class="pun">,</span><span class="pln"> name string</span><span class="pun">,</span><span class="pln"> no </span><span class="typ">int</span><span class="pun">,</span><span class="pln"> port </span><span class="typ">int</span><span class="pun">)</span><span class="pln"> </span><span class="pun">(*</span><span class="typ">Node</span><span class="pun">,</span><span class="pln"> error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    logFile</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> os</span><span class="pun">.</span><span class="typ">OpenFile</span><span class="pun">(</span><span class="pln">filepath</span><span class="pun">.</span><span class="typ">Join</span><span class="pun">(</span><span class="pln">app</span><span class="pun">.</span><span class="pln">logsDir</span><span class="pun">,</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"%s.%d.txt"</span><span class="pun">,</span><span class="pln"> name</span><span class="pun">,</span><span class="pln"> no</span><span class="pun">)),</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">O_RDWR</span><span class="pun">|</span><span class="pln">os</span><span class="pun">.</span><span class="pln">O_CREATE</span><span class="pun">|</span><span class="pln">os</span><span class="pun">.</span><span class="pln">O_APPEND</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0666</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> nil</span><span class="pun">,</span><span class="pln"> err
    </span><span class="pun">}</span><span class="pln">


    n </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&amp;</span><span class="typ">Node</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">App</span><span class="pun">:</span><span class="pln">     app</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Name</span><span class="pun">:</span><span class="pln">    name</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">No</span><span class="pun">:</span><span class="pln">      no</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Port</span><span class="pun">:</span><span class="pln">    port</span><span class="pun">,</span><span class="pln">
        stateCh</span><span class="pun">:</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="typ">NextState</span><span class="pun">),</span><span class="pln">
        logFile</span><span class="pun">:</span><span class="pln"> logFile</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">


    go func</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            next </span><span class="pun">:=</span><span class="pln"> </span><span class="pun">&lt;-</span><span class="pln">n</span><span class="pun">.</span><span class="pln">stateCh
            </span><span class="kwd">if</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">State</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> next</span><span class="pun">.</span><span class="typ">State</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> next</span><span class="pun">.</span><span class="pln">doneCh </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
                    close</span><span class="pun">(</span><span class="pln">next</span><span class="pun">.</span><span class="pln">doneCh</span><span class="pun">)</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
                </span><span class="kwd">continue</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">


            </span><span class="kwd">switch</span><span class="pln"> next</span><span class="pun">.</span><span class="typ">State</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
            </span><span class="kwd">case</span><span class="pln"> </span><span class="typ">StateUp</span><span class="pun">:</span><span class="pln">
                log</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Starting process %s.%d"</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">No</span><span class="pun">)</span><span class="pln">


                cmd </span><span class="pun">:=</span><span class="pln"> exec</span><span class="pun">.</span><span class="typ">Command</span><span class="pun">(</span><span class="str">"bash"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"-c"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"for f in .profile.d/*; do source $f; done; "</span><span class="pun">+</span><span class="pln">n</span><span class="pun">.</span><span class="typ">Cmd</span><span class="pun">)</span><span class="pln">
                cmd</span><span class="pun">.</span><span class="typ">Env</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> append</span><span class="pun">(</span><span class="pln">cmd</span><span class="pun">.</span><span class="typ">Env</span><span class="pun">,</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"HOME=%s"</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">App</span><span class="pun">.</span><span class="pln">appDir</span><span class="pun">))</span><span class="pln">
                cmd</span><span class="pun">.</span><span class="typ">Env</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> append</span><span class="pun">(</span><span class="pln">cmd</span><span class="pun">.</span><span class="typ">Env</span><span class="pun">,</span><span class="pln"> fmt</span><span class="pun">.</span><span class="typ">Sprintf</span><span class="pun">(</span><span class="str">"PORT=%d"</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">Port</span><span class="pun">))</span><span class="pln">
                cmd</span><span class="pun">.</span><span class="typ">Env</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> append</span><span class="pun">(</span><span class="pln">cmd</span><span class="pun">.</span><span class="typ">Env</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">App</span><span class="pun">.</span><span class="typ">Env</span><span class="pun">...)</span><span class="pln">
                cmd</span><span class="pun">.</span><span class="typ">Dir</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">App</span><span class="pun">.</span><span class="pln">appDir
                cmd</span><span class="pun">.</span><span class="typ">Stdout</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">logFile
                cmd</span><span class="pun">.</span><span class="typ">Stderr</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">logFile
                err </span><span class="pun">:=</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Start</span><span class="pun">()</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
                    log</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Process %s.%d exited"</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">No</span><span class="pun">)</span><span class="pln">
                    n</span><span class="pun">.</span><span class="typ">State</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="typ">StateUp</span><span class="pln">


                </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    n</span><span class="pun">.</span><span class="typ">Process</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Process</span><span class="pln">
                    n</span><span class="pun">.</span><span class="typ">State</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="typ">StateUp</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">


                </span><span class="kwd">if</span><span class="pln"> next</span><span class="pun">.</span><span class="pln">doneCh </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
                    close</span><span class="pun">(</span><span class="pln">next</span><span class="pun">.</span><span class="pln">doneCh</span><span class="pun">)</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">


                go func</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
                    err </span><span class="pun">:=</span><span class="pln"> cmd</span><span class="pun">.</span><span class="typ">Wait</span><span class="pun">()</span><span class="pln">
                    </span><span class="kwd">if</span><span class="pln"> err </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
                        log</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Process %s.%d exited"</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">No</span><span class="pun">)</span><span class="pln">
                        n</span><span class="pun">.</span><span class="pln">stateCh </span><span class="pun">&lt;-</span><span class="pln"> </span><span class="typ">NextState</span><span class="pun">{</span><span class="pln">
                            </span><span class="typ">State</span><span class="pun">:</span><span class="pln"> </span><span class="typ">StateDown</span><span class="pun">,</span><span class="pln">
                        </span><span class="pun">}</span><span class="pln">
                    </span><span class="pun">}</span><span class="pln">
                </span><span class="pun">}()</span><span class="pln">


            </span><span class="kwd">case</span><span class="pln"> </span><span class="typ">StateDown</span><span class="pun">:</span><span class="pln">
                log</span><span class="pun">.</span><span class="typ">Printf</span><span class="pun">(</span><span class="str">"Stopping process %s.%d"</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">Name</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">No</span><span class="pun">)</span><span class="pln">


                </span><span class="kwd">if</span><span class="pln"> n</span><span class="pun">.</span><span class="typ">Process</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
                    n</span><span class="pun">.</span><span class="typ">Process</span><span class="pun">.</span><span class="typ">Kill</span><span class="pun">()</span><span class="pln">
                    n</span><span class="pun">.</span><span class="typ">Process</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> nil
                </span><span class="pun">}</span><span class="pln">


                n</span><span class="pun">.</span><span class="typ">State</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="typ">StateDown</span><span class="pln">
                </span><span class="kwd">if</span><span class="pln"> next</span><span class="pun">.</span><span class="pln">doneCh </span><span class="pun">!=</span><span class="pln"> nil </span><span class="pun">{</span><span class="pln">
                    close</span><span class="pun">(</span><span class="pln">next</span><span class="pun">.</span><span class="pln">doneCh</span><span class="pun">)</span><span class="pln">
                </span><span class="pun">}</span><span class="pln">
            </span><span class="pun">}</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}()</span><span class="pln">


    </span><span class="kwd">return</span><span class="pln"> n</span><span class="pun">,</span><span class="pln"> nil
</span><span class="pun">}</span><span class="pln">


func </span><span class="pun">(</span><span class="pln">n </span><span class="pun">*</span><span class="typ">Node</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Start</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    n</span><span class="pun">.</span><span class="pln">stateCh </span><span class="pun">&lt;-</span><span class="pln"> </span><span class="typ">NextState</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">State</span><span class="pun">:</span><span class="pln"> </span><span class="typ">StateUp</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span><span class="pln">


func </span><span class="pun">(</span><span class="pln">n </span><span class="pun">*</span><span class="typ">Node</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Stop</span><span class="pun">()</span><span class="pln"> error </span><span class="pun">{</span><span class="pln">
    doneCh </span><span class="pun">:=</span><span class="pln"> make</span><span class="pun">(</span><span class="pln">chan </span><span class="typ">int</span><span class="pun">)</span><span class="pln">
    n</span><span class="pun">.</span><span class="pln">stateCh </span><span class="pun">&lt;-</span><span class="pln"> </span><span class="typ">NextState</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">State</span><span class="pun">:</span><span class="pln">  </span><span class="typ">StateDown</span><span class="pun">,</span><span class="pln">
        doneCh</span><span class="pun">:</span><span class="pln"> doneCh</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">&lt;-</span><span class="pln">doneCh
    </span><span class="kwd">return</span><span class="pln"> nil
</span><span class="pun">}</span></pre>

<p>
	قد تبدو كتابة هذه الشيفرة البرمجية للوهلة الأولى أكثر الخطوات تعقيدًا حتى الآن، لذا دعنا نبسطها بتقسيمها إلى 4 أجزاء: يمثل مضمون دالة <code>NewNode</code> أول جزئين، فعندما تُستدعَى هذه الدالة تهيئ populate نسخة من هيكل بيانات <code>Node</code> بإعدادات محددة وتولّد برنامجًا routine بلغة غو Go مهمته إيقاف الإجرائيات المقابلة لهذه العقدة وتشغيلها. 
</p>

<p>
	يمثل التابعان <code>Start</code> و<code>Stop</code> في بنية <code>Node</code> الجزئين التاليين، حيث توقَف الإجرائية أو تشغَّل بتمرير رسالة <code>message</code> خلال قناة معينة تفيد بأن برنامج غو هذا الذي ينفَّذ على كل عقدة على حدة يستمر في المراقبة. يمكن تمرير رسالة لتشغيل إجرائية ورسالة مختلفة لإيقافها.
</p>

<p>
	بما أن الخطوات الفعلية لإيقاف أو تشغيل إجرائية تحدث ضمن برنامج Go routine، فلا مجال لحدوث حالات تسابق race conditions بسبب الطريقة التي تتم بها إدارة تشغيل الأكواد بشكل متوازي يبدأ برنامج غو Go routine حلقة لا نهائية infinite loop تبقى بانتظار رسالة <code>message</code> في قناة <code>stateCh</code>، فإذا مرت الرسالة في القناة يُرسَل طلب إلى العقدة node بتشغيل الإجرائية ضمن <code>case StateUp</code>، ونستخدم برنامج باش Bash لتنفيذ هذا الأمر. 
</p>

<p>
	أثناء هذه العملية تُضبط العقدة الأمر بحيث يستخدم متغيرات البيئة environment variables التي حددها المستخدم، ويعيد توجيه الخرج القياسي standard output ومجاري الخطأ error streams إلى ملف سجل log file سبق تحديده. أما لإيقاف الإجرائية ضمن <code>case StateDown</code> فإن البرنامج يوقفها قسريًا باستخدام أمر <code>kill</code>. في حال رغبنا بإيقاف الإجرائية تدريجيًا يمكننا جعله يرسل إشارة SIGTERM والانتظار بضع ثوان قبل إيقافها. يسهّل تابعا <code>Start</code> و <code>Stop</code> تمرير الرسالة المناسبة إلى القناة، إذ يرسل التابع <code>Start</code> رسالة إلى القناة بتشغيل الإجرائية ويعود، في حين ينتظر تابع <code>Stop</code> أن تتوقف الإجرائية قبل أن يعود.
</p>

<h2 id="-7">
	جمع الخطوات السابقة
</h2>

<p>
	لم يتبقَ لنا الآن سوى إضافة جميع الشيفرات السابقة إلى الدالة الرئيسية لبرنامجنا، حيث سيُنفَّذ تحميل ملف الإعدادت وتحليله parse، وتحديث حزم البناء، ومحاولة تحديث تطبيقنا لمرة واحدة، وتشغيل خادم الويب ليصغي إلى حمولات حدث الدفع push event التي ترد من غيت هب.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7582_23" style=""><span class="com">// main.go</span><span class="pln">


func main</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    cfg</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> toml</span><span class="pun">.</span><span class="typ">LoadFile</span><span class="pun">(</span><span class="str">"config.tml"</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">


    url</span><span class="pun">,</span><span class="pln"> ok </span><span class="pun">:=</span><span class="pln"> cfg</span><span class="pun">.</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"buildpack.url"</span><span class="pun">).(</span><span class="pln">string</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">ok </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">.</span><span class="typ">Fatal</span><span class="pun">(</span><span class="str">"buildpack.url not defined"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    err </span><span class="pun">=</span><span class="pln"> </span><span class="typ">UpdateBuildpack</span><span class="pun">(</span><span class="pln">url</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">


    </span><span class="com">// Read configuration options into variables repo (string), env ([]string) and procs (map[string]int)</span><span class="pln">
    </span><span class="com">// ...</span><span class="pln">


    app</span><span class="pun">,</span><span class="pln"> err </span><span class="pun">:=</span><span class="pln"> </span><span class="typ">NewApp</span><span class="pun">(</span><span class="pln">repo</span><span class="pun">,</span><span class="pln"> env</span><span class="pun">,</span><span class="pln"> procs</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">


    err </span><span class="pun">=</span><span class="pln"> app</span><span class="pun">.</span><span class="typ">Update</span><span class="pun">()</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">


    secret</span><span class="pun">,</span><span class="pln"> _ </span><span class="pun">:=</span><span class="pln"> cfg</span><span class="pun">.</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"hook.secret"</span><span class="pun">).(</span><span class="pln">string</span><span class="pun">)</span><span class="pln">


    http</span><span class="pun">.</span><span class="typ">Handle</span><span class="pun">(</span><span class="str">"/hook"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">NewHookHandler</span><span class="pun">(&amp;</span><span class="typ">HookOptions</span><span class="pun">{</span><span class="pln">
        </span><span class="typ">App</span><span class="pun">:</span><span class="pln">    app</span><span class="pun">,</span><span class="pln">
        </span><span class="typ">Secret</span><span class="pun">:</span><span class="pln"> secret</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}))</span><span class="pln">


    addr</span><span class="pun">,</span><span class="pln"> ok </span><span class="pun">:=</span><span class="pln"> cfg</span><span class="pun">.</span><span class="typ">Get</span><span class="pun">(</span><span class="str">"core.addr"</span><span class="pun">).(</span><span class="pln">string</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">!</span><span class="pln">ok </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">.</span><span class="typ">Fatal</span><span class="pun">(</span><span class="str">"core.addr not defined"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">


    err </span><span class="pun">=</span><span class="pln"> http</span><span class="pun">.</span><span class="typ">ListenAndServe</span><span class="pun">(</span><span class="pln">addr</span><span class="pun">,</span><span class="pln"> nil</span><span class="pun">)</span><span class="pln">
    </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بما أن أداتنا تتطلب أن تكون حزم البناء مستودعات غيت بسيطة، فلن تنفذ دالة <code>UpdateBuildpack</code> المتضمنة في شيفرة <a href="https://github.com/hjr265/toptal-hopper/blob/master/buildpack.go" rel="external nofollow">buildpack.go</a> سوى عمليتي نسخ <code>git clone</code> وسحب <code>git pull</code> لرابط المستودع عند الضرورة وذلك لتحديث النسخة المحلية من حزمة البناء.
</p>

<h2 id="-8">
	تجربة الأداة
</h2>

<p>
	في حال لم نستنسخ <a href="https://github.com/hjr265/toptal-hopper" rel="external nofollow">المستودع</a> بعد فيمكن استنساخه الآن. وإذا كان بGo distribution مثبتًا فيمكن البدء بتصريف compile البرنامج.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7582_25" style=""><span class="pln">mkdir hopper
cd hopper
</span><span class="kwd">export</span><span class="pln"> GOPATH</span><span class="pun">=`</span><span class="pln">pwd</span><span class="pun">`</span><span class="pln">
go get github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">hjr265</span><span class="pun">/</span><span class="pln">toptal</span><span class="pun">-</span><span class="pln">hopper
go install github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">hjr265</span><span class="pun">/</span><span class="pln">toptal</span><span class="pun">-</span><span class="pln">hopper</span></pre>

<p>
	لنطلع على ما تنفذه هذه الأوامر على التوالي:
</p>

<ul>
	<li>
		إنشاء مجلد وتسميته hopper
	</li>
	<li>
		الانتقال إليه
	</li>
	<li>
		جعله المجلد الخاص بمساحة العمل GOPATH
	</li>
	<li>
		جلب الشيفرة من غيت هب إلى جانب مكتبات غو Go الضرورية، وتصريف البرنامج لشيفرة binary يمكن إيجادها في مجلد <code>‎$GOPATH/bin</code>
	</li>
</ul>

<p>
	قبل استخدام هذه الأداة على خادم علينا أن ننشئ تطبيق ويب بسيط لنختبر هذه الأداة عليه. في حال رغبنا بالحصول على تطبيق جاهز قد أنشأ صاحب المقال تطبيق ويب بسيط يعرض عبارة "Hello World" بلغة Node.js ورفعه إلى <a href="https://github.com/hjr265/hopper-hello.js" rel="external nofollow">مستودع غيت هب</a> آخر يمكننا اشتقاقه fork وإعادة استخدامه لاختبار الأداة. علينا في الخطوة التالية رفع الشيفرة الثنائية المصرَّفة compiled binary إلى خادم وإنشاء ملف إعدادات في المجلد ذاته:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7582_27" style=""><span class="com"># config.tml </span><span class="pln">


</span><span class="pun">[</span><span class="pln">core</span><span class="pun">]</span><span class="pln">
addr </span><span class="pun">=</span><span class="pln"> </span><span class="str">":26590"</span><span class="pln">
</span><span class="pun">[</span><span class="pln">buildpack</span><span class="pun">]</span><span class="pln">
url </span><span class="pun">=</span><span class="pln"> </span><span class="str">"https://github.com/heroku/heroku-buildpack-nodejs.git"</span><span class="pln">


</span><span class="pun">[</span><span class="pln">app</span><span class="pun">]</span><span class="pln">
repo </span><span class="pun">=</span><span class="pln"> </span><span class="str">"hjr265/hopper-hello.js"</span><span class="pln">


    </span><span class="pun">[</span><span class="pln">app</span><span class="pun">.</span><span class="pln">env</span><span class="pun">]</span><span class="pln">
    GREETING </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Hello"</span><span class="pln">


    </span><span class="pun">[</span><span class="pln">app</span><span class="pun">.</span><span class="pln">procs</span><span class="pun">]</span><span class="pln">
    web </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln">


</span><span class="pun">[</span><span class="pln">hook</span><span class="pun">]</span><span class="pln">
secret </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span></pre>

<p>
	لنبدأ بأول خيار في ملف الإعدادات لدينا وهو <code>core.addr</code> الذي يتيح لنا ضبط منفذ HTTP لخادم الويب الداخلي لبرنامجنا، وقد اخترناه في مثالنا هذا ليكون <code>26590:</code> وهذا يعني أن برنامجنا سيصغي إلى حمولات حدث الدفع عن طريق الرابط <code>http://{host}:26590/hook</code>.
</p>

<p>
	عندما نضبط خطاف الويب في غيت هب ما عليك سوى تبديل <code>{host}</code> أي المضيف باسم النطاق أو عنوان IP الذي يشير إلى خادمنا. وفي حال كنا نستخدم جدار حماية على خادمنا لا ننسى أن فتح هذا المنفذ عليه. ثم نحدد حزمة البناء بإضافة رابط غيت الذي يشير إليها، ونستخدم في مثالنا <a href="https://github.com/heroku/heroku-buildpack-nodejs" rel="external nofollow">حزمة بناء بلغة Node.js الخاصة بهيروكو Heroku</a>.
</p>

<p>
	أما خيار <code>app</code> فقد حددنا ضمنه في خيار <code>repo</code> الاسم الكامل لمستودع غيت هب الذي يستضيف شيفرة التطبيق البرمجية، وبما أن رابط استضافة شيفرة التطبيق المستخدم في مثالنا <code><a href="https://github.com/hjr265/hopper-hello.js" ipsnoembed="true" rel="external nofollow">https://github.com/hjr265/hopper-hello.js</a></code> حددنا الاسم الكامل للمستودع <code>hjr265/hopper-hello.js</code>، ثم حددنا بعض متغيرات البيئة الخاصة بالتطبيق وعدد كل نوع من الإجرائيات التي نحتاجها، وأنهينا الملف باختيار رمز سري لنتحقق من حمولات حدث الدفع الواردة.
</p>

<p>
	يمكننا الآن تشغيل برنامج الأتمتة الذي بنيناه على الخادم. في حال كانت جميع إعداداتنا صحيحة إضافة إلى ضبط مفاتيح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> Keys حتى يُسمح للخادم بالوصول إلى المستودع، سينجح البرنامج في جلب الشيفرة البرمجية وتجهيز البيئة باستخدام حزمة البناء وتشغيل التطبيق.
</p>

<p>
	ما علينا الآن سوى إعداد خطاف ويب في مستودع غيت هب لتصدير أحداث الدفع وتوجيهها إلى الرابط <code>http://{host}:26590/hook</code>. ولا ننسى بالطبع استبدال <code>{host}</code> باسم النطاق أو عنوان IP الذي يشير إلى الخادم. لنختبر عمل الأداة التي بنيناها، نحاول إجراء بعض التغييرات على التطبيق وندفعها إلى غيت هب، ستلاحظ حينها أن أداة الأتمتة ستبدأ العمل فورًا وتٌحدّث المستودع على الخادم وتصّرف التطبيق ثم تعيد تشغيله.
</p>

<h2 id="-9">
	الخاتمة
</h2>

<p>
	نأمل أن تساعدكم هذه المقالة في بناء أداة أتمتة نشر تطبيقات الويب يمكن تعديلها وفق متطلبات العمل. بالطبع لا ننصح بتطبيقها بشكلها الحالي في بيئة تشغيلية دون تطويرها وتحسينها، إذ يمكن مثلًا تحسين تعاملها مع الأخطاء، وجعل الأداة تدعم عمليات الإيقاف وإعادة التشغيل التدريجية، واستخدام <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">دوكر Docker</a> لاحتواء الإجرائيات عوضًا عن تشغيلها مباشرة. أما في حال لم يتطلب عملنا تصميم أداة مخصصة لتنفيذ متطلبات معينة فيمكن دومًا استخدام أحد الحلول الجاهزة الموجودة على الإنترنت والتي تكون مجرَّبة ومستقرة.
</p>

<p>
	ترجمة -وبتصرّف- للمقال <a href="https://www.toptal.com/devops/deploy-web-applications-automatically-using-github-webhooks" rel="external nofollow">Deploy Web Applications Automatically Using GitHub Webhooks </a> لصاحبه Mahmud Ridwan.
</p>

<h2 id="-10">
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/go/%D9%83%D9%8A%D9%81-%D8%AA%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-%D8%A8%D9%84%D8%BA%D8%A9-go-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-1804-r747/" rel="">كيف تنشر تطبيق ويب بلغة Go باستخدام خادم Nginx على أوبنتو 18.04</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A7%D9%84%D9%85%D9%88%D8%AC%D9%87%D8%A9-%D9%84%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r132/" rel="">نشر تطبيقات الويب الموجهة لبيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AE%D8%AF%D9%85%D8%A9-%D9%87%D9%8A%D8%B1%D9%88%D9%83%D9%88-heroku-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r1100/" rel="">نشر تطبيق Node.js على الويب: خدمة هيروكو (Heroku) مثالًا</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%A3%D8%AF%D8%A7%D8%AA%D9%8A-%D8%AF%D9%88%D9%83%D8%B1-docker-%D9%88%D8%A8%D9%88%D8%AF%D9%85%D8%A7%D9%86-podman-r816/" rel="">الفرق بين أداتي دوكر Docker وبودمان Podman</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">836</guid><pubDate>Sat, 08 Feb 2025 15:03:01 +0000</pubDate></item><item><title>&#x646;&#x634;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x631;&#x648;&#x628;&#x64A; &#x623;&#x648;&#x646; &#x631;&#x64A;&#x644;&#x632; &#x639;&#x644;&#x649; &#x62E;&#x627;&#x62F;&#x645; &#x623;&#x648;&#x628;&#x646;&#x62A;&#x648; 24.04 &#x62E;&#x637;&#x648;&#x629; &#x628;&#x62E;&#x637;&#x648;&#x629;</title><link>https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B1%D9%88%D8%A8%D9%8A-%D8%A3%D9%88%D9%86-%D8%B1%D9%8A%D9%84%D8%B2-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-2404-%D8%AE%D8%B7%D9%88%D8%A9-%D8%A8%D8%AE%D8%B7%D9%88%D8%A9-r826/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_11/24_04.png.6852692752b2b9060bef2a8b73e03509.png" /></p>
<p>
	نشرح في هذا المقال نشر تطبيق روبي أون ريلز Ruby on Rails على خادم أوبنتو خطوة بخطوة بدايةً من اختيار خادم الاستضافة وتثبيت الاعتماديات dependencies عليه، ووصولًا إلى إعداد خادم NGINX لاستقبال طلبات التطبيق وبناء قاعدة بيانات التطبيق ونشرها على خادم الاستضافة بمساعدة Capistrano وهي أداة مفتوحة المصدر مخصصة لأتمتة عمليات النشر.
</p>

<h2 id="-1">
	أولًا: إعداد خادم الإنتاج
</h2>

<p>
	سنشرح بداية أبرز معايير اختيار الخادم الافتراضي الخاص VPS الذي سيعمل عليه <a href="https://academy.hsoub.com/programming/ruby/rails/" rel="">تطبيق Rails</a> فخيارات الاستضافة كثيرة وعليك أن تعرف الفروقات فيما بينها وتعتمد ما يناسب احتياجات تطبيقك سواء من ناحية الحجم أو المواصفات أو الأمان وغيرها.
</p>

<h3 id="-2">
	اختيار مزود الاستضافة
</h3>

<p>
	هناك العديد من مزودي خدمات الاستضافة ولكل منهم سلبياته وإيجابياته، ويُقصد بمزود خدمة الاستضافة الجهة التي تمتلك خوادم موجودة في مركز بيانات datacenter وتؤجرها للراغبين باستخدامها بمقابل مادي يدفعونه شهريًا.
</p>

<p>
	إن استئجار الخوادم الموجودة في مراكز البيانات أمر ضروري عند <a href="https://academy.hsoub.com/devops/servers/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A7%D9%84%D9%85%D9%88%D8%AC%D9%87%D8%A9-%D9%84%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r132/" rel="">نشر التطبيقات في بيئة الإنتاج</a> فهو يحميك من المشكلات الطارئة المتعلقة بالتشغيل وأعطال التجهيزات العتادية لأن المزود يؤمن حلولًا لها، كما يوفر لتطبيقك اتصالًا فائق السرعة بالإنترنت وهو أمر مفيد تشغيل التطبيقات المقدمة للعملاء عبر الإنترنت.
</p>

<p>
	إذا كنت تحتاج تحكمًا كبيرًا ببيئة تطبيقك فاستبعد مزودي خدمات الاستضافة المُدارة managed hosting providers أي شركات الاستضافة التي تتكفل بإدارة البنية التحتية التي استأجرتها كاملة لأنها لا تمنحك تحكمًا كافيًا ببيئتك وتفتقر عادةً إلى تثبيت آخر التحديثات.
</p>

<h3 id="-3">
	كيف أحدد مواصفات الخادم الذي أحتاجه لتطبيق ريلز؟
</h3>

<p>
	تحتاج تطبيقات إطار العمل Rails وتطبيقات <a href="https://academy.hsoub.com/programming/ruby/%D9%84%D8%BA%D8%A9-%D8%B1%D9%88%D8%A8%D9%8A/" rel="">روبي</a> عمومًا إلى سعة ذاكرة وصول عشوائي RAM كبيرة، فسعة الذاكرة RAM معيار أساسي عليك التركيز عليه عند اختيار الخادم، وتذكر أنك ستحتاج لتثبيت مكونات أخرى على الخادم ستأخذ حصتها من RAM أيضًا، ومنها قاعدة بيانات التطبيق مثل: <a href="https://academy.hsoub.com/devops/servers/databases/mongodb/" rel="">MongoDB</a> أو <a href="https://academy.hsoub.com/devops/servers/databases/postgresql/" rel="">PostgreSQL</a> أو <a href="https://academy.hsoub.com/devops/servers/databases/redis/" rel="">Redis</a> التي تعمل في الخلفية.
</p>

<p>
	فإذا كنت تنشر تطبيقك الأول فإن خادمًا بذاكرة RAM حجمها 2 جيجابايت مناسب ومقبول التكلفة، وبالرغم من إمكانية البدء بذاكرة 1 جيجابايت لكنها لن تلبي طلبك على الأغلب وسرعان ما ستنفذ عند تجميع الأصول compiling assets وتجهيز الملفات أثناء النشر إلى بيئة الإنتاج.
</p>

<p>
	كما ستحتاج لزيادة سعة RAM على الخادم في الحالات التي تستخدم فيها اعتماديات كبيرة مع تطبيقك مثل خدمات البحث أو غيرها حسب ما تتطلبه هذه الاعتماديات إضافةً إلى احتياجات لتطبيقك، فخدمة بحث مثل ElasticSearch على سبيل المثال تحتاج لوحدها إلى 4 جيجابايت، لذا يلجأ البعض لتشغيلها على خادم منفصل عن <a href="https://academy.hsoub.com/programming/ruby/rails/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-ruby-on-rails-%D9%85%D8%B9-rbenv-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-1804-r843/" rel="">خادم تطبيقات روبي وريلز</a> لتسهيل توسعة الموارد المحجوزة للخدمتين (أي التطبيق والبحث) بمعزل عن بعضهما.
</p>

<h3 id="-4">
	إنشاء الخادم
</h3>

<p>
	سنعرض هنا كمثال طريقة إنشاء الخادم على منصة الاستضافة DigitalOcean علمًا أن العملية متقاربة في معظم المنصات، افتح المنصة وانتقل إلى صفحة إنشاء قطرة Droplet ثم تابع الخطوات.
</p>

<p>
	ملاحظة: Droplet مصطلح خاص بمنصة DigitalOcean يشير إلى وحدة افتراضية تشبه الخادم الافتراضي Virtual Server يمكن للمستخدمين إنشاؤها لتشغيل المواقع والتطبيقات وقواعد بيانات ويمكن تخصيص مواردها المختلفة كالمعالج والذاكرة ومساحة التخزين.
</p>

<h4 id="1">
	الخطوة 1: اختر نظام تشغيل الخادم
</h4>

<p>
	اخترنا هنا نظام أوبنتو 20.24، وهو نظام تشغيل يتمتع بدعم فني طويل الأمد LTS أي أنه سيتلقى تحديثات أمنية أكثر من المعتاد وعلى مدى سنوات طويلة، وهذا أمر مهم عند نشر التطبيقات في بيئة الإنتاج.
</p>

<p>
	توضح الصورة التالية خيار الخادم أوبنتو 20.24 من القائمة المنسدلة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162549" href="https://academy.hsoub.com/uploads/monthly_2024_11/img02-droplet.png.a5c62632709e85ef7ff94b3833a55fc1.png" rel=""><img alt="img02 droplet" class="ipsImage ipsImage_thumbnailed" data-fileid="162549" data-unique="kz44pzfr1" src="https://academy.hsoub.com/uploads/monthly_2024_11/img02-droplet.png.a5c62632709e85ef7ff94b3833a55fc1.png"> </a>
</p>

<h4 id="2">
	الخطوة 2: حدد حجم الخادم ومواصفاته
</h4>

<p>
	بعد اختيار نظام التشغيل الخاص بخادم الاستضافة ستظهر أمامك نافذة لتحدد حجم الخادم ومواصفاته، كما توضح الصورة التالية.
</p>

<p>
	في حال لم تكن متأكدًا من الحجم المناسب لتطبيقك، يمكنك اختيارحجم RAM لتكون 2 جيجابايت، وتغيير لاحقًا الحجم زيادة أو نقصانًا حسب احتياجات تطبيقك، وهنا تكمن ميزة <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A7%D9%84%D8%AD%D9%88%D8%B3%D8%A8%D8%A9-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-virtualization-%D9%88%D8%A3%D9%88%D8%AC%D9%87-%D8%A7%D8%AE%D8%AA%D9%84%D8%A7%D9%81%D9%87%D8%A7-%D8%B9%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-containers-r599/" rel="">الخوادم الافتراضية</a> مقارنة بالخوادم الفيزيائية إذ يمكنك تغيير مواصفاتها بسهولة في أي لحظة بزيادة سعة الذاكرة RAM أو إنقاصها وكذلك الأمر بالنسبة لوحدات المعالجة المركزية CPU ووحدات التخزين وغيرها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162550" href="https://academy.hsoub.com/uploads/monthly_2024_11/img03-droplet-size.png.a4413a6fd6ad2b844754063bb972222c.png" rel=""><img alt="img03 droplet size" class="ipsImage ipsImage_thumbnailed" data-fileid="162550" data-unique="4kf3ihkqw" src="https://academy.hsoub.com/uploads/monthly_2024_11/img03-droplet-size.png.a4413a6fd6ad2b844754063bb972222c.png"> </a>
</p>

<h4 id="3">
	الخطوة 3: اختر المنطقة الجغرافية
</h4>

<p>
	الخطوة التالية هي تحديد منطقة الخادم server region أي المكان الجغرافي الذي يتواجد فيه مركز البيانات الذي سيعمل عليه خادمك الافتراضي، اختر المنطقة الأقرب لمكان تواجد مستخدمي تطبيقك أو مكان تواجدك كمدير للتطبيق والخادم.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="162551" href="https://academy.hsoub.com/uploads/monthly_2024_11/img04-droplet-region.png.19f2b549608772e145fe0477b88bd8e7.png" rel=""><img alt="img04 droplet region" class="ipsImage ipsImage_thumbnailed" data-fileid="162551" data-unique="t1i111vyp" src="https://academy.hsoub.com/uploads/monthly_2024_11/img04-droplet-region.png.19f2b549608772e145fe0477b88bd8e7.png"> </a>
</p>

<h4 id="4">
	الخطوة 4: ضبط الخيارات الإضافية
</h4>

<p>
	يوجد عدد من الخيارات الأخرى يمكنك ضبطها حسب احتياجات عملك إذا رغبت بذلك، ومنها:
</p>

<ul>
	<li>
		<p>
			1. <strong>الشبكات الخاصة Private Networking</strong>: يفيدك إعداد <a href="https://academy.hsoub.com/devops/networking/" rel="">الشبكات</a> الخاصة إذا كان خادمك يحتاج للتخاطب مع خوادم أخرى، كالحالة التي يكون فيها خادم قاعدة البيانات منفصلًا عن خادم التطبيق.
		</p>
	</li>
	<li>
		<p>
			2. <strong>البروتوكول IPv6</strong>: بتفعيل هذا الخيار يمكنك إعطاء خادمك عنوان <a href="https://academy.hsoub.com/devops/networking/%D8%A7%D9%84%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D8%B3%D8%A7%D8%AF%D8%B3-%D9%85%D9%86-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-ip-r504/" rel="">IPv6</a>.
		</p>
	</li>
	<li>
		<p>
			3. <strong>المراقبة Monitoring</strong>: يعطيك بعض المقاييس التقريبية التي تفيدك في قياس نسب استخدام الخادم.
		</p>
	</li>
	<li>
		<p>
			4. <strong>النسخ الاحتياطي Backups</strong>: يعني تفعيل هذا الخيار أخذ صورة image أو نسخة كاملة عن خادمك يمكنك استعادته منها عند حدوث أي طارئ، لكن هذه النسخ الكاملة لا تُشَغَّل بفترات متقاربة لذا تُعدّ النسخ الاحتياطية الساعية لقاعدة بيانات التطبيق أو <a href="https://gorails.com/guides/hourly-production-server-database-and-file-backups" rel="external nofollow">Hourly database backups</a> أفضل منها في معظم الحالات.
		</p>
	</li>
</ul>

<h4 id="5">
	الخطوة 5: أنشئ خادمك
</h4>

<p>
	بعد ضبط كافة الخيارات السابقة، اضغط على زر الإنشاء Create وستُنشئ DigitalOcean خادمك الجديد خلال دقيقة تقريبًا. بعدها توجه إلى بريدك الإلكتروني بمجرد إتمام عملية الإنشاء لتستلم كلمة مرور الخادم.
</p>

<p>
	يمكنك الآن الاتصال بالخادم من حاسوبك المحلي Local Machine بواسطة عنوان IP الخاص به ومستخدم الجذر <code>root</code> الذي استلمته عبر البريد الإلكتروني، كما في المثال التالي طبعًا مع استبدال <code>1.2.3.4</code> بعنوان IP لخادمك:
</p>

<pre class="ipsCode">ssh root@1.2.3.4
</pre>

<h4 id="6">
	الخطوة 6: أنشئ مستخدم النشر
</h4>

<p>
	أنشئ مستخدمًا لعملية النشر باسم <code>deploy</code> مثلًا بأذونات محدودة، واعتمد عليه في تشغيل برمجياتك على الخادم في بيئة الإنتاج بدلًا من تشغيلها من حساب المستخدم الجذر، يقلل ذلك من خطر حصول المخترقين على التحكم الكامل بخادمك في حال نجح باختراقه.
</p>

<p>
	إذًا في أثناء دخولك الأول إلى الخادم باستخدام حساب الجذر <code>root</code> أنشئ مستخدمًا جديدًا وامنحه امتيازات <code>sudo</code> كما يلي:
</p>

<pre class="ipsCode">root@1.2.3.4
adduser deploy
adduser deploy sudo
exit
</pre>

<p>
	أضف بعدها <a href="https://academy.hsoub.com/devops/security/ssh/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%AE%D9%88%D8%A7%D8%AF%D9%8A%D9%85-ssh-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%A1-%D9%88%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD-r55/" rel="">مفتاح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr></a> إلى الخادم لتسريع الدخول إليه، سنستخدم في ذلك الأداة <code><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></abbr>-copy-id</code>، علمًا أنها لا تتوفر افتراضيًا على أجهزة ماك فإذا كان جهازك المحلي من نوع ماك ثبتها باستخدام <code>homebrew</code> وفق الأمر <code>brew install <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">ssh</abbr></abbr>-copy-id</code> ثم نفذ التالي:
</p>

<pre class="ipsCode">ssh-copy-id root@1.2.3.4
ssh-copy-id deploy@1.2.3.4
</pre>

<p>
	يمكنك الآن تسجيل الدخول إلى الخادم بحساب المستخدم <code>root</code> أو المستخدم <code>deploy</code> ومن دون الحاجة لكتابة كلمة المرور لأن تفعيل مفتاح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> يغنيك عنها.
</p>

<p>
	لنفتح الآن جلسة اتصال <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> بواسطة مستخدم النشر <code>deploy</code> وهو المستخدم الذي سننفذ بواسطته كافة الخطوات التالية في المقال، علمًا أنك لن تُطالب بإدخال كلمة مروره كما ذكرنا:
</p>

<pre class="ipsCode">ssh deploy@1.2.3.4
</pre>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="400" id="ips_uid_6977_6" referrerpolicy="strict-origin-when-cross-origin" src="https://academy.hsoub.com/applications/core/interface/index.html" title="الاتصال بخادم Linux عبر SSH" width="930" data-embed-src="https://www.youtube.com/embed/NqKdCONHNgE"></iframe>
</p>

<h2 id="-5">
	ثانيًا: تثبيت روبي على الخادم
</h2>

<p>
	يشبه <a href="https://academy.hsoub.com/programming/ruby/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%B1%D9%88%D8%A8%D9%8A-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r2113/" rel="">تثبيت روبي</a> في بيئة الإنتاج تثبيته في بيئة التطوير مع مزيد من التدقيق في تثبيت جميع اعتماديات <a href="https://academy.hsoub.com/devops/linux/" rel="">لينكس</a> الضرورية لضمان تصريف compile روبي بطريقة صحيحة، ويساعدك استخدام مدير إصدارات روبي Ruby version manager على نشر الإصدارات الجديدة بسهولة ومن دون أي تعديل في ملفات الإعداد config files.
</p>

<p>
	الخطوة الأولى هي تثبيت الاعتمادات الضرورية لتصريف كل من روبي وإطار العمل ريلز، وأهمها ما يلزم لعمل <a href="https://academy.hsoub.com/programming/ruby/rails/%D9%83%D9%8A%D9%81-%D8%AA%D8%B3%D8%AA%D8%AE%D8%AF%D9%85-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-webpacker-%D8%A8%D8%AF%D9%84%D9%8B%D8%A7-%D9%85%D9%86-asset-pipline-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-rails-r562/" rel="">المكتبة Webpacker</a> مع ريلز لذا سنبدأ بإضافة مستودعات Yarn و <a href="https://academy.hsoub.com/programming/javascript/nodejs/" rel="">Node.js</a> إلى نظامنا لنتمكن من تثبيتها.
</p>

<p>
	ثم سنثبت <a href="https://academy.hsoub.com/devops/servers/databases/redis/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D9%86%D8%B5%D9%8A%D8%A8-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-redis-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-r46/" rel="">Redis</a> لنستطيع استخدام ActionCable مع <a href="https://academy.hsoub.com/programming/html/%D9%85%D8%B3%D8%AA%D9%82%D8%A8%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-html-%D8%B9%D8%A8%D8%B1-%D9%85%D9%82%D8%A7%D8%A8%D8%B3-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-websocket-r1556/" rel="">مقابس الويب WebSocket</a> في بيئة الإنتاج، كما قد يرغب البعض باستخدام Redis بصفته مخزن تخبئة cashing لخادم الإنتاج.
</p>

<p>
	لنطبق ذلك عمليًّا، تأكد أولًا من تسجيل دخولك بالمستخدم <code>deploy</code> ثم اكتب الأوامر التالية:
</p>

<pre class="ipsCode">deploy@1.2.3.4
# إضافة مستودع Node.js
curl -sL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
# إضافة مستودع Yarn 
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo add-apt-repository ppa:chris-lea/redis-server
# تحديث الحزم من المستودعين المضافين أعلاه
sudo apt-get update
# تثبيت الاعتماديات المطلوبة لتصريف روبي من المستودعين المضافين
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev dirmngr gnupg apt-transport-https ca-certificates redis-server redis-tools nodejs yarn
</pre>

<p>
	يمكننا الآن تثبيت روبي على الخادم بعد إتمام تثبيت الاعتماديات وقد استخدمنا في هذا المقال الإصدار 3.3.1 من روبي لكن تستطيع اختيار الإصدار الذي تفضله.
</p>

<p>
	كما سبق وذكرنا سنستخدم مدير الإصدارات <code>rbenv</code> لتثبيت روبي، فهو أسهل في التعامل والترقية وأيضًا يوفر لك عددًا من المكونات الإضافية المفيدة لضبط متغيرات بيئة الإنتاج.
</p>

<p>
	نَفِّذْ الأوامر التالية:
</p>

<pre class="ipsCode">deploy@1.2.3.4
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' &gt;&gt; ~/.bashrc
echo 'eval "$(rbenv init -)"' &gt;&gt; ~/.bashrc
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' &gt;&gt; ~/.bashrc
git clone https://github.com/rbenv/rbenv-vars.git ~/.rbenv/plugins/rbenv-vars
exec $SHELL
rbenv install 3.3.1
rbenv global 3.3.1
ruby -v
# ruby 3.3.1
</pre>

<p>
	نثبت الآن مُجَمِّع الوحدات <code>Bundler</code> (وهو أداة تستخدم في مشاريع الويب لتنظيم الوحدات والاعتماديات وتوليد الأصول):
</p>

<pre class="ipsCode">deploy@1.2.3.4
# يثبت الأمر التالي الإصدار الأخير من Bundler، والإصدار الأحدث عند إعداد المقال هو 2‪.x 
gem install bundler
# أو نفذ الأمر التالي إذا رغبت بتثبيت إصدار أقدم مثل 1‪.x
gem install bundler -v 1.17.3
# يعطيك الأمر التالي رقم إصدار Bundler ويساعدك بالنتيجة على التأكد من صحة تثبيته
bundle -v
# Bundler version 2.0
</pre>

<p>
	إذا حصلت على رسالة خطأ مفادها عدم العثور على Bundler، نفذ الأمر <code>rbenv rehash</code> وكرر المحاولة.
</p>

<h2 id="nginxpassenger">
	ثالثًا: إعداد خادم الويب NGINX والوحدة Passenger
</h2>

<p>
	يتطلب العمل في بيئة الإنتاج وجود خادم ويب مثل <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r434/" rel="">NGINX</a> أمام ريلز لاستقبال طلبات HTTP والتعامل مع شهادات <a href="https://academy.hsoub.com/devops/servers/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%B4%D9%87%D8%A7%D8%AF%D8%A9-ssl-%D9%85%D9%86-%D8%B3%D9%84%D8%B7%D8%A9-%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%AA%D8%AC%D8%A7%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AD%D8%B5%D9%88%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A9-%D9%88%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87%D8%A7-r148/" rel=""><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr></a> وملفات التطبيق الثابتة static files بطريقة أسرع من روبي، وعلى أرض الواقع من غير المنطقي طرح تطبيقك للمستخدمين من دون خادم ويب.
</p>

<p>
	اعتمدنا في مثالنا على NGINX والوحدة Passenger، إذ سيستقبل NGINX طلبات HTTP الواردة إلى خادم التطبيق، ويحولها إلى Passenger الذي سيشغل تطبيقك.
</p>

<p>
	اكتب الأوامر التالية لديك لإضافة مستودع Passenger وتثبيته على الخادم:
</p>

<pre class="ipsCode">deploy@1.2.3.4
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger $(lsb_release -cs) main &gt; /etc/apt/sources.list.d/passenger.list'
sudo apt-get update
sudo apt-get install -y nginx-extras libnginx-mod-http-passenger
if [ ! -f /etc/nginx/modules-enabled/50-mod-http-passenger.conf ]; then sudo ln -s /usr/share/nginx/modules-available/mod-http-passenger.load /etc/nginx/modules-enabled/50-mod-http-passenger.conf ; fi
sudo ls /etc/nginx/conf.d/mod-http-passenger.conf
</pre>

<p>
	ثم افتح ملف إعدادات Passenger باستخدام أي محرر نصوص تفضله مثل <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%82%D8%A7%D8%B1%D9%86%D8%A9-%D8%A8%D9%8A%D9%86-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A%D9%85-vim-%D9%88%D9%86%D8%A7%D9%86%D9%88-nano-r1590/" rel=""><code>nano</code></a> أو <a href="https://academy.hsoub.com/programming/workflow/%D9%85%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-%D9%85%D8%AD%D8%B1%D8%B1-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-vim-%D9%84%D8%B1%D9%81%D8%B9-%D8%A5%D9%86%D8%AA%D8%A7%D8%AC%D9%8A%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D9%84-r1618/" rel=""><code>vim</code></a> كما يلي:
</p>

<pre class="ipsCode">deploy@1.2.3.4
# تحرير الملف باستخدام nano
sudo nano /etc/nginx/conf.d/mod-http-passenger.conf
# تحرير الملف باستخدام vim
sudo vim /etc/nginx/conf.d/mod-http-passenger.conf
</pre>

<p>
	عَدِّل السطر الذي يحتوي القيمة <code>passenger_ruby</code> ضمن الملف ليصبح وفق التالي، وذلك لتوجيه Passenger إلى الإصدار المطلوب من روبي:
</p>

<pre class="ipsCode">passenger_ruby /home/deploy/.rbenv/shims/ruby;
</pre>

<p>
	احفظ التغييرات على الملف وأغلقه.
</p>

<p>
	ثم شغل NGINX كما يلي:
</p>

<pre class="ipsCode">deploy@1.2.3.4
sudo service nginx start
</pre>

<p>
	يمكنك التأكد من صحة تشغيل NGINX بفتح عنوان IP العام للخادم في متصفح الإنترنت لديك، وستحصل على رسالة الترحيب التالية "Welcome to NGINX".
</p>

<p>
	والآن احذف ملف الإعدادات الافتراضي لخادم NGINX، وأضف ملفًا خاصًا لتطبيقك:
</p>

<pre class="ipsCode">deploy@1.2.3.4
sudo rm /etc/nginx/sites-enabled/default
# تحرير الملف باستخدام nano
sudo nano /etc/nginx/sites-enabled/myapp
# تحرير الملف باستخدام VIM
sudo vim /etc/nginx/sites-enabled/myapp
</pre>

<p>
	عدل على الملف ليصبح وفق الآتي، واستبدل <code>myapp</code> باسم تطبيقك، علمًا أننا سنستخدم الملف نفسه لاحقًا لتحديد المجلد <code>deploy_to</code> الخاص بالمكتبة Capistrano:
</p>

<pre class="ipsCode">server {
  listen 80;
  listen [::]:80;

  server_name _;
  root /home/deploy/myapp/current/public;

  passenger_enabled on;
  passenger_app_env production;

  location /cable {
    passenger_app_group_name myapp_websocket;
    passenger_force_max_concurrent_requests_per_process 0;
  }

  # Allow uploads up to 100MB in size
  client_max_body_size 100m;

  location ~ ^/(assets|packs) {
    expires max;
    gzip_static on;
  }
}
</pre>

<p>
	احفظ التغييرات على الملف وأغلقه، ثم أعد تحميل NGINX كما يلي لتطبيق التغييرات:
</p>

<pre class="ipsCode">deploy@1.2.3.4
sudo service nginx reload
</pre>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="400" id="ips_uid_6977_7" referrerpolicy="strict-origin-when-cross-origin" src="https://academy.hsoub.com/applications/core/interface/index.html" title="تثبيت وضبط خادم Nginx" width="930" data-embed-src="https://www.youtube.com/embed/F9K3m9AbHCY"></iframe>
</p>

<h2 id="-6">
	رابعًا: إنشاء قاعدة البيانات
</h2>

<p>
	سنعرض طريقتين لإنشاء قاعدة بيانات التطبيق، الأولى باستخدام PostgreSQL وهي الخيار الأفضل في بيئة الإنتاج، والثانية باستخدام MySQL، اتبع الأسلوب الذي يناسبك.
</p>

<h3 id="postgresql">
	إنشاء قاعدة بيانات PostgreSQL
</h3>

<p>
	ثبت في البداية خادم Postgres مع المكتبة libpq التي تسمح بتصريف gem pg وهي مكتبة روبي المخصصة للتعامل مع PostgreSQL. للمزيد يمكنك مشاهدة الفيديو التالي:
</p>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="400" id="ips_uid_6977_8" referrerpolicy="strict-origin-when-cross-origin" src="https://academy.hsoub.com/applications/core/interface/index.html" title="تثبيت وإعداد قاعدة البيانات PostgreSQL" width="930" data-embed-src="https://www.youtube.com/embed/eZlLwNAHwsk"></iframe>
</p>

<p>
	سينشأ مع التثبيت مستخدم لينكس يدعى <code>postgres</code> يملك الصلاحيات الكاملة على قاعدة البيانات، يمكنك استخدامه لإنشاء مستخدم قاعدة بيانات خاص بتطبيقك، اخترنا له الاسم <code>deploy</code> في مثالنا.
</p>

<p>
	ثم أنشئ قاعدة بيانات التطبيق، تدعى في مثالنا <code>myapp</code>، وتأكد أن المستخدم <code>deploy</code> هو مالك قاعدة البيانات، وفق الأوامر التالية:
</p>

<pre class="ipsCode">deploy@1.2.3.4
sudo apt-get install postgresql postgresql-contrib libpq-dev
sudo su - postgres
createuser --pwprompt deploy
createdb -O deploy myapp
exit
</pre>

<p>
	والآن يمكنك الاتصال بقاعدة البيانات الجديدة وفق التالي:
</p>

<pre class="ipsCode">psql -U deploy -W -h 127.0.0.1 -d myapp
</pre>

<p>
	استخدم <code>127.0.0.1</code> بدلًا من <code>localhost</code> عند الاتصال بقاعدة البيانات.
</p>

<h3 id="mysql">
	إنشاء قاعدة بيانات MySQL
</h3>

<p>
	ستحتاج لتثبيت نسختي الخادم server والعميل client من MySQL لإنشاء قاعدة بيانات التطبيق وتصريف روبي gem عبر الواحهة mysql2 (يساعدك الاطلاع على المقال <a href="https://academy.hsoub.com/devops/servers/databases/mysql/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-mysql-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r433/" rel="">كيفية تثبيت MySQL على أوبونتو 18.04</a>).
</p>

<p>
	وبعد التثبيت اكتب الأوامر التالية:
</p>

<pre class="ipsCode">deploy@1.2.3.4
sudo apt-get install mysql-server mysql-client libmysqlclient-dev
sudo mysql_secure_installation
# هذا الأمر مخصص لفتح واجهة سطر الأوامر لنظام MySQL لننشئ المستخدم وقاعدة البيانات
mysql -u root -p
</pre>

<p>
	بعد تنفيذ الأوامر السابقة ستفتح واجهة سطر الأوامر لنظام MySQL وبواسطتها يمكنك إنشاء قاعدة بيانات تطبيقك ومستخدم قاعدة بيانات مخصص لإدارتها ويملك الصلاحيات الكاملة عليها، ويستحسن أن تلتزم بإنشاء مستخدم قاعدة بيانات خاص للتطبيق فهذا الإجراء يُعدّ أكثر آمانًا.
</p>

<p>
	عندما تقرأ الأوامر الموجودة أسفل الفقرة سيتبادر لذهنك أننا ننشئ مستخدمين اثنين على قاعدة البيانات لكن ذلك يرجع لاختلاف طريقة تعامل MySQL مع الاتصالات المحلية <code>localhost</code> والبعيدة التي تجري عبر الشبكة أي عبر عنوان IP، وبالأوامر التي كتبناها هنا قد سمحنا بنوعي الاتصال.
</p>

<p>
	وانتبه قبل التنفيذ لضرورة استبدال الأسماء المذكورة بما يناسب تطبيقك كما يلي:
</p>

<ul>
	<li>
		استبدل <code>myapp</code> باسم قاعدة بياناتك، وهي تماثل اسم التطبيق عادةً.
	</li>
	<li>
		استبدل <code>omeFancyPassword123</code> بكلمة مرورك.
	</li>
	<li>
		استبدل <code>deploy</code> باسم مستخدم قاعدة البيانات الذي تختاره.
	</li>
</ul>

<pre class="ipsCode">deploy@1.2.3.4
CREATE DATABASE IF NOT EXISTS myapp;
CREATE USER IF NOT EXISTS 'deploy'@'localhost' IDENTIFIED BY '$omeFancyPassword123';
CREATE USER IF NOT EXISTS 'deploy'@'%' IDENTIFIED BY '$omeFancyPassword123';
GRANT ALL PRIVILEGES ON myapp.* TO 'deploy'@'localhost';
GRANT ALL PRIVILEGES ON myapp.* TO 'deploy'@'%';
FLUSH PRIVILEGES;
\q
</pre>

<h2 id="capistrano">
	خامسًا: النشر باستخدام Capistrano
</h2>

<p>
	بعد إتمام تهيئة الخادم وأدواته حان الوقت لتحميل الشيفرة البرمجية الخاصة بالتطبيق إلى مرحلة الإنتاج.
</p>

<p>
	وهنا يأتي دور المكتبة Capistrano التي تساعدك على إنشاء نسخ من مستودع تطبيقك في مرحلة الإنتاج، وعلى إنشاء إصداراته الجديدة لاحقًا بكل سهولة.
</p>

<h3 id="capistrano-1">
	إعداد Capistrano
</h3>

<p>
	ثَبِّت Capistrano ضمن <a href="https://academy.hsoub.com/programming/ruby/rails/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-ruby-on-rails-r526/" rel="">تطبيق ريلز</a> على حاسوبك المحلي (أي حيث طورت التطبيق وحيث توجد شيفرته البرمجية).
</p>

<p>
	ثم أضف جواهر روبي أو <code>gems</code> التالية إلى الملف <code>Gemfile</code> كما يلي:
</p>

<pre class="ipsCode">gem 'capistrano', '~&gt; 3.11'
gem 'capistrano-rails', '~&gt; 1.4'
gem 'capistrano-passenger', '~&gt; 0.2.0'
gem 'capistrano-rbenv', '~&gt; 2.1', '&gt;= 2.1.4'
</pre>

<p>
	ثم نَفِّذ الأوامر التالية من حاسوبك المحلي بهدف تثبيت <code>gems</code> السابقة وتثبيت ملفات الإعدادات الخاصة بالمكتبة Capistrano:
</p>

<pre class="ipsCode">bundle
cap install STAGES=production
</pre>

<p>
	وستنشئ بعدها الملفات التالية:
</p>

<ul>
	<li>
		<code>Capfile</code>.
	</li>
	<li>
		<code>config/deploy.rb</code>.
	</li>
	<li>
		<code>config/deploy/production.rb</code>.
	</li>
</ul>

<p>
	افتح في البداية الملف <code>Capfile</code> وأضِف إليه الأسطر التالية:
</p>

<pre class="ipsCode">require 'capistrano/rails'
require 'capistrano/passenger'
require 'capistrano/rbenv'

set :rbenv_type, :user
set :rbenv_ruby, '3.3.1'
</pre>

<p>
	ثم افتح الملف <code>config/deploy.rb</code> لتُعرِّف تطبيقك ضمنه، وتحصل على التفاصيل الخاصة بمستودع التطبيق:
</p>

<pre class="ipsCode">set :application, "myapp"
set :repo_url, "git@github.com:username/myapp.git"

# Deploy to the user's home directory
set :deploy_to, "/home/deploy/#{fetch :application}"

append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', '.bundle', 'public/system', 'public/uploads'

# Only keep the last 5 releases to save disk space
set :keep_releases, 5

# Optionally, you can symlink your database.yml and/or secrets.yml file from the shared directory during deploy
# This is useful if you don't want to use ENV variables
# append :linked_files, 'config/database.yml', 'config/secrets.yml'
</pre>

<p>
	وأخيرًا عَدِّل على الملف <code>config/deploy/production.rb</code> لإضافة عنوان IP العام للخادم إلى عمليات النشر، وذلك وفق التالي، ولا تنسَ استبدال <code>1.2.3.4</code> بعنوان خادمك:
</p>

<pre class="ipsCode">server '1.2.3.4', user: 'deploy', roles: %w{app db web}
</pre>

<p>
	يتبقى لنا الخطوة الأخيرة قبل النشر وهي إضافة متغيرات البيئة إلى خادم الإنتاج، لذا افتح جلسة اتصال <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> مع الخادم من حاسوبك المحلي:
</p>

<pre class="ipsCode">ssh deploy@1.2.3.4
</pre>

<p>
	ونفذ التالي:
</p>

<pre class="ipsCode">mkdir /home/deploy/myapp
nano /home/deploy/myapp/.rbenv-vars
</pre>

<p>
	ثم أضف متغيرات البيئة المناسبة لحالتك من بين التالي إلى هذا الملف:
</p>

<pre class="ipsCode"># For Postgres
DATABASE_URL=postgresql://deploy:PASSWORD@127.0.0.1/myapp

# For MySQL
DATABASE_URL=mysql2://deploy:$omeFancyPassword123@localhost/myapp

RAILS_MASTER_KEY=ohai
SECRET_KEY_BASE=1234567890

STRIPE_PUBLIC_KEY=x
STRIPE_PRIVATE_KEY=y
# etc...
</pre>

<p>
	احفظ التغييرات على الملف، وستُحَمَّل متغيرات البيئة المذكورة هنا تلقائيًا إلى الخادم في كل مرة تُشَغِّل فيها أوامر روبي داخل مجلد التطبيق على الخادم.
</p>

<p>
	يفيدك هذا الأسلوب في تخصيص متغيرات بيئة مستقلة لكل تطبيق تنشره على الخادم.
</p>

<p>
	يمكنك الآن نشر التطبيق على خادم الإنتاج، بتنفيذ الأمر التالي من حاسوبك المحلي:
</p>

<pre class="ipsCode">cap production deploy
</pre>

<p>
	اكتب عنوان IP الخادم في متصفح الإنترنت وإذا كانت كافة إعداداتك صحيحة فستظهر أمامك واجهة تطبيق ريلز Rails الذي نشرته.
</p>

<p>
	وإذا لم تحصل على واجهة ريلز، فيمكنك تتبع الخطأ بمراجعة ملفات تسجيل الأحداث logs من جلسة اتصال <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr> مع الخادم كما يلي:
</p>

<pre class="ipsCode">deploy@1.2.3.4
# لعرض ملفات تسجيل الأحداث الخاصة بريلز
less /home/deploy/myapp/current/log/production.log
# لعرض ملفات تسجيل الأحداث الخاصة بخادم NGINX و Passenger 
sudo less /var/log/nginx/error.log
</pre>

<p>
	تنجم أغلب الأخطاء عن وجود خلل في أحد متغيرات البيئة أو ملفات الإعدادات التي جهزناها لمرحلة الإنتاج، وبمجرد وصولك للخطأ وتصحيحه يمكنك إعادة تشغيل تطبيقك أو إعادة نشره ثم التحقق مجددًا من المتصفح لمعرفة النتيجة.
</p>

<h2 id="-7">
	سادسًا: توصيات إضافية
</h2>

<p>
	إلى جانب جودة التطبيق الذي طورته بواسطة روبي أو غيرها وصحة نشره على الخادم، فإن متطلبات العمل في البيئة الحقيقية تفرض عليك الاهتمام بجوانب أخرى مثل:
</p>

<ul>
	<li>
		<p>
			تركيب شهادة <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr> على الخادم من خدمة مجانية مثل LetsEncrypt أو غيرها لحماية البيانات المتبادلة مع التطبيق، يساعدك في ذلك مقال <a href="https://academy.hsoub.com/devops/servers/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8-%D8%B4%D9%87%D8%A7%D8%AF%D8%A9-ssl-%D9%85%D8%AC%D8%A7%D9%86%D9%8A%D8%A9-%D8%B9%D8%A8%D8%B1-%D8%AE%D8%AF%D9%85%D8%A9-lets-encrypt-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r151/" rel="">تنصيب شهادة <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة"><abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></abbr> مجانية عبر خدمة Let's encrypt على خادوم لينكس</a> على أكاديمية حسوب.
		</p>
	</li>
	<li>
		<p>
			تفعيل النسخ الاحتياطي الساعي Hourly Backups أو النسخ الاحتياطي الدوري عمومًا الذي ينسخ بياناتك إلى وحدة تخزين خارجية مثل S3، فهو يخفف من مخاطر ضياع البيانات ويساعدك على استعادتها في حال تعرض خادمك أو تطبيقك لأي عطل أو حادث طارئ.
		</p>
	</li>
	<li>
		<p>
			اتباع خطة لتدوير ملفات تسجيل الأحداث logs حتى لا تملأ مساحة التخزين على الخادم دون أن تنتبه لها.
		</p>
	</li>
	<li>
		<p>
			اتخاذ التدابير الأمنية الضرورية لحماية الخادم من الهجمات السيبرانية، يفيدك في ذلك مقال <a href="https://academy.hsoub.com/devops/security/7-%D8%AA%D8%AF%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A3%D9%85%D9%86%D9%8A%D8%A9-%D9%84%D8%AD%D9%85%D8%A7%D9%8A%D8%A9-%D8%AE%D9%88%D8%A7%D8%AF%D9%8A%D9%85%D9%83-r60/" rel="">7 تدابير أمنية لحماية خواديمك</a> والمقالات الأخرى الموجودة في قسم <a href="https://academy.hsoub.com/devops/security/" rel="">حماية</a> على أكاديمية حسوب.
		</p>
	</li>
</ul>

<p>
	ترجمة -وبتصرف- لمقال <a href="https://gorails.com/deploy/ubuntu/24.04" rel="external nofollow">Deploy Ruby On Rails:Ubuntu 24.04 Noble Numbat in 2024</a> من موقع Go Rails.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-express-%D9%88%D8%AA%D9%88%D8%B3%D9%8A%D8%B9%D9%87-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-memcachier-%D9%85%D9%86-%D9%85%D9%86%D8%B5%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-digitalocean-r803/" rel="">كيفية نشر تطبيق Express وتوسيعه باستخدام إضافة MemCachier من منصة تطبيقات DigitalOcean</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-flask-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-pythonanywhere-r423/" rel="">نشر تطبيقات Flask باستخدام PythonAnywhere</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%AA%D9%88%D8%B2%D9%8A%D8%B9%D9%87%D8%A7-%D9%88%D9%81%D9%82-%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r646/" rel="">نشر التطبيقات وتوزيعها وفق نهج التسليم المستمر</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-reactjs-%D8%B0%D9%88-%D9%88%D8%A7%D8%AC%D9%87%D8%A7%D8%AA-%D8%AE%D9%84%D9%81%D9%8A%D8%A9-nodejs-%D8%B9%D9%84%D9%89-%D9%85%D9%86%D8%B5%D8%A9-heroku-r512/" rel="">فيديو: نشر تطبيق React.js ذو واجهات خلفية Node.js على منصة Heroku</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">826</guid><pubDate>Wed, 20 Nov 2024 15:04:01 +0000</pubDate></item><item><title>&#x643;&#x64A;&#x641;&#x64A;&#x629; &#x646;&#x634;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642; Express &#x648;&#x62A;&#x648;&#x633;&#x64A;&#x639;&#x647; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x625;&#x636;&#x627;&#x641;&#x629; MemCachier &#x645;&#x646; &#x645;&#x646;&#x635;&#x629; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; DigitalOcean</title><link>https://academy.hsoub.com/devops/deployment/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-express-%D9%88%D8%AA%D9%88%D8%B3%D9%8A%D8%B9%D9%87-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B6%D8%A7%D9%81%D8%A9-memcachier-%D9%85%D9%86-%D9%85%D9%86%D8%B5%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-digitalocean-r803/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2024_02/---Express----MemCachier----DigitalOcean-d2-3.png.b46f57c6cc284e41a5bc25a7fa6325dd.png" /></p>
<p>
	يُعد إطار <span ipsnoautolink="true">Express</span> أحد الإطارات الشائعة لبناء تطبيقات الويب السريعة وواجهات برمجة التطبيقات <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr> باستخدام بيئة التشغيل Node.js.
</p>

<p>
	<a href="https://www.digitalocean.com/products/app-platform" rel="external nofollow">منصة تطبيقات DigitalOcean</a> عبارة عن منتج منصة على أساس خدمة PaaS لضبط إعدادات التطبيقات ونشرها من مستودع شيفرات برمجية، فهي توفر طريقة سريعة وفعالة لنشر تطبيقات <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/" rel="">Express</a>.
</p>

<p>
	سنتعلم في دليلنا هذا، كيفية نشر تطبيق Express على منصة تطبيقات DigitalOcean ثم توسيع نطاقه عن طريق إضافة التخزين المؤقت باستخدام إضافة متجر <a href="https://marketplace.digitalocean.com/add-ons/memcachier" rel="external nofollow">DigitalOcean Marketplace</a> المخصصة لـ MemCachier. إذ يتوافق MemCachier مع نظام <a href="https://memcached.org/" rel="external nofollow">memcached</a> للتخزين المؤقت للكائنات ولديه عدة مزايا، مثل تحسين سيناريوهات الفشل باستخدام مجموعة حواسيب عالية التوفر.
</p>

<p>
	سننشئ أولاً تطبيق Express يحسب عددًا أوليًا، ويحتوي على زر "أعجبني"، ويستخدم محرك قوالب <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-template-%D9%81%D9%8A-express-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%82%D8%A7%D9%84%D8%A8-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A-%D9%84%D9%85%D9%88%D9%82%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r2209/" rel="">Template engine</a>. ستمكنك هذه الميزات من تنفيذ عدة استراتيجيات تخزين مؤقت لاحقًا.
</p>

<p>
	ثم ستدفع شيفرة التطبيق على Git Hub لتنشره بعدها على منصة التطبيقات App Platform. وأخيرًا، ستطبق ثلاث تقنيات تخزين مؤقت للكائنات لتسريع التطبيق وجعله سهل التطوير والتوسع. بنهاية هذا المقال، ستكون قادرًا على نشر تطبيق Express على منصة App Platform، وتطبيق تقنيات التخزين المؤقت لتخزرين العمليات ذات الاستخدام الكثيف للموارد، وتخزين طرق العرض المصيّر rendered views، والجلسات sessions.
</p>

<h2 id="">
	المتطلبات الأساسية
</h2>

<p>
	لمتابعة خطوات هذا المقال التعليمي، ستحتاج إلى ما يلي:
</p>

<ul>
	<li>
		تثبيت بيئة تشغيل <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/" rel="">Node.js</a> على جهازك العامل بنظام أوبنتو 22.04، ننصحك باتباع خطوات <a href="https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-22-04" rel="external nofollow">المقال التالي</a>. أما إن كنت تستخدم نظام تشغيل آخر، فعليك اتباع <a href="https://www.digitalocean.com/community/tutorial_series/how-to-install-node-js-and-create-a-local-development-environment" rel="external nofollow">المقال التالي</a>.
	</li>
	<li>
		خادم Express مُثبّت عليه Node.js، ووللقيام بذلك ننصحك بالإطلاع على الخطوتين الأولى والثانية في <a href="https://www.digitalocean.com/community/tutorials/nodejs-express-basics" rel="external nofollow">هذا المقال</a>.
	</li>
	<li>
		حساب غيت هب GitHub و تثبيت غيت Git على جهازك، إذ سننشر التطبيق في حسابك على غيت هب GitHub ثم سننشره على منصة تطبيقات DigitalOcean، اتبع الخطوات الواردة في <a href="https://academy.hsoub.com/programming/workflow/git/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D9%85%D8%B3%D8%A7%D9%87%D9%85%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D8%B4%D8%A7%D8%B1%D9%8A%D8%B9-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-%D8%A7%D8%A8%D8%AF%D8%A3-%D8%A8%D8%AA%D8%B9%D9%84%D9%85-%D9%86%D8%B8%D8%A7%D9%85-git-r1580/" rel="">المقال التالي</a> لتثبيت غيت على جهازك.
	</li>
	<li>
		حساب على DigitalOcean لنشر التطبيق على منصة App Platform، وننبهك إلى أن نشر التطبيقات على هذه المنصة مأجور، لذا اطّلع على أجور منصة <a href="https://docs.digitalocean.com/products/app-platform/details/pricing/" rel="external nofollow">App Platform</a> قبل البدء.
	</li>
	<li>
		متصفح إنترنت مثل <a href="https://www.google.com/chrome/" rel="external nofollow">متصفح كروم Chrome</a> أو <a href="https://www.mozilla.org/en-US/firefox/new/" rel="external nofollow">فايرفوكس Fire Fox</a>.
	</li>
	<li>
		<p>
			فهم أساسيات عمل <span ipsnoautolink="true">موّلد قوالب Express</span>.
		</p>
	</li>
	<li>
		<p>
			فهم آلية عمل البرمجيات الوسيطة middle-ware<span style="display: none;"> </span>.
		</p>
	</li>
</ul>

<h2 id="-1">
	الخطوة الأولى: ضبط قالب عرض مصيّر
</h2>

<p>
	سنثبّت في هذه الخطوة <a href="http://expressjs.com/en/guide/using-template-engines.html#using-template-engines-with-express" rel="external nofollow">موّلد قوالب Express</a>، وننشئ قالبًا للمسار الرئيسي للتطبيق GET/، ثم سنحدّث المسار كي يستخدم هذا القالب. تمَكّننا القوالب من استخدام التخزين المؤقت للعرض المصيّر rendered view، مما يزيد سرعة معالجة الطلب ويقلل استخدام الموارد.
</p>

<p>
	لبدء العمل، انتقل إلى مجلد مشروع خادم Express باستخدام المحرر، سنثبّت محرك قوالب Express لاستخدام القوالب الثابتة في التطبيق، حيث يستبدل محرك القوالب المتغيرات الموجودة في ملف القالب بالقيم ثم يحول القالب إلى ملف HTML، ثم يُرسَل كرد أو استجابة response على طلب request. إذ يؤدي استخدام القوالب إلى تسهيل العمل باستخدام HTML.
</p>

<p>
	والآن، ثبّت <a href="https://github.com/mde/ejs" rel="external nofollow">مكتبة قوالب جافا سكربت المضمنة ejs</a>، كما يمكنك استخدام أحد <a href="https://expressjs.com/en/resources/template-engines.html" rel="external nofollow">محركات القوالب التي يدعمها Express</a> مثل Mustache أو Pug أو Nunjucks.
</p>

<pre class="ipsCode">npm install ejs
</pre>

<p>
	بعد تثبيت مكتبة <code>ejs</code>، عليك ضبط إعدادات تطبيق Express كي يستخدمها، وذلك عن طريق فتح الملف <code>server.js</code> في محرر النصوص البرمجية، وإضافة السطر الثالث كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_16" style=""><span class="pln">server</span><span class="pun">.</span><span class="pln">js
</span><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'view engine'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'ejs'</span><span class="pun">);</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'Successful response.'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يعدّل هذا السطر <a href="https://expressjs.com/en/4x/api.html#app.settings.table" rel="external nofollow">إعدادات التطبيق</a> ويعين خاصيّة <code>view engine</code> على <code>ejs</code>. ثم احفظ الملف بعد ذلك.
</p>

<p>
	<strong>ملاحظة:</strong> سنستخدم في هذا المقال الإعداد <code>view engine</code>، كما يمكنك استخدام الإعداد <code>views</code> الذي يدّل تطبيق Express على مكان وجود ملفات القوالب، وقيمته الافتراضية هي <code>views/.</code> والآن، أنشئ مجلد <code>views</code> ثم أنشئ فيه الملف <code>views/index.ejs</code> وافتحه في محرر النصوص البرمجية. وأضف توصيف القالب إلى الملف:
</p>

<pre class="ipsCode">views/index.ejs
&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="utf-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
    &lt;title&gt;Find the largest prime number&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;Find the largest prime number&lt;/h1&gt;

    &lt;p&gt;
      For any number N, find the largest prime number less than or equal to N.
    &lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre>

<p>
	ثم احفظ الملف. بعد أن أنشأت القالب، عليك تعديل المسار كي تتمكن من استخدامه. افتح الملف <code>server.js</code> وأضف ما يلي إليه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_18" style=""><span class="pun">...</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	لاحظ أن دالة رد النداء <code>render</code> تأخذ اسم القالب كوسيط أول، وفي مثالنا يتوافق <code>index</code> مع اسم الملف <code>views/index.ejs</code>. والآن، أعد تشغيل التطبيق لكي تطبق التغيرات التي أجريتها. أوقف عمل الخادم في الطرفية باستخدام المفتاحين <strong>Ctrl+C</strong> ثم أعد تشغيل الخادم باستخدام الأمر التالي:
</p>

<pre class="ipsCode">node server.js
</pre>

<p>
	الآن انتقل إلى <code>localhost:3000</code> في المتصفح لمشاهدة محتوى القالب، ستحصل على نتيجة مماثلة لما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="145439" href="https://academy.hsoub.com/uploads/monthly_2024_02/FRhjviO.png.03fe10a21d5514c62cfb5913ccca0129.png" rel=""><img alt="frhjvio" class="ipsImage ipsImage_thumbnailed" data-fileid="145439" data-unique="em59rhipo" src="https://academy.hsoub.com/uploads/monthly_2024_02/FRhjviO.thumb.png.d0c4a8595b005ade81e5bc0b511b4bba.png"> </a>
</p>

<p>
	والآن، أصبح تطبيقك يتمتع بعرض مُصَيّر للقوالب، لكنه لا ينفذ شيئًا الآن. سنضيف في الخطوة التالية وظيفة للعثور على عدد أولي.
</p>

<h2 id="express">
	الخطوة الثانية، إضافة الوظائف إلى تطبيق Express
</h2>

<p>
	سنضيف في هذه الخطوة ميّزة العثور على عدد أولي، وميّزة إبداء الإعجاب باستخدام زر "أعجبني Like". سنستخدم هذه الميزات للتفاعل مع التطبيق بعد نشره على منصة App Platform في الخطوة الرابعة من هذا المقال.
</p>

<h3 id="-2">
	العثور على العدد الأولي
</h3>

<p>
	سنضيف الآن دالة إلى تطبيقنا وظيفتها العثور على أكبر عدد أولي أقل من أو يساوي <code>N</code>، حيث يشير <code>N</code> إلى عدد ما. سنرسل العدد <code>N</code> عبر استمارة باستخدام التابع <code>GET</code> إلى المسار الرئيسي <code>(/)</code> مع إضافة <code>N</code> كوسيط للاستعلام query، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2215_22" style=""><span class="pln"> localhost:3000/?n=10 </span></pre>

<p>
	تمثل القيمة<code> 10</code> هنا عينة الاستعلام، ويمكن أن يحتوي المسار الرئيسي على عناوين URL متعددة تنتج طرق عرض مصيّرة، كما يمكن تخزينها مؤقتًا بصورة فردية.
</p>

<p>
	أضف نموذجًا كالتالي يحتوي على عنصر إدخال لإدخال <code>N</code> في الملف <code>views/index.ejs</code>:
</p>

<pre class="ipsCode">...

&lt;p&gt;
  For any number N, find the largest prime number less than or equal to N.
&lt;/p&gt;

&lt;form action="/" method="get"&gt;
  &lt;label&gt;
    N
    &lt;input type="number" name="n" placeholder="e.g. 10" required&gt;
  &lt;/label&gt;
  &lt;button&gt;Find Prime&lt;/button&gt;
&lt;/form&gt;

...
</pre>

<p>
	لاحظ أن إجراء النموذج form يُرسَل إلى المسار الرئيسي <code>/</code>، الذي سيُعالَج بواسطة المسار الرئيسي <code>(...'/')get.app</code> في ملف <code>server.js</code>. وبما أن التابع هو <code>get</code>، فستضاف بيانات <code>n</code> إلى عنوان URL كوسيط للاستعلام. بعد ذلك، عند إجراء طلب باستخدام وسيط استعلام <code>n</code>، ستُمَرَر تلك البيانات إلى القالب.
</p>

<p>
	أضف الأسطر التالية بعد التابع <code>get</code> على الملف <code>server.js</code>، ثم احفظ الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_24" style=""><span class="pun">...</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">n</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> locals </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> n </span><span class="pun">};</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">,</span><span class="pln"> locals</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	تتحقق الأسطر السابقة من وجود طلب له وسيط استعلام ذو القيمة <code>n</code>، فإذا تحقق ذلك يُصيَّر الملف <code>index</code> مع تمرير القيمة <code>n</code> إليه، وإلا فيُعرض الملف <code>index</code> بدون بيانات.
</p>

<p>
	<strong>ملاحظة:</strong> لا يمكن الوثوق دائمًا بالبيانات التي يدخلها المستخدم، لذا لإنشاء تطبيق جاهزًا للنشر يُنصح بالتحقق من المدخَلات باستخدام مكتبة <a href="https://joi.dev/api" rel="external nofollow">joi</a>. لاحظ أن للتابع <code>render</code> وسيط ثاني اختياري <code>locals</code> يُعَرّف المتغيرات المحلية التي تمرر إلى القالب لتصيير العرض. يحدد اسم الواصف المختصر الخاصية <code>n</code> للكائن المحلي <code>locals</code>، فعندما يكون للمتغير نفس اسم واف الكائن المُسند إليه، يمكن حذف اسم المتغير. لذلك يمكن كتابة { n: n } بالشكل { n }.
</p>

<p>
	والآن، أصبح بإمكاننا عرض القالب بعد أن أضفنا إليه البيانات. أضف الأسطر التالية على ملف <code>views/index.ejs</code>:
</p>

<pre class="ipsCode">&lt;% if (locals.n) { %&gt;
  &lt;p&gt;N: &lt;%= n %&gt;&lt;/p&gt;
&lt;% } %&gt;

...
</pre>

<p>
	وهكذا يعرض التطبيق المتغيرات المحلية <code>n</code> إن وجدت. احفظ الملف، ثم أعد تشغيل الخادم لتحديث التطبيق. سيُعرَض النموذج مع زر لإيجاد العدد الأولي Find Prime، حيث يأخذ التطبيق المدخلات من المستخدم ويعرضها تحت النموذج:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="145437" href="https://academy.hsoub.com/uploads/monthly_2024_02/b28fUGz.png.969b6db8b78291b20de660d9e8a66ede.png" rel=""><img alt="b28fugz" class="ipsImage ipsImage_thumbnailed" data-fileid="145437" data-unique="qcq6zzgrb" src="https://academy.hsoub.com/uploads/monthly_2024_02/b28fUGz.thumb.png.8d31bdbf79b16dc4ac93acf6a1e48b39.png"> </a>
</p>

<p>
	أدخل عددًا ما ولاحظ أن العنوان URL سيتغير وسيضاف إليه العدد الذي أدخلته، على سبيل المثال، إن أدخلت العدد <code>40</code> سيصبح العنوان كالتالي: <code><a href="http://localhost:3000/?n=40" ipsnoembed="true" rel="external nofollow">http://localhost:3000/?n=40</a></code> كما سيُعرض العدد الذي أدخلته أسفل النموذج على الشكل التالي <strong>N: 40</strong>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="145443" href="https://academy.hsoub.com/uploads/monthly_2024_02/YV64Q98.png.e4a4640b83c89f88a85e537e9bd6a3e1.png" rel=""><img alt="yv64q98" class="ipsImage ipsImage_thumbnailed" data-fileid="145443" data-unique="74xaekcdc" src="https://academy.hsoub.com/uploads/monthly_2024_02/YV64Q98.thumb.png.50df73635e49220df0ac9d0afb95f03f.png"> </a>
</p>

<p>
	والآن بعد أن أصبح بإمكاننا إدخال قيمة للعدد <code>N</code> وعرضها، علينا إضافة دالة للعثور على أكبر عدد أولي أصغر أو مساوي للعدد <code>N</code>، وعرض النتيجة على الشاشة.
</p>

<p>
	أنشئ مجلد <code>utils</code>، ثم أنشئ الملف <code>utils/findPrime.js</code>. افتح الملف <code>findPrime.js</code> في المحرر ثم أضف الكود التالية لتعريف وظيفة أو دالة للعثور على العدد الأولي، ولا تنسَ حفظ الملف بعدها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_27" style=""><span class="pln">utils</span><span class="pun">/</span><span class="pln">findPrime</span><span class="pun">.</span><span class="pln">js
</span><span class="com">/**
 * Find the largest prime number less than or equal to `n`
 * @param {number} n A positive integer greater than the smallest prime number, 2
 * @returns {number}
 */</span><span class="pln">
module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">let</span><span class="pln"> prime </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln"> </span><span class="com">// initialize with the smallest prime number</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">let</span><span class="pln"> i </span><span class="pun">=</span><span class="pln"> n</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">--)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">let</span><span class="pln"> isPrime </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">let</span><span class="pln"> j </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln"> j </span><span class="pun">&lt;</span><span class="pln"> i</span><span class="pun">;</span><span class="pln"> j</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">i </span><span class="pun">%</span><span class="pln"> j </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        isPrime </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">isPrime</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      prime </span><span class="pun">=</span><span class="pln"> i</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">break</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> prime</span><span class="pun">;</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يوّثق تعليق <a href="https://jsdoc.app/" rel="external nofollow">JSDoc</a> عمل الدالة، إذ تبدأ الخوارزمية بالعدد الأولي الأول 2، ثم ثم تكرر العمل بدءًا من العدد <code>n</code> وتنقص الرقم بمقدار 1 في كل حلقة. وتستمر الدالة في حلقة التكرار والبحث عن عدد أولي حتى يصبح الرقم 2، وهو أصغر رقم أولي.
</p>

<p>
	تفترض كل حلقة أن العدد الحالي هو عدد أولي، ثم تتحقق من صحة هذا الافتراض، عن طريق التحقق من وجود عامل آخر للعدد الحالي آخر غير 1 ونفسه. إذا أمكن قسمة العدد الحالي على أي رقم أكبر من 1 وأقل من العدد نفسه دون باقي، فهو ليس عددًا أوليًا. ثم ستتحق الدالة بعد ذلك من العدد التالي. بعدها، أضف السطر الثاني إلى ملف <code>server.js</code> لاستيراد دالة العدد الأولي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_29" style=""><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> findPrime </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/findPrime'</span><span class="pun">);</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	عدّل على المسار الرئيسي لإيجاد العدد الأولى وتمرير قيمته إلى القالب من خلال إضافة السطر التالي إلى ملف <code>server.js</code>، ثم احفظ الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_31" style=""><span class="kwd">const</span><span class="pln"> prime </span><span class="pun">=</span><span class="pln"> findPrime</span><span class="pun">(</span><span class="pln">n</span><span class="pun">);</span></pre>

<p>
	والآن، لعرض النتيجة في القالب وإظهار قيمة <code>N</code> أضف الشيفرة التالية في ملف <code>views/index.ejs</code>:
</p>

<pre class="ipsCode">&lt;% if (locals.n &amp;&amp; locals.prime) { %&gt;
  &lt;p&gt;
    The largest prime number less than or equal to &lt;%= n %&gt; is &lt;strong&gt;&lt;%= prime %&gt;&lt;/strong&gt;.
  &lt;/p&gt;
&lt;% } %&gt;
...
</pre>

<p>
	بدلًا عن:
</p>

<pre class="ipsCode">&lt;% if (locals.n) { %&gt;
  &lt;p&gt;N: &lt;%= n %&gt;&lt;/p&gt;
&lt;% } %&gt;
</pre>

<p>
	ولا تنسَ حفظ الملف، ثم أعد تشغيل الخادم. اختبر عمل الدالة عن طريق إدخال عدد ما، كالعدد <code>10</code>، ستظهر رسالة:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2215_33" style=""><span class="pln"> The largest prime number less than or equal to 10 is 7 </span></pre>

<p>
	ومفادها أن العدد الأولي الأعظمي الأصغر أو يساوي العدد <code>10</code> هو العدد <code>7</code>.
</p>

<p>
	أصبح بإمكان التطبيق إيجاد وعرض رقم أولي وفقًا لرقم يدخله المستخدم، والآن علينا إضافة زر أعجبني.
</p>

<h3 id="-3">
	إضافة زر أعجبني
</h3>

<p>
	ينتج تطبيقنا حاليًا طرق عرض ونتائج مختلفة بناءً على كل عدد <code>N</code> ندخله، ومن المرجح أن يظل المحتوى كما هو. سيوفر لنا زر <strong>أعجبني Like</strong> طريقة لتحديث محتوى العرض. يوضح لنا هذا الزر الحاجة إلى إبطال طريقة العرض المخزنة مؤقتًا عند تغير محتوياتها، إذ سيفيدنا ذلك عند التخزين المؤقت لطرق العرض المصيّرة لاحقًا في مقالنا. عند استخدام زر "أعجبني"، يجب تخزين بيانات الإعجاب في مكان ما. على الرغم من أن التخزين الثابت persistent storage مثالي، إلا أننا ستخزن الإعجابات في الذاكرة نظرًا لأن إنشاء قاعدة بيانات يقع خارج نطاق مقالنا. هكذا، ستكون البيانات سريعة الزوال، مما يعني أننا سنفقد كل البيانات عندما يتوقف الخادم. افتح ملف <code>server.js</code> وأضف المتغير التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_35" style=""><span class="com">/**
 * Key is `n`
 * Value is the number of 'likes' for `n`
 */</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> likesMap </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{};</span></pre>

<p>
	يُستخدم الغرض <code>likesMap</code> كخريطة لتخزين إعجابات الأعداد. المفتاح هو <code>n</code> وقيمه هي عدد إعجابات <code>n</code>. يجب تهيئة إعجابات العدد عند إرساله. أضف السطر الثاني والثالث على ملف <code>server.js</code> لتهيئة إعجابات <code>N</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_37" style=""><span class="pun">...</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> prime </span><span class="pun">=</span><span class="pln"> findPrime</span><span class="pun">(</span><span class="pln">n</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// Initialize likes for this number when necessary</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">])</span><span class="pln"> likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> locals </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> n</span><span class="pun">,</span><span class="pln"> prime </span><span class="pun">};</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">,</span><span class="pln"> locals</span><span class="pun">);</span><span class="pln">

</span><span class="pun">..</span></pre>

<p>
	تتحقق عبارة <code>if</code> من وجود إعجابات للعدد الحالي. في حالة عدم وجود إعجابات، يُهيَّئ <code>likesMaps</code> على القيمة <code>0</code>. ثم أضف الإعجابات كمتغير محلي للعرض، عن طريق تعديل سطر المتغيرات المحلية ليصبح كما يلي ولا تنسَ حفظ الملف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_39" style=""><span class="kwd">const</span><span class="pln"> locals </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> n</span><span class="pun">,</span><span class="pln"> prime</span><span class="pun">,</span><span class="pln"> likes</span><span class="pun">:</span><span class="pln"> likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> </span><span class="pun">};</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">,</span><span class="pln"> locals</span><span class="pun">);</span></pre>

<p>
	بعد أن أصبح لدينا بيانات الإعجابات، أصبح بإمكاننا عرض قيمتها وإضافة زر <strong>أعجبني</strong>. عدّل على ملف <code>views/index.ejs</code> لإضافة توصيف Markup لزر أعجبني كما يلي:
</p>

<pre class="ipsCode">&lt;% if (locals.n &amp;&amp; locals.prime) { %&gt;
  &lt;p&gt;
    The largest prime number less than or equal to &lt;%= n %&gt; is &lt;strong&gt;&lt;%= prime %&gt;&lt;/strong&gt;.
  &lt;/p&gt;

  &lt;form action="/like" method="get"&gt;
    &lt;input type="hidden" name="n" value="&lt;%= n %&gt;"&gt;
    &lt;input type="submit" value="Like"&gt; &lt;%= likes %&gt;
  &lt;/form&gt;
&lt;% } %&gt;
</pre>

<p>
	يجب أن يبدو ملف <code>views/index.ejs</code> على النحو التالي:
</p>

<pre class="ipsCode">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="utf-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
    &lt;title&gt;Find the largest prime number&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;Find the largest prime number&lt;/h1&gt;

    &lt;p&gt;
      For any number N, find the largest prime number less than or equal to N.
    &lt;/p&gt;

    &lt;form action="/" method="get"&gt;
      &lt;label&gt;
        N
        &lt;input type="number" name="n" placeholder="e.g. 10" required&gt;
      &lt;/label&gt;
      &lt;button&gt;Find Prime&lt;/button&gt;
    &lt;/form&gt;

    &lt;% if (locals.n &amp;&amp; locals.prime) { %&gt;
      &lt;p&gt;
        The largest prime number less than or equal to &lt;%= n %&gt; is &lt;strong&gt;&lt;%= prime %&gt;&lt;/strong&gt;.
      &lt;/p&gt;
      &lt;form action="/like" method="get"&gt;
        &lt;input type="hidden" name="n" value="&lt;%= n %&gt;"&gt;
        &lt;input type="submit" value="Like"&gt; &lt;%= likes %&gt;
      &lt;/form&gt;
    &lt;% } %&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre>

<p>
	لا تنسَ حفظ الملف. ثم أعد تشغيل الخادم. سيظهر على الشاشة زر <strong>أعجبني</strong> له القيمة <code>0</code> بعد ظهور نتيجة العدد الأولي.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="145441" href="https://academy.hsoub.com/uploads/monthly_2024_02/likebutton.png.452a960947df31003a8a912395523652.png" rel=""><img alt="likebutton" class="ipsImage ipsImage_thumbnailed" data-fileid="145441" data-unique="88b6dx9f2" src="https://academy.hsoub.com/uploads/monthly_2024_02/likebutton.thumb.png.c4d9a617f805b6357921f25407697001.png"> </a>
</p>

<p>
	يؤدي النقر فوق زر "أعجبني" إلى إرسال طلب <code>GET</code> إلى المسار <code>like/</code>، بالقيمة الحالية لـ <code>N</code> كوسيط استعلام عبر إدخال غير مرئي. لكن ستحصل على رسالة خطأ <code>404</code> والعبارة <code>Cannot GET /like</code>، لأن التطبيق ليس لديه مسار مطابق بعد. سنضيف الآن المسار اللازم للتعامل مع الطلب، وذلك عبر إضافة الأسطر التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_41" style=""><span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/like'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">n</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]++;</span><span class="pln">

  res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(`/?</span><span class="pln">n</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">n</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	يتحق المسار الجديد من وجود <code>n</code> وفي حال عدم وجودها نعود إلى المسار الرئيسي. أما في حال وجودها، فيزيد التطبيق عدد الإعجابات لهذا العدد. ثم يوجهنا إلى شاشة العرض التي ضغطنا فيها على زر "أعجبني". يجب أن يبدو الملف على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_43" style=""><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> findPrime </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/findPrime'</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'view engine'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'ejs'</span><span class="pun">);</span><span class="pln">

</span><span class="com">/**
 * Key is `n`
 * Value is the number of 'likes' for `n`
 */</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> likesMap </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{};</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">n</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> prime </span><span class="pun">=</span><span class="pln"> findPrime</span><span class="pun">(</span><span class="pln">n</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// Initialize likes for this number when necessary</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">])</span><span class="pln"> likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> locals </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> n</span><span class="pun">,</span><span class="pln"> prime</span><span class="pun">,</span><span class="pln"> likes</span><span class="pun">:</span><span class="pln"> likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> </span><span class="pun">};</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">,</span><span class="pln"> locals</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/like'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">n</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]++;</span><span class="pln">

  res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(`/?</span><span class="pln">n</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">n</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">;</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Example</span><span class="pln"> app is listening on port $</span><span class="pun">{</span><span class="pln">port</span><span class="pun">}.`)</span><span class="pln">
</span><span class="pun">);</span></pre>

<p>
	احفظ الملف، ثم أعد تشغيل التطبيق واختبر زر "أعجبني"، يجب أن يزداد عدد الإعجابات مع كل نقرة.
</p>

<p>
	<strong>ملاحظة:</strong> يمكنك استخدام تابع <code>POST</code> بدلاً من <code>GET</code> لهذا المسار، والذي يوافق نمط <a href="https://ar.wikipedia.org/wiki/%D8%B1%D8%B3%D8%AA_(%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA)" rel="external nofollow">RESTful</a> أكثر لأننا حدثنا أحد الموارد. نستخدم في مقالنا تابع <code>GET</code> بدلاً من التعامل مع طلب <code>POST</code> حتى تتمكن من العمل مع وسطاء استعلام الطلب المألوفة.
</p>

<p>
	أصبح التطبيق مكتملًا الآن ويعمل جيدًا، ويمكنك الاستعداد لنشره على منصة App Platform. في الخطوة التالية، سنُوْدِع الشيفرة في غيت Git، ثم ندفعها إلى مستودع غيت هب GitHub.
</p>

<h2 id="-4">
	الخطوة الثالثة، إنشاء مستودع الشيفرات البرمجية
</h2>

<p>
	سننشئ في هذه الخطوة مستودع شيفرات برمجية Code Repository كي نخزّن فيه الملفات اللازمة للنشر. سنُوْدِع commit الشيفرة في غيت Git، ثم ندفعها push أو نضيفها إلى مستودع غيت هب GitHub، الذي سنستخدمه لنشر التطبيق على App platform.
</p>

<h3 id="-5">
	إيداع الشيفرة في جيت
</h3>

<p>
	سنُودع الشيفرة في جيت كي تصبح جاهزةً لإضافتها أو دفعها إلى جيت هب.
</p>

<p>
	<strong>ملاحظة:</strong> إن لم تسجل الدخول إلى حسابك وتضبط الإعدادات، فاحرص على إعداد <a href="https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-20-04#setting-up-git" rel="external nofollow">غيت</a> و مصادقته حسابك على غيت هب باستخدام بروتوكول <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>.
</p>

<p>
	أولًا، عليك تهيئة مستودع غيت:
</p>

<pre class="ipsCode">git init
</pre>

<p>
	ثم عليك استبعاد اعتماديات التطبيق dependencies، وذلك بإنشاء ملف اسمه <code>gitignore.</code> وإضافة الشيفرة التالية فيه:
</p>

<pre class="ipsCode">.gitignore
node_modules

# macOS file
.DS_Store
</pre>

<p>
	<strong>ملاحظة:</strong> السطر <code>DS_Store.</code> خاص بالأجهزة التي تعمل باستخدام نظام ماك أو إس mac OS ولا حاجة لإضافة إن كان نظام التشغيل مختلفًا. والآن احفظ الملف.
</p>

<p>
	ثم أضف كل الملفات إلى غيت:
</p>

<pre class="ipsCode">git add .
</pre>

<p>
	وأَودِع التغييرات بواسطة الأمر التالي:
</p>

<pre class="ipsCode">git commit -m "Initial commit"
</pre>

<p>
	يُستخدم الخيار <code>m-</code> لتحديد رسالة إيداع من اختيارك. والآن بعد إيداع الشيفرة، ستحصل على خرج مماثل لما يلي:
</p>

<pre class="ipsCode">Output
[main (root-commit) deab84e] Initial commit
 6 files changed, 1259 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 package-lock.json
 create mode 100644 package.json
 create mode 100644 server.js
 create mode 100644 utils/findPrime.js
 create mode 100644 views/index.ejs
</pre>

<p>
	أي أنك أودعت الشيفرة في غيت، والآن عليك دفعها إلى غيت هب.
</p>

<h3 id="-6">
	دفع الشيفرة إلى مستودع غيت هب
</h3>

<p>
	أصبح بإمكانك الآن دفع الشيفرة إلى غيت هب، ثم ربط الشيفرة بمنصة App Platform ونشره. أولاً، سجل الدخول إلى غيت هب من المتصفح و<a href="https://github.com/new" rel="external nofollow">أنشئ مستودعًا جديدًا</a> باسم Express-memcache. ثم أنشئ مستودعًا فارغًا بدون ملفات <code>README</code> أو <code>gitignore.</code> أو ملفات الشهادات. يمكنك جعل المستودع خاصًا أو عامًا. كما يمكنك مراجعة توثيق غيت هب حول <a href="https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-new-repository" rel="external nofollow">كيفية إنشاء مستودع جديد</a>. ثم عد إلى الطرفية، وأضف المستودع الذي أنشأته بمثابة <code>remote origin</code>، وحدّث اسم المستخدم الخاص بك:
</p>

<pre class="ipsCode">git remote add origin https://github.com/your_username/express-memcache.git
</pre>

<p>
	يوجه الأمر السابق غيت إلى المكان الذي يجب عليه إضافة الشيفرة فيه. ثم سمّي الفرع الافتراضي فرعًا رئيسيًا <code>main</code>:
</p>

<pre class="ipsCode">git branch -M main
</pre>

<p>
	ثم أضف الشيفرة إلى مستودعك:
</p>

<pre class="ipsCode">git push -u origin main
</pre>

<p>
	ثم أدخل بياناتك إذا طُلب منك ذلك. وستحصل على خرج مشابه لما يلي:
</p>

<pre class="ipsCode">Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 8 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (10/10), 9.50 KiB | 9.50 MiB/s, done.
Total 10 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/your_username/express-memcache.git
 * [new branch]      main -&gt; main
Branch 'main' set up to track remote branch 'main' from 'origin'.
</pre>

<p>
	وبهذا أصبحت شيفرة التطبيق موجودةً على غيت هب، وأصبح التطبيق جاهزًا للنشر على منصة App Platform.
</p>

<h2 id="appplatform">
	الخطوة الرابعة، نشر التطبيق على منصة App Platform
</h2>

<p>
	سننشر في هذه الخطوة، تطبيقنا على منصة تطبيقات <a href="https://www.digitalocean.com/products/app-platform" rel="external nofollow">App Platform</a>. سننشئ حسابًا على المنصة ونسمح له بالوصول إلى مستودع غيت هب لنشر التطبيق. سنحدّث أولًا إعدادات البيئة كي يُتاح لها قراءة الضبط بواسطة عنوان البيئة <code>PORT</code>.
</p>

<h3 id="-7">
	تحديث إعدادات بيئة التطبيق
</h3>

<p>
	سنسمح في هذه الفقرة لمتغيرات البيئة بالوصول وقراءة إعدادات منفذ التطبيق، لأن هذه الإعدادات ربما تتغير عند النشر، لذا فإن هذه الخطوة ستُمكّن التطبيق من الوصول إلى المنفذ من بيئة منصة التطبيقات. افتح ملف <code>server.js</code> في محرر النصوص البرمجية وتأكد من إضافة الأسطر التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_45" style=""><span class="pun">...</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">;</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Example</span><span class="pln"> app is listening on port $</span><span class="pun">{</span><span class="pln">port</span><span class="pun">}.`)</span><span class="pln">
</span><span class="pun">);</span></pre>

<p>
	تنّص هذه الأسطر على استخدام متغير البيئة <code>port</code> في حال وجوده، أو استخدام المنفذ الافتراضي <code>3000</code>. والآن، أصبح التطبيق قادرًا على القراءة من منفذ بيئة منصة التطبيقات التي سننشره عليها.
</p>

<h3 id="appplatform-1">
	إنشاء ونشر التطبيق على منصة App Platform
</h3>

<p>
	سنُعدّ التطبيق للنشر على منصة App Platform، ولكن تذكر أن هذه الخطوة مأجورة، وعليك دفع رسوم تشغيل التطبيق على منصة App Platform بالثانية (بدءًا من دقيقة واحدة على الأقل). يمكنك الإطلاع على الأسعار في صفحة <strong>المعاينة Review</strong>، كما يمكنك الاطلاع على <a href="https://docs.digitalocean.com/products/app-platform/details/pricing/" rel="external nofollow">نظام تسعير منصة App Platform</a> للحصول على تفاصيل أكثر.
</p>

<p>
	أولاً، سجل الدخول إلى حسابك على DigitalOcean، ثم انقر على إنشاء create من <a href="https://cloud.digitalocean.com/apps/" rel="external nofollow">لوحة تحكم التطبيقات Apps Dashboard</a>. كما يمكنك الاطلاع على <a href="https://docs.digitalocean.com/products/app-platform/how-to/create-apps/" rel="external nofollow">توثيق كيفية إنشاء التطبيقات في منصة App Platform</a>.
</p>

<p>
	ثم ادخل إلى صفحة <strong>Create Resource From Source Code screen</strong>، واختر غيت هب كمزود الخدمة <strong>Service Provider</strong>، وامنح الإذن<br>
	لـDigitalOcean للوصول إلى مستودعك. ننصحك بتحديد المستودع الذي تريد نشره فقط. سُتطالب بتثبيت تطبيق DigitalOcean GitHub. حدد مستودعك من القائمة وانقر فوق <strong>التالي Next</strong>. في شاشة الموارد <strong>Resources</strong>، انقر على تعديل الشريحة <strong>Edit Plan</strong> لتحديد حجم الاشتراك.
</p>

<p>
	سنستخدم في مقالنا الاشتراك الأساسي ذو خدمات الويب الأصغر حجمًا (512 ميغابايت من ذاكرة الوصول العشوائي vCPU | RAM) مخصصة لمورد <strong>express-memcache</strong>. إذ يوفر الاشتراك الأساسي وأصغر خدمة ويب موارد كافيةً لتطبيقنا التدريبي. بعد اختيار الاشتراك، انقر على رجوع <strong>Back</strong>. ثم انقر على علامة تبويب المعلومات <strong>info</strong> في شريط التنقل الأيسر وانتبه على المنطقة التطبيق، إذ سنحتاج إلى ذلك في الخطوة التالية لإضافة <a href="https://marketplace.digitalocean.com/add-ons/memcachier" rel="external nofollow">MemCachier</a> من متجر DigitalOcean. أخيرًا، انقر على نافذة <strong>المعانية Review</strong>، ثم زر إنشاء موارد <strong>Create Resources</strong> لإنشاء التطبيق ونشره. سيستغرق الأمر بعض الوقت لإنشاء التطبيق ونشره. ستصلك رسالة عند الانتهاء فيها رابط التطبيق بعد نشره.
</p>

<p>
	بهذا نكون قد أنشأنا تطبيق Express يمكنه العثور على رقم أولي ويحتوي على زر أعجبني. ثم أَودَعنا شيفرة التطبيق إلى غيت ودفعناه إلى غيت هب، ثم نشرنا التطبيق على App Platform. والآن، لجعل تطبيق Express أسرع وأكثر قابلية للتوسع، سوف ننفذ ثلاث استراتيجيات للتخزين المؤقت للكائنات. ستحتاج إلى ذاكرة تخزين مؤقت Cache، والتي سننشئها في الخطوة التالية.
</p>

<h2 id="memcachier">
	الخطوة الخامسة، إعداد ذاكرة تخزين مؤقت للكائنات باستخدام MemCachier
</h2>

<p>
	في هذه الخطوة، سننشئ ونضبط إعدادات ذاكرة تخزين مؤقت للكائنات. يمكنك استخدام أي ذاكرة تخزين مؤقت متوافقة مع <a href="https://memcached.org/" rel="external nofollow">memcached</a>. سنستخدم في مقالنا <a href="https://marketplace.digitalocean.com/add-ons/memcachier" rel="external nofollow">إضافة MemCachier</a> من متجر DigitalOcean. وذاكرة التخزين المؤقت MemCachier هي عبارة عن ذاكرة تخزين مؤقت تخزن البيانات على هيئة قيمة-مفتاح.
</p>

<p>
	أولًا، علينا إضافة MemCachier من متجر Digital Ocean، وللقيام بذلك انتقل إلى صفحة إضافة <a href="http://marketplace.digitalocean.com/add-ons/memcachier" rel="external nofollow">MemCachier</a>، ثم انقر على <strong>Add MemCachier</strong>، ثم اختر المنطقة المناسبة لتطبيقك، يجب أن تختار المنطقة نفسها للتطبيق والذاكرة كي تخفف من التأخير بقدر الإمكان. كما يمكنك مراجعة إعدادات التطبيق للتأكد من المنطقة واختيار اشتراك معين. ثم، انقر على <strong>Add MemCachier</strong> لإضافة الذاكرة المؤقتة.
</p>

<p>
	<strong>ملاحظة:</strong> لمعرفة اختصارات اسماء المناطق، ننصحك بزيارة <a href="https://docs.digitalocean.com/products/platform/availability-matrix/#available-datacenters" rel="external nofollow">مركز بيانات DigitalOcean</a>، فمثلًا منطقة سان فرانسيسكو اختصارها هو sfo3.
</p>

<p>
	والآن، عليك ضبط إعدادات التطبيق لاستخدام الذاكرة. انتقل إلى <a href="https://cloud.digitalocean.com/add-ons/" rel="external nofollow">لوحة تحكم الإضافات Add-Ons dashboard</a> ثم انقر على إضافة MemCachier للانتقال إلى لوحة التحكم. بعدها، انقر على زر عرض <strong>Show</strong> متغيرات الإعدادات <strong>Configuration Variables</strong> لعرض القيم التالية <code>MEMCACHIER_USERNAME</code>، <code>MEMCACHIER_PASSWORD</code>، <code>MEMCACHIER_SERVERS</code>. انتبه على هذه القيم لأننا سنحتاجها لاحقًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="145442" href="https://academy.hsoub.com/uploads/monthly_2024_02/shown.png.4bd1ce53a8a9cbbba6160610dcb74a7f.png" rel=""><img alt="shown" class="ipsImage ipsImage_thumbnailed" data-fileid="145442" data-unique="6jkh4yifp" src="https://academy.hsoub.com/uploads/monthly_2024_02/shown.thumb.png.f5261c993c44b0243cba7c8198e50d18.png"> </a>
</p>

<p>
	سنحفظ الآن متغيرات إعدادات MemCachier كمتغيرات بيئة للتطبيق. انتقل إلى لوحة تحكم التطبيق ثم انقر على الإعدادات <strong>Settings</strong>. ثم من <strong>Components</strong> انقر على <strong>express-memc</strong> وانتقل لقسم متغيرات البيئة <strong>Environment Variables</strong> وانقر على تعديل <strong>Edit</strong> ثم أضف متغيرات إعدادات MemCachier ذات المفاتيح: (<code>MEMCACHIER_USERNAME</code>، <code>MEMCACHIER_PASSWORD</code>، <code>MEMCACHIER_SERVERS</code>) وأضف القيم كل منها وفقًا للقيم الموجودة في لوحة تحكم MemCachier&gt; انقر على خيار التشفير <strong>Encrypt</strong> بجانب المفتاح <code>MEMCACHIER_PASSWORD</code> لتشفير كلمة المرور، ثم انقر على حفظ <strong>Save</strong>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="145438" href="https://academy.hsoub.com/uploads/monthly_2024_02/envar.png.c6b3046322f9deb4a6fadaccd090a437.png" rel=""><img alt="envar" class="ipsImage ipsImage_thumbnailed" data-fileid="145438" data-unique="7houzunew" src="https://academy.hsoub.com/uploads/monthly_2024_02/envar.thumb.png.7c6e776f7bd5e481ced7935af72e0e1e.png"> </a>
</p>

<p>
	والآن، علينا ضبط عميل ذاكرة memcache client في التطبيق باستخدام المتغيرات التي أدخلنا كي يستطيع التطبيق التخاطب مع الذاكرة. ثبّت المكتبة <code>memjs</code> في الطرفية:
</p>

<pre class="ipsCode">npm install memjs
</pre>

<p>
	ثم أنشأ مجلد خدمات <code>services</code>، وأنشأ فيه الملف <code>services/memcache.js</code> وافتحه في المحرر. ثم استورد مكتبة <code>memjs</code> في أعلى الملف واضبط إعدادات عميل الذاكرة cache client:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_48" style=""><span class="kwd">const</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="typ">Client</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'memjs'</span><span class="pun">);</span><span class="pln">

module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Client</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">MEMCACHIER_SERVERS</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  failover</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
  timeout</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln">
  keepAlive</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	ولا تنسَ حفظ الملف.
</p>

<p>
	تنشأ الشيفرة السابقة عميل لذاكرة التخزين المؤقتة MemCachier. أما بالنسبة للخيار <code>failover</code>، ضبطناه على القيمة <code>true</code> لاستخدام مجموعة حواسيب MemCachier عالية التوفر في حال حدوث الفشل. إذ إنه في حالة فشل الخادم، ستُرسل أوامر المفاتيح المخزنة على هذا الخادم تلقائيًا إلى الخادم التالي المتاح.
</p>

<p>
	ينصح باستخدام مهلة <code>timeout</code> مدتها ثانية واحدة إذ إنها أفضل للتطبيقات المنشورة من المهلة الافتراضية البالغة 0.5 ثانية. أما السطر <code>keepAlive: true</code> فيبقي الاتصال مع ذاكرة التخزين المؤقت مفتوحًا حتى في وضع الخمول، وهو أمر محبّذ لأن عملية إجراء الاتصال بطيئة، ويجب أن تكون ذاكرة التخزين المؤقت سريعة لتكون فعالة. حصلنا في هذه الخطوة على ذاكرة تخزين مؤقت باستخدام إضافة MemCachier من متجر DigitalOcean. ثم أضفنا إعدادات ذاكرة التخزين المؤقت كمتغيرات بيئة في منصة التطبيقات، كي نستطيع إعداد عميل باستخدام مكتبة <code>memjs</code> ويتمكن التطبيق من الاتصال بذاكرة التخزين المؤقت.
</p>

<p>
	والآن، أصبحنا جاهزين لتطبيق للتخزين المؤقت في Express، وهو ما سنتعلمه في خطوتنا التالية.
</p>

<h2 id="expressmemcachier">
	الخطوة السادسة، تطبيق تقنيات التخزين المؤقت في Express باستخدام إضافة MemCachier
</h2>

<p>
	أصبح بإمكانك الآن استخدام ذاكرة التخزين المؤقت للكائنات بعد نشر التطبيق وإضافة ميزة التخزين المؤقت MemCachier. إذ سننفذ في هذه الخطوة ثلاث تقنيات للتخزين المؤقت للكائنات. سنبدأ بالتخزين المؤقت للعمليات ذات الاستخدام الكثيف للموارد لتحسين سرعة الاستخدام وكفاءته. بعد ذلك، سنطبق تقنيات لتخزين طرق العرض المصيّرة بعد الإدخال لتحسين معالجة الطلب وتخزين الجلسات القصيرة مؤقتًا لإتاحة المجال لتوسيع نطاق تطبيقك فيما بعد.
</p>

<h3 id="-8">
	التخزين المؤقت للعمليات ذات الاستخدام الكثيف للموارد
</h3>

<p>
	سنخزن العمليات الحوسبية ذات الاستخدام الكثيف للموارد تخزينًا مؤقتًا لتسريع التطبيق، مما يؤدي إلى استخدام وحدة المعالجة المركزية بكفاءة أكبر. إذ تُعد الدالة <code>findPrime</code> عملية حسابية تستخدم الموارد بكثافة عند إدخال عدد كبير. سنخزن نتائج الدالة مؤقتًا عند توفرها بدلاً من تكرار العملية الحسابية. أولاً، افتح ملف <code>server.js</code> لإضافة عميل الذاكرة <code>memcache</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_50" style=""><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> findPrime </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/findPrime'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> memcache </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./services/memcache'</span><span class="pun">);</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	ثم خزّن عددًا أوليًا محسوبًا من قبل في الذاكرة المؤقتة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_52" style=""><span class="pun">...</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> prime </span><span class="pun">=</span><span class="pln"> findPrime</span><span class="pun">(</span><span class="pln">n</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> </span><span class="str">'prime_'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> n</span><span class="pun">;</span><span class="pln">

  memcache</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> prime</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">(),</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> expires</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	ولا تنسَ حفظ الملف. لاحظ أن التابع <code>set</code> يقبل مفتاحًا key كوسيط أول، وقيمة للسلسلة أي value كوسيطه الثاني. لذا حولنا العدد الأولي إلى سلسلة، أما الشرط الثالث فيحرص على عدم انتهاء مدة العنصر المُخزّن، والشرط الرابع والأخير هو تابع استدعاء اختياري عند حدوث خطأ ما.
</p>

<p>
	<strong>ملاحظة1:</strong> يجب التعامل مع أخطاء ذاكرة التخزين المؤقت بحذر. إذ تعد هذه ذاكرة بمثابة تحسين ويجب ألا تؤدي إلى تعطل التطبيق. في من الممكن أن يعمل التطبيق على نحو جيد، وإن كان أبطأ، بدون ذاكرة التخزين المؤقت.
</p>

<p>
	<strong>ملاحظة 2:</strong> بإمكان التطبيق الآن العمل محليًا ولكن بدون تخزين مؤقت، وسيظهر خطأ في الخرج عند استدعاء <code>memcache.set</code> لأن التطبيق لن يستمكن من إيجاد خادم تخزين مؤقت، وستحصل حينها على خرج مشابه للتالي:
</p>

<pre class="ipsCode">Output
MemJS: Server &lt;localhost:11211&gt; failed after (2) retries with error - connect ECONNREFUSED 127.0.0.1:11211
Error: No servers available
...
</pre>

<p>
	لن تحتاج إلى التخزين المحلي لإكمال باقي خطوات المقال. إذا يمكنك تشغيل <code>localhost:11211</code> في العنوان الذي يُعد العنوان الافتراضي لـ <code>memjs</code>.
</p>

<p>
	والآن، عليك إيداع التغييرات:
</p>

<pre class="ipsCode">git add . &amp;&amp; git commit -m "Add memjs client and cache prime number"
</pre>

<p>
	ثم أضف هذه التغييرات إلى غيت هب، والتي ستُنشر إلى منصة التطبيقات تلقائيًا:
</p>

<pre class="ipsCode">git push
</pre>

<p>
	ستظهر رسالة على لوحة تحكم منصة التطبيقات تشير إلى أن تطبيقك قيد الإنشاء، بعد أن كانت حالته <strong>Deployed</strong> أي منشور. وعند اكتمال إنشاء التطبيق، افتح التطبيق في متصفحك وأدخل عددًا للعثور على أكبر عدد أولي أصغر أو مساوٍ له.
</p>

<p>
	<strong>ملاحظة:</strong> قد تظهر رسالة "Waiting for service" على لوحة التحكم، وتعني حالة انتظار الخدمة، وهذه الرسالة ستختفى من تلقاء نفسها، ولكن إن طال ظهورها عليك تحديث التطبيق للتأكد أن التطبيق قد نُشر بعد اكتمال إنشائه.
</p>

<p>
	والآن، عليك الرجوع إلى صفحة <a href="https://cloud.digitalocean.com/add-ons/" rel="external nofollow">إضافات لوحة التحكم</a> ثم النقر على <strong>View MemCachier</strong> لعرض لوحة تحليلات ذاكرة التخزين المؤقت.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="145440" href="https://academy.hsoub.com/uploads/monthly_2024_02/KhcUKJG.png.0383892222122bc95a79d326d9a4f8f0.png" rel=""><img alt="khcukjg" class="ipsImage ipsImage_thumbnailed" data-fileid="145440" data-unique="co0570fxt" src="https://academy.hsoub.com/uploads/monthly_2024_02/KhcUKJG.thumb.png.710b5a70d104a8b5ba9ea63f071cc093.png"> </a>
</p>

<p>
	في هذه اللوحة، زدنا الخيار <strong>Set Cmds</strong> في لوحة <strong>All Time Stats</strong> وحالة العناصر <strong>Items</strong> في لوحة التخزين <strong>Storage</strong> بمقدار <code>1</code>.<br>
	في كل مرة ترسل عددًا، سيزداد كل من <strong>Set Cmds</strong> و<strong>Items</strong>. ويجب عليك الضغط على زر التحديث لتحميل الإحصائيات الجديدة.
</p>

<p>
	<strong>ملاحظة:</strong> يُعد التحقق من سجلات التطبيق على منصة App Platform أمرًا مفيدًا لتصحيح الأخطاء. يمكنك النقر من لوحة تحكم التطبيق على <strong>Runtime Logs</strong> لعرض الأخطاء.
</p>

<p>
	يمكن الاستفادة من العناصر المخزنة في الذاكرة المؤقتة. ستتحقق الآن مما إذا كان العنصر قد خُزّن مسبقًا، وإذا كان الأمر كذلك، فسوف يُعرض من ذاكرة التخزين المؤقت، وإلا سيبحث التطبيق عن العدد الأولي كما في السابق. عّدل ملف <code>server.js</code>، إذ عليك تعديل بعض الأسطر وإضافة بعد الاسطر الجديدة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_54" style=""><span class="pun">...</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">n</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">let</span><span class="pln"> prime</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> </span><span class="str">'prime_'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> n</span><span class="pun">;</span><span class="pln">

  memcache</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">val </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// Use the value from the cache</span><span class="pln">
      </span><span class="com">// Convert Buffer string before converting to number</span><span class="pln">
      prime </span><span class="pun">=</span><span class="pln"> parseInt</span><span class="pun">(</span><span class="pln">val</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">());</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// No cached value available, find it</span><span class="pln">
      prime </span><span class="pun">=</span><span class="pln"> findPrime</span><span class="pun">(</span><span class="pln">n</span><span class="pun">);</span><span class="pln">

      memcache</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> prime</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">(),</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> expires</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">// Initialize likes for this number when necessary</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">])</span><span class="pln"> likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> locals </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> n</span><span class="pun">,</span><span class="pln"> prime</span><span class="pun">,</span><span class="pln"> likes</span><span class="pun">:</span><span class="pln"> likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> </span><span class="pun">};</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">,</span><span class="pln"> locals</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	تهيئ الشبفرة السابقة العدد الأولي <code>prime</code> بدون قيمة، باستخدام الكلمة المفتاحية <code>Let</code>، حيث أُعيدَ تعيين قيمتها. ثم يحاول تابع <code>memcache.get</code> استرداد العدد الأولي المخزن. توجد معظم شيفرة المتحكم الآن في تابع الاستدعاء <code>memcache.get</code> لأن نتيجته لازمة لتحديد كيفية التعامل مع الطلب. فإن كان ثمة قيمة مخزنة متاحة، فعليه استخدامها، وإلا عليه إجراء الحسابات اللازمة للعثور على العدد الأولي وتخزين النتيجة في ذاكرة التخزين المؤقت. إن القيمة التي يستدعيها تابع الاستدعاء <code>memcache.get</code> هي قيمة تخزين مؤقتة <a href="https://nodejs.org/api/buffer.html" rel="external nofollow">Buffer</a>، لذا عليك تحويلها إلى سلسلة قبل تحويل التابع <code>prime</code> إلى عدد. أودع التغييرات ثم ادفعها إلى غيت هب لتنشرها:
</p>

<pre class="ipsCode">git add . &amp;&amp; git commit -m "Check cache for prime number" &amp;&amp; git push
</pre>

<p>
	عندما ترسل رقمًا لم يخزّن بعد في التطبيق، ستزداد إحصائيات <strong>Set Cmds</strong> وحالة العناصر <strong>Items</strong> وإحصائيات الأخطاء <strong>get misses</strong> في لوحة تحكم MemCachier بمقدار 1. تحدث حالة الخطأ miss عند محاولة الحصول على العنصر من ذاكرة التخزين المؤقت قبل تعيينه، فالعنصر غير موجود في ذاكرة التخزين المؤقت، مما يؤدي إلى حدوث خطأ، ثم يخزّن العنصر بعد ذلك. عند إدخال عدد مخزّن مسبقًا، سيزداد عدد مرات الحصول على النتائج <strong>get hits</strong>.
</p>

<p>
	وهكذا نكون قد خزّنا العمليات ذات الاستخدام الكثيف للموارد تخزينًا مؤقتًا. بعد ذلك، سنخزّن طرق عرض التطبيق المصيّرة مؤقتًا.
</p>

<h3 id="-9">
	تخزين طرق العرض المصيّرة
</h3>

<p>
	سنخزّن الآن طرق عرض التطبيق مؤقتًا باستخدام برنامج وسيط. أعددنا في خطوة سابقة <code>ejs</code> كمولد قوالب وأنشأنا قالبًا لعرض كل رقم مُدخل n. قد يستهلك إنشاء العرض كثيرًا من الموارد، لذا فإن تخزينه مؤقتًا يمكن أن يسرع معالجة الطلب ويستخدم موارد أقل.
</p>

<p>
	أولًا، أنشئ مجلدًا للبرنامج الوسيط وسمّه <code>middleware</code>. ثم أنشئ الملف <code>middleware/cacheView.js</code> وافتحه في المحرر. ثم أضف هذه الأسطر في ملف <code>CacheView.js</code> لدالة البرنامج الوسيط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_56" style=""><span class="kwd">const</span><span class="pln"> memcache </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../services/memcache'</span><span class="pun">);</span><span class="pln">

</span><span class="com">/**
 * Express middleware to cache views and serve cached views
 */</span><span class="pln">
module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">view_$</span><span class="pun">{</span><span class="pln">req</span><span class="pun">.</span><span class="pln">url</span><span class="pun">}`;</span><span class="pln">

  memcache</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">val </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// Convert Buffer string to send as the response body</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">val</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">());</span><span class="pln">
      </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	استوردنا في الشيفرة السابقة عميل الذاكرة <code>memcache</code>. ثم عرفنا عن مفتاح، مثل <code>view_/?n=100</code>. وبعدها، تحققنا من وجود عرض لهذا المفتاح في ذاكرة التخزين المؤقت باستخدام التابع <code>memcache.get</code>. إذا لم يكن هناك خطأ ووُجدت قيمة لهذا المفتاح، ينتهي الطلب عن طريق إرسال العرض مرةً أخرى إلى العميل.
</p>

<p>
	بعد ذلك، إذا لم يكن العرض مخزنًا، فعليك تخزينه مؤقتًا، عن طريق تعديل override التابع <code>res.send</code> بإضافة الأسطر التالية على ملف <code>middleware/cacheView.js</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_58" style=""><span class="kwd">const</span><span class="pln"> originalSend </span><span class="pun">=</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">;</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">send </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">body</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      memcache</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> expires</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">

      originalSend</span><span class="pun">.</span><span class="pln">call</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">};</span></pre>

<p>
	يجب أن يصبح محتوى الملف <code>middleware/cacheView.js</code> كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_60" style=""><span class="kwd">const</span><span class="pln"> memcache </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'../services/memcache'</span><span class="pun">);</span><span class="pln">

</span><span class="com">/**
 * Express middleware to cache views and serve cached views
 */</span><span class="pln">
module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">view_$</span><span class="pun">{</span><span class="pln">req</span><span class="pun">.</span><span class="pln">url</span><span class="pun">}`;</span><span class="pln">

  memcache</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">val </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// Convert Buffer string to send as the response body</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">val</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">());</span><span class="pln">
      </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> originalSend </span><span class="pun">=</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">;</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">send </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">body</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      memcache</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> expires</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">

      originalSend</span><span class="pun">.</span><span class="pln">call</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يمكنك تعديل التابع <code>res.send</code> باستعمال دالة تخزن العرض في ذاكرة التخزين المؤقت قبل استدعاء دالة الإرسال الأساسية <code>send</code> كالمعتاد. إذ يمكنك استدعاء دالة الإرسال الأساسية <code>send</code> باستخدام تابع الاستدعاء <code>call</code> الذي يستخدم <code>this</code> والذي يعبر عن context الحالي بعد تعليمة الذاكرة <code>memcache.set</code>. احرص على استخدام <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function#parameters" rel="external nofollow">دالة مجهولة</a> (وليس <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions" rel="external nofollow">دالة سهمية</a>)، كي تحدد قيمة <code>this</code> الصحيحة. بعد ذلك، مرر التحكم إلى البرنامج الوسيط عن طريق إضافة الأمر next:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_62" style=""><span class="pun">...</span><span class="pln">

</span><span class="com">/**
 * Express middleware to cache views and serve cached views
 */</span><span class="pln">
module</span><span class="pun">.</span><span class="pln">exports </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">view_$</span><span class="pun">{</span><span class="pln">req</span><span class="pun">.</span><span class="pln">url</span><span class="pun">}`;</span><span class="pln">

  memcache</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">val </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// Convert Buffer to UTF-8 string to send as the response body</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">val</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">());</span><span class="pln">
      </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> originalSend </span><span class="pun">=</span><span class="pln"> res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">;</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">send </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">body</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      memcache</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> expires</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">

      originalSend</span><span class="pun">.</span><span class="pln">call</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">,</span><span class="pln"> body</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">

    next</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	إن إضافة التابع <code>next</code> يؤدي استدعاء دالة البرنامج الوسيط التالي في التطبيق. لكن لا يوجد في مثالنا برامج وسيط آخر، لذلك يُستدعى المتحكم controller. يوّلد التابع <code>res.render</code> الخاص بـ Express صفحة عرض view، ثم يستدعي التابع <code>res.send</code> داخليًا باستخدام طريقة العرض. والآن، في وحدة تحكم المسار الرئيسي، يُستدعى تابع التعديل override عند استدعاء <code>res.render</code>، ليخزّن العرض في ذاكرة التخزين المؤقت قبل استدعاء دالة <code>send</code> الأصلية لإكمال الرد.
</p>

<p>
	<strong>ملاحظة:</strong> يمكنك تمرير استدعاء للتابع <code>render</code> في المتحكم، ولكن عليك تكرار شيفرة التخزين المؤقت للعرض في وحدة التحكم لكل مسار يخزن مؤقتًا. والآن، استورد البرنامج الوسيط في الملف <code>server.js</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_64" style=""><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> findPrime </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/findPrime'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> memcache </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./services/memcache'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> cacheView </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./middleware/cacheView'</span><span class="pun">);</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	ثم أضف الوسيط <code>cacheView</code> لاستخدامه في المسار الرئيسي مع التابع <code>/ Get</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_66" style=""><span class="pun">...</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> cacheView</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	ثم أودع التغييرات وادفعها إلى غيت هب لنشرها:
</p>

<pre class="ipsCode">git add . &amp;&amp; git commit -m "Add view caching" &amp;&amp; git push
</pre>

<p>
	عند إدخال عدد جديد، ستزداد إحصائيات لوحة تحكم MemCachier لكل من <strong>Set Cmds</strong> و<strong>Items</strong> و<strong>get misses</strong> بمقدار اثنين: مرة لحساب العدد الأولي ومرة للعرض. عند تحديث التطبيق بنفس الرقم، فسترى زيادةً بمقدار 1 في <strong>get hit</strong> لوحة التحكم. لأن صفحة العرض استُرِدت من ذاكرة التخزين المؤقت، لذلك لا توجد حاجة لجلب نتيجة العدد الأولي من التطبيق <strong>ملاحظة:</strong> إن خيار <code>view cache</code> مفعّل تلقائيًا في الإنتاج، لكن هذا الخيار لا يخزن محتوى خرج القالب، بل يخزن القالب فقط.ويعاد عرض الصفحة مع كل طلب، حتى عندما تكون ذاكرة التخزين المؤقت قيد التشغيل. إذًا فخيار <code>view cache</code>، مختلف ولكنه مكمل للتخزين المؤقت للعرض الذي طبقناه.
</p>

<p>
	قد تلاحظ بعد أن خزّنا العرض أن زر الاعجاب لا يعمل. إذا سجلت عدد الإعجابات، فستلاحظ أنها تتغير وتزداد، لكن العرض المخزّن بحاجة إلى التحديث عندما يتغير عدد الإعجابات. إذًا يجب إيقاف العرض المخزن مؤقتًا عند تغيير العرض.
</p>

<p>
	ثم سنوقف العرض المخزّن عندما يتغير عدد الإعجابات <code>likes</code> وذلك عن طريق حذفه من ذاكرة التخزين المؤقت. عدّل دالة إعادة التوجيه <code>redirect</code> في ملف <code>server.js</code> عن طريق إضافة الأسطر التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_68" style=""><span class="pln">likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]++;</span><span class="pln">

  </span><span class="com">// The URL of the page being 'liked'</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`/?</span><span class="pln">n</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">n</span><span class="pun">}`;</span><span class="pln">

  res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span></pre>

<p>
	لتصبح الدالة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_70" style=""><span class="pun">...</span><span class="pln">
app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/like'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">n</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]++;</span><span class="pln">

  </span><span class="com">// The URL of the page being 'liked'</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`/?</span><span class="pln">n</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">n</span><span class="pun">}`;</span><span class="pln">

  res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	بعد تغير عدد الاعجابات في صفحة العرض، ستصبح النسخة المخزنة غير صالحة، لذا أضف الأسطر التالية لحذف عدد الإعجابات من ذاكرة التخزين المؤقت عندما تتغير الإعجابات <code>likes</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_72" style=""><span class="pun">...</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`/?</span><span class="pln">n</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">n</span><span class="pun">}`;</span><span class="pln">

  </span><span class="com">// The view for this URL has changed, so the cached version is no longer valid, delete it from the cache.</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">view_$</span><span class="pun">{</span><span class="pln">url</span><span class="pun">}`;</span><span class="pln">
  memcache</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	يجب أن يكون ملف <code>server.js</code> مماثلًا لما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_74" style=""><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> findPrime </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/findPrime'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> memcache </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./services/memcache'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> cacheView </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./middleware/cacheView'</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> app </span><span class="pun">=</span><span class="pln"> express</span><span class="pun">();</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'view engine'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'ejs'</span><span class="pun">);</span><span class="pln">

</span><span class="com">/**
 * Key is `n`
 * Value is the number of 'likes' for `n`
 */</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> likesMap </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{};</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">,</span><span class="pln"> cacheView</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">n</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">let</span><span class="pln"> prime</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">const</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> </span><span class="str">'prime_'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> n</span><span class="pun">;</span><span class="pln">

  memcache</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">val </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// Use the value from the cache</span><span class="pln">
      </span><span class="com">// Convert Buffer string before converting to number</span><span class="pln">
      prime </span><span class="pun">=</span><span class="pln"> parseInt</span><span class="pun">(</span><span class="pln">val</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">());</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// No cached value available, find it</span><span class="pln">
      prime </span><span class="pun">=</span><span class="pln"> findPrime</span><span class="pun">(</span><span class="pln">n</span><span class="pun">);</span><span class="pln">

      memcache</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> prime</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">(),</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> expires</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">},</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="com">// Initialize likes for this number when necessary</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">])</span><span class="pln"> likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">const</span><span class="pln"> locals </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> n</span><span class="pun">,</span><span class="pln"> prime</span><span class="pun">,</span><span class="pln"> likes</span><span class="pun">:</span><span class="pln"> likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> </span><span class="pun">};</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">render</span><span class="pun">(</span><span class="str">'index'</span><span class="pun">,</span><span class="pln"> locals</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/like'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">query</span><span class="pun">.</span><span class="pln">n</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  likesMap</span><span class="pun">[</span><span class="pln">n</span><span class="pun">]++;</span><span class="pln">

  </span><span class="com">// The URL of the page being 'liked'</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> url </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`/?</span><span class="pln">n</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">n</span><span class="pun">}`;</span><span class="pln">

  </span><span class="com">// The view for this URL has changed, so the cached version is no longer valid, delete it from the cache.</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> key </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">view_$</span><span class="pun">{</span><span class="pln">url</span><span class="pun">}`;</span><span class="pln">
  memcache</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  res</span><span class="pun">.</span><span class="pln">redirect</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> port </span><span class="pun">=</span><span class="pln"> process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">PORT </span><span class="pun">||</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">;</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span class="pln">port</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Example</span><span class="pln"> app is listening on port $</span><span class="pun">{</span><span class="pln">port</span><span class="pun">}.`)</span><span class="pln">
</span><span class="pun">);</span></pre>

<p>
	لا تنسَ حفظ الملف، ثم أودع التغييرات وادفعها للنشر:
</p>

<pre class="ipsCode">git add . &amp;&amp; git commit -m "Delete invalid cached view" &amp;&amp; git push
</pre>

<p>
	والآن سيعمل زر الاعجاب في التطبيق وستتغير الإحصائيات التالية في لوحة تحكم MemCachier عند النقر على الزر:
</p>

<ul>
	<li>
		<strong>delete hits</strong> تزداد عند حذف العرض.
	</li>
	<li>
		<strong>get misses</strong> تزداد عند حذف العرض وعدم وجوده في ذاكرة التخزين المؤقت.
	</li>
	<li>
		<strong>get hits</strong> تزداد عند العثور على العدد الأولي في ذاكرة التخزين المؤقت.
	</li>
	<li>
		<strong>Set Cmds</strong> تزداد عند إضافة العرض إلى ذاكرة التخزين المؤقت.
	</li>
	<li>
		<strong>Items</strong> تبقى على حالها عند حذف أو إضافة العرض. وهكذا نكون قد طبقنا التخزين المؤقت للعرض وأبطلنا استخدام صفحات العرض المخزنة عند تغييرها. أما التقنية الأخيرة التي سننفذها هي التخزين المؤقت للجلسة.
	</li>
</ul>

<h3 id="-10">
	التخزين المؤقت للجلسات
</h3>

<p>
	سنتعلم في هذه الفقرة كيفية إضافة جلسات Sessions وتخزينها مؤقتًا في تطبيق Express، مما يجعل ذاكرة التخزين المؤقت مخزنًا للجلسات. وحيث إن إحدى الاستخدامات الشائعة للجلسات هي تسجيل دخول المستخدم user login، لذا يمكننا اعتبار هذه الفقرة خطوةً أوليةً لتنفيذ نظام تسجيل دخول المستخدم في المستقبل (على الرغم من أن نظام تسجيل دخول المستخدم خارج عن نطاق مقالنا). يمكن أن يكون تخزين الجلسات قصيرة الأمد في ذاكرة التخزين المؤقت أسرع وأكثر قابليةً للتوسع من تخزينها في قواعد البيانات.
</p>

<p>
	<strong>ملاحظة:</strong> تعتبر ذاكرة التخزين المؤقت مثالية لتخزين الجلسات القصيرة التي انتهت مدتها. ومع ذلك، فإن ذاكرة التخزين المؤقت ليست دائمة؛ لذا تعد حلول التخزين الدائمة كقواعد البيانات أكثر ملاءمةً لتخزين الجلسات طويلة الأمد.
</p>

<p>
	والآن، ثبّت أداة <a href="https://github.com/expressjs/session" rel="external nofollow">الجلسات express-session</a> كي تضيف جلسات إلى التطبيق وإلى مكتبة <a href="https://github.com/liamdon/connect-memjs" rel="external nofollow">connect-memjs</a> لاستخدام MemCachier كمخزن للجلسات:
</p>

<pre class="ipsCode">npm install express-session connect-memjs
</pre>

<p>
	استورد ملف <code>express-session</code> و <code>connect-memjs</code> إلى ملف <code>server.js</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_76" style=""><span class="kwd">const</span><span class="pln"> express </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> findPrime </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./utils/findPrime'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> memcache </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./services/memcache'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> cacheView </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'./middleware/cacheView'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> session </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'express-session'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> </span><span class="typ">MemcacheStore</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'connect-memjs'</span><span class="pun">)(</span><span class="pln">session</span><span class="pun">);</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	تُمرَر جلسة البرنامج الوسيط يتم تمرير البرنامج الوسيط للجلسة إلى وحدة الاتصال <code>connect</code> في memcached، مما يسمح لها بالوراثة من <code>Express.session.Store</code>.
</p>

<p>
	عدّل إعدادات البرنامج الوسيط للجلسة كي يستخدا ذاكرة التخزين المؤقت كمخزن له، وذلك بإضافة الأسطر التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_78" style=""><span class="pun">...</span><span class="pln">

app</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'view engine'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'ejs'</span><span class="pun">);</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">
  session</span><span class="pun">({</span><span class="pln">
    secret</span><span class="pun">:</span><span class="pln"> </span><span class="str">'your-session-secret'</span><span class="pun">,</span><span class="pln">
    resave</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">,</span><span class="pln">
    saveUninitialized</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    store</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">MemcacheStore</span><span class="pun">({</span><span class="pln">
      servers</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">MEMCACHIER_SERVERS</span><span class="pun">],</span><span class="pln">
      prefix</span><span class="pun">:</span><span class="pln"> </span><span class="str">'session_'</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}),</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	يستخدم <code>secret</code> لتسجيل ملفات تعريف الارتباط للجلسة أو ما يُعرف باسم cookie. لا تنسَ استبدال <code>your-session-secret</code> بسلسلة مميزة. <strong>ملاحظة:</strong> يجب استخدام متغيرات البيئة لضبط <code>secret</code> لمراحل الإنتاج، وذلك بإضافة السطر
</p>

<pre class="ipsCode">secret: process.env.SESSION_SECRET || 'your-session-secret'
</pre>

<p>
	لكن عندها يجب ضبط متغير البيئة في لوحة تحكم App Platform. يجبر الأمر <code>Resave</code> الجلسة على إعادة الحفظ إذا تُعدَل أثناء الطلب. ولأننا لا نريد تخزين العنصر في الذاكرة التخزين المؤقت مرة أخرى، لذا علينا ضبط قيمته على <code>false</code>. saveUninitialized: false is useful when you only want to save modified sessions, as is often the case with login sessions where a user property might be added to the session after authentication. In this case, you will store all sessions indiscriminately, so you set it to true. يُعد الأمر <code>saveUninitialized: false</code> مفيدًا لحفظ الجلسات المعدلة فقط، كما هو الحال غالبًا مع جلسات تسجيل الدخول، حيث يمكن إضافة خاصية مستخدم إلى الجلسة بعد المصادقة، حينها سنخزّن جميع الجلسات بصورة عشوائية، لذلك سنضبطها على القيمة <code>true</code>.
</p>

<p>
	أخيرًا، اربط قيمة <code>store</code> مع ذاكرة التخزين المؤقتة، واضبط بادئة ذاكرة التخزين المؤقت للجلسة على <code>._session</code> وهذا يعني أن مفتاح عنصر الجلسة في الذاكرة سيبدو كالتالي: <code>session_&lt;session ID&gt;</code>.
</p>

<p>
	بعدها، أضف برامجًا وسيطًا لتصحيح الأخطاء على مستوى التطبيق، والتي ستساعد في تحديد الجلسات المخزنة قيد التنفيذ:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2215_80" style=""><span class="pun">...</span><span class="pln">

app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="pln">
  session</span><span class="pun">({</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">);</span><span class="pln">

</span><span class="com">/**
 * Session sanity check middleware
 */</span><span class="pln">
app</span><span class="pun">.</span><span class="pln">use</span><span class="pun">(</span><span class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">,</span><span class="pln"> next</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Session ID:'</span><span class="pun">,</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">id</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// Get the item from the cache</span><span class="pln">
  memcache</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(`</span><span class="pln">session_$</span><span class="pun">{</span><span class="pln">req</span><span class="pun">.</span><span class="pln">session</span><span class="pun">.</span><span class="pln">id</span><span class="pun">}`,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">val </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Session from cache:'</span><span class="pun">,</span><span class="pln"> val</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">());</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  next</span><span class="pun">();</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="pun">...</span></pre>

<p>
	وهكذا سيسجل البرنامج الوسيط معرف الجلسة لكل طلب. ثم سيحصل على جلسة هذا المعرف من ذاكرة التخزين المؤقت ويسجل محتوياتها. حيث توضح هذه الطريقة أن الجلسات تعمل وتخزّن. احفظ الملف، ثم أودع التغييرات وادفعها للنشر:
</p>

<pre class="ipsCode">git add . &amp;&amp; git commit -m "Add session caching" &amp;&amp; git push
</pre>

<p>
	أدخل رقمًا في التطبيق ثم تحقق من سجلات وقت التشغيل **Runtime Logs ** في لوحة التحكم للوصول إلى رسائل تصحيح الأخطاء. ستجد معرف الجلسة والقيمة التي سجلتها، مما يوضح أن الجلسات تعمل وتخزّن بصورة صحيحة. في لوحة تحكم MemCachier، عند تخزين العرض والجلسة، سترى ثلاث نتائج لكل تحديث للصفحة: واحدة لصفحة العرض، وواحدة للجلسة، وواحدة لجلسة برنامج تصحيح الأخطاء الوسيط. والآن، يمكنك التوقف عند هذه الخطوة، أو يمكنك الاطلاع على الخطوة السابعةالإضافية.
</p>

<h3 id="-11">
	الخطوة السابعة، حذف الموارد (اختيارية)
</h3>

<p>
	ثمة رسوم عليك دفعها عند نشر التطبيق على منصة App platform، لذا يمكنك حذف التطبيق وإضافة MemCachier عند الانتهاء. انقر على الإجراءات <strong>Actions</strong> من لوحة التحكم ثم انقر على حذف التطبيق <strong>Destroy App</strong>. لحذف إضافة MemCachier، انقر على الإضافات <strong>Add-Ons</strong>، ثم على MemCachier، وانقر بعدها على الإعدادات والحذف <strong>Settings and Destroy</strong>. تلغى ذاكرة MemCachier المجانية بعد 30 يومًا من عدم استخدامها، ولكن يُعد تنظيف الادوات أمرًا جيدًا.
</p>

<h2 id="-12">
	ختامًا
</h2>

<p>
	تعلمنا في مقالنا كيفية إنشاء تطبيق Express للعثور على عدد أولي باستخدام وكيفية إضافة زر "أعجبني". ثم رفعنا هذا التطبيق على غيت هب ونشرناه على منصة تطبيقات DigitalOcean. ثم سرّعنا التطبيق وجعلناه قابلًا للتوسع من خلال تنفيذ ثلاث تقنيات للتخزين المؤقت للكائنات باستخدام إضافة MemCachier للتخزين المؤقت للعمليات ذات الاستخدام الكثيفة للموارد، ومن خلال العرض view، والجلسات sessions.
</p>

<p>
	يمكنك الاطلاع على ملفات هذا المقال في <a href="https://github.com/do-community/express-memcache" rel="external nofollow">مستودع مجتمع DigitalOcean</a>، تحتوي المفاتيح في كل استراتيجيات التخزين المؤقت التي نفذناها على بادئة: <code>_prime</code> و  <code>_view</code> و<code> _session</code> prime_ و view_ وsession_. بالإضافة إلى ميزة فضاء الاسم، توفر البادئة فائدةً إضافيةً وهي السماح بتحديد أداء ذاكرة التخزين المؤقت.
</p>

<p>
	استخدمنا في مقالنا شريحة المطورين developer plan في MemCachier، ولكن يمكنك تجربة <a href="https://marketplace.digitalocean.com/add-ons/memcachier#plans" rel="external nofollow">شريحة مُدارة بالكامل</a> تأتي مع مجموعة ميزات االفحص الدقيق Introspection، مما يتيح لك تتبع أداء البادئات الفردية. على سبيل المثال، يمكنك مراقبة معدل النقر <strong>hit rate</strong> أو نسبة النقر <strong>hit ratio</strong> على أي بادئة، مما يوفر رؤية تفصيلية لأداء ذاكرة التخزين المؤقت. إن أردت متابعة استخدام MemCachier، يمكنك مراجعة <a href="https://www.memcachier.com/documentation/getting-started" rel="external nofollow">التوثيقات التالية</a>.
</p>

<p>
	إذا واجهت مشاكلًا عند تطبيق إحدى هذه الخطوات، يمكنك الحصول على الدعم والمساعدة عبر إضافة سؤالك في قسم الأسئلة والأجوبة في <a href="https://academy.hsoub.com/questions/" rel="">أكاديمية حسوب</a>.
</p>

<p>
	ترجمة -وبتصرف- [للمقال ](How To Deploy an Express Application and Scale with MemCachier on DigitalOcean App Platform) من <a href="https://www.digitalocean.com/" rel="external nofollow">موقع DigitalOcean</a> لكاتبيه Patrick O'Hanlon و Caitlin Postal.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-express-%D9%88%D8%A8%D9%8A%D8%A6%D8%A9-node-r2168/" rel="">مدخل إلى إطار عمل الويب Express وبيئة Node</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nodejs-%D9%88%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-express-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-r1441/" rel="">دليل استخدام Node.js وإطار العمل Express للمبتدئين</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%B9%D9%85%D9%84%D9%8A-%D9%84%D8%AA%D8%B9%D9%84%D9%85-express-%D8%A7%D9%84%D8%AC%D8%B2%D8%A1-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%A7%D8%AC-r2202/" rel="">نشر تطبيق Express في بيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D9%82%D9%88%D8%A7%D9%84%D8%A8-template-%D9%81%D9%8A-express-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D9%82%D8%A7%D9%84%D8%A8-%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A-%D9%84%D9%85%D9%88%D9%82%D8%B9-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%85%D8%AD%D9%84%D9%8A%D8%A9-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r2209/" rel="">مقدمة إلى القوالب Template في Express: إنشاء القالب الأساسي لموقع مكتبة محلية مثالًا</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">803</guid><pubDate>Sat, 24 Feb 2024 12:00:00 +0000</pubDate></item><item><title>&#x645;&#x62F;&#x62E;&#x644; &#x625;&#x644;&#x649; &#x625;&#x62F;&#x627;&#x631;&#x629; &#x636;&#x628;&#x637; &#x627;&#x644;&#x62E;&#x648;&#x627;&#x62F;&#x645; Configuration Management</title><link>https://academy.hsoub.com/devops/deployment/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%B6%D8%A8%D8%B7-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%AF%D9%85-configuration-management-r689/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_03/----.png.c51ee9d327f0b4e0d125e3077eaf06a2.png" /></p>
<p>
	تشير إدارة الضبط Configuration Management -أو CM اختصارًا- إلى عملية المعالجة المنتظمة للتغييرات على النظام بطريقة تحافظ على التكامل بمرور الوقت، ويُستخدَم هذا المصطلح على نطاق واسع للإشارة إلى إدارة ضبط الخادم بالرغم من أن هذه العملية لم تنشأ في مجال تقانة المعلومات.
</p>

<p>
	تلعب عملية الأتمتة دورًا أساسيًا في إدارة ضبط الخادم، وهي الآلية المُستخدَمة لجعل الخادم يصل إلى الحالة المرغوبة، والتي حدَّدتها مسبقًا سكربتات الإعداد المسبق Provisioning Scripts باستخدام لغة وميزات معينة خاصة بالأداة. تُعَد الأتمتة جوهر إدارة ضبط الخوادم، لذا من الشائع الإشارة إلى أدوات إدارة الضبط بأنها أدوات الأتمتة Automation Tools أو أدوات أتمتة تقانة المعلومات IT Automation Tools. يوجد مصطلح شائع آخر يُستخدَم لوصف ميزات الأتمتة التي تقدّمها أدوات إدارة الضبط وهو تنسيق الخادم Server Orchestration أو تنسيق تقانة المعلومات IT Orchestration، لأن هذه الأدوات قادرة على إدارة خادم أو مئات الخوادم من جهاز تحكم مركزي، وقد فصلنا الفرق بينهما في مقال <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%A7-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%AA%D9%86%D8%B3%D9%8A%D9%82-%D9%88%D8%A7%D9%84%D8%A3%D8%AA%D9%85%D8%AA%D8%A9%D8%9F-r562/" rel="">الفرق بين التنسيق والأتمتة</a> فارجع إليه لمزيد من التفاصيل.
</p>

<p>
	هناك عدد من أدوات إدارة الضبط المتاحة في السوق مثل:
</p>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/puppet/" rel="">Puppet</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/ansible/" rel="">Ansible</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/chef/" rel="">Chef</a>
	</li>
	<li>
		Salt
	</li>
</ul>

<p>
	حيث ستتميز كل أداة بخصائصها وستعمل بطرق مختلفة، إلا أن لها الغرض نفسه، وهو التأكد من أن حالة النظام تتطابق مع الحالة التي توضحها سكربتات الإعداد المسبق.
</p>

<p>
	هذا المقال جزء من سلسلة حول إدارة ضبط الخوادم، وإليك روابط فصول السلسلة:
</p>

<ul>
	<li>
		<span ipsnoautolink="true">مدخل إلى إدارة ضبط الخوادم Configuration Management</span>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/ansible/%D9%85%D8%A8%D8%A7%D8%AF%D8%A6-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%B6%D8%A8%D8%B7-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%AF%D9%85-configuration-management-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%AF%D9%84%D9%8A%D9%84-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-ansible-r690/" rel="">مبادئ إدارة ضبط الخوادم Configuration Management: كتابة دليل تشغيل الأداة Ansible</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/puppet/%D9%85%D8%A8%D8%A7%D8%AF%D8%A6-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%B6%D8%A8%D8%B7-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%AF%D9%85-configuration-management-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86-manifests-%D9%84%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-puppet-r691/" rel="">مبادئ إدارة ضبط الخوادم Configuration Management: كتابة ملفات البيان Manifests للأداة Puppet</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/chef/%D9%85%D8%A8%D8%A7%D8%AF%D8%A6-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%B6%D8%A8%D8%B7-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%AF%D9%85-configuration-management-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%A7%D9%84%D9%88%D8%B5%D9%81%D8%A7%D8%AA-recipes-%D9%81%D9%8A-%D8%A7%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-chef-r692/" rel="">مبادئ إدارة ضبط الخوادم Configuration Management: كتابة الوصفات Recipes في الأداة Chef</a>
	</li>
</ul>

<h2>
	الفوائد التي تجنيها الخوادم من إدارة الضبط
</h2>

<p>
	يتطلب استخدام إدارة الضبط تخطيطًا أوليًا وجهدًا أكثر من إدارة النظام اليدوية، ولكن ستتحسّن بنى الخادم التحتية بأكملها باستثناء البسيطة منها من خلال الفوائد التي توفرها إدارة الضبط، حيث سنوضح فيما يلي بعضًا من هذه الفوائد.
</p>

<h3>
	الإعداد المسبق السريع للخوادم الجديدة
</h3>

<p>
	يمكن لأداة إدارة الضبط أتمتة معظم -إن لم يكن كل- عملية الإعداد المسبق نيابة عنك عند نشر خادم جديد، إذ تجعل الأتمتة عملية الإعداد المسبق أسرع وأكثر كفاءة لأنها تتيح أداء المهام الشاقة بصورة أسرع وأدق من أيّ إنسان يطبّقها، حيث يمكن أن يستغرق نشر خادم الويب يدويًا مثلًا ساعات بالموازنة مع بضع الدقائق التي تحتاجها إدارة أو أتمتة الضبط حتى مع وجود التوثيق الصحيح والشامل.
</p>

<h3>
	التعافي السريع من الأحداث العصيبة
</h3>

<p>
	تأتي ميزة أخرى مع الإعداد المسبق السريع، وهي التعافي السريع من الأحداث العصيبة، فإذا أصبح الخادم غير متصل بسبب ظروف غير معروفة، فقد يستغرق الأمر عدة ساعات لفحص النظام بصورة صحيحة ومعرفة ما حدث، حيث يكون في مواقف مشابهة نشرُ <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم</a> بديل هو الطريقة الأكثر أمانًا لإعادة خدماتك للعمل أثناء إجراء فحص مفصّل على الخادم المتأثر، ويمكن تطبيق ذلك بطريقة سريعة وموثوقة من خلال إدارة الضبط والأتمتة.
</p>

<h3>
	لا مزيد من الخوادم ذات الإدارة اليدوية Snowflake
</h3>

<p>
	تبدو للوهلة الأولى إدارة النظام اليدوية طريقة سهلة لنشر الخوادم وإصلاحها بسرعة ولكن لها سلبياتها، إذ يصبح بمرور الوقت من الصعب معرفة ما هو مُثبَّتٌ على الخادم وما هي التغييرات التي أجريت عندما لا تكون العملية مؤتمتة. يمكن للإصلاحات اليدوية السريعة Hotfixes وتعديلات الضبط وتحديثات البرمجيات أن تحوّل الخوادم إلى خوادم Snowflake فريدة تصعب إدارتها وتكرارها. تُوثَّق جميع الإجراءات اللازمة لإحضار خادم جديد أو تحديث خادم موجود مسبقًا في سكربتات الإعداد المسبق باستخدام أداة إدارة الضبط.
</p>

<h3>
	التحكم في الإصدارات لبيئة الخادم
</h3>

<p>
	سيكون لديك القدرة على تطبيق العديد من الأدوات وطرق سير العمل التي تستخدمها في شيفرة البرمجيات على بيئة الخادم بمجرد ترجمة ضبط خادمك إلى مجموعة من سكربتات الإعداد المسبق.
</p>

<p>
	يمكن استخدام أدوات التحكم في الإصدارات مثل <a href="https://academy.hsoub.com/programming/workflow/git/" rel="">جيت Git</a> لتتبّع التغييرات التي أُجريت على الإعداد المسبق ولإبقاء فروع الإصدارات القديمة من السكربتات منفصلةً، ويمكنك استخدام التحكم في الإصدارات لتنفيذ سياسة مراجعة الشيفرة البرمجية لسكربتات الإعداد المسبق، إذ يجب إرسال أي تغييرات بوصفها طلبَ سحب ويجب أن يوافق قائد المشروع عليها قبل قبولها. ستضيف هذه الممارسة تناسقًا إضافيًا إلى إعداد بنيتك التحتية.
</p>

<h3>
	البيئات المكررة
</h3>

<p>
	تسهّل إدارة الضبط تكرار البيئات مع البرمجيات والضبط نفسه، مما يمكّنك من بناء نظام بيئي متعدد المراحل بفعالية مع خوادم الإنتاج والتطوير والاختبار، ويمكنك استخدام الآلات الافتراضية المحلية للتطوير، والتي أُنشِئت باستخدام سكربتات الإعداد المسبق نفسها. ستعمل هذه الممارسة على تقليل المشاكل التي تسببها اختلافات البيئة التي تحدث بصورة متكررة عند نشر التطبيقات في بيئة الإنتاج أو عند مشاركتها بين زملاء العمل الذين يستخدمون إعدادات أجهزة مختلفة (مثل نظام تشغيل و/أو إصدارات برمجيات و/أو ضبط مختلف).
</p>

<h2>
	نظرة عامة على أدوات إدارة الضبط
</h2>

<p>
	لكل أداة إدارة ضبط CM مصطلحاتها الخاصة وفلسفتها ونظامها البيئي، ولكنها تشترك في العديد من الخصائص ولديها مفاهيم متشابهة، إذ تستخدم معظم أدوات إدارة الضبط نموذج متحكِّم أو رئيسي ونموذج عقدة أو وكيل، حيث يوجّه المتحكِّم ضبط العقد بناءً على سلسلة من التعليمات أو المهام المُحدَّدة في سكربتات الإعداد المسبق.
</p>

<p>
	سنوضّح فيما يلي الميزات الأكثر شيوعًا الموجودة في معظم أدوات إدارة ضبط الخوادم.
</p>

<h3>
	إطار عمل مؤتمت
</h3>

<p>
	توفر كل أداة إدارة ضبط CM صياغة محددة ومجموعة من الميزات التي يمكنك استخدامها لكتابة سكربتات الإعداد المسبق، إذ تحتوي معظم الأدوات على ميزات تجعل لغتها تشبه <a href="https://academy.hsoub.com/programming/general/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9/" rel="">لغات البرمجة التقليدية</a> ولكن بطريقة مبسطة، حيث تُعَد المتغيرات والحلقات والتعليمات الشرطية ميزات شائعة متوفرة لتسهيل إنشاء سكربتات إعداد مسبق أكثر تنوعًا.
</p>

<h3>
	السلوك الراسخ Idempotent Behavior
</h3>

<p>
	تتعقّب أدوات إدارة الضبط حالة الموارد لتجنب تكرار المهام التي جرى تنفيذها مسبقًا، فإذا ثُبِّتت الحزمة، فلن تحاول الأداة تثبيتها مرة أخرى. الهدف هو أن يصل النظام (أو يحتفظ) بالحالة المطلوبة بعد تشغيل كل إعداد مسبق حتى لو شغّلته عدة مرات. تتمتع هذه الأدوات بسلوك راسخ وهذا ما يميزها، ولكن لا يُفرَض هذا السلوك بالضرورة في جميع الحالات.
</p>

<h3>
	معلومات النظام
</h3>

<p>
	توفر أدوات إدارة الضبط معلومات مفصلة حول النظام المُعَد مسبقًا، وهذه البيانات متاحة من خلال المتغيرات العامة وتُعرَف باسم الحقائق Facts، والتي تشمل أشياءً مثل واجهات الشبكة وعناوين IP ونظام التشغيل والتوزيع. ستوفر كل أداة مجموعة مختلفة من الحقائق، ويمكن استخدامها لجعل سكربتات الإعداد المسبق والقوالب أكثر تكيفًا مع أنظمة متعددة.
</p>

<h3>
	نظام القوالب
</h3>

<p>
	توفر معظم أدوات إدارة الضبط CM نظام قوالب مبني مسبقًا يمكن استخدامه لتسهيل إعداد ملفات الضبط والخدمات، حيث تدعم القوالب عادةً المتغيرات والحلقات والتعليمات الشرطية التي يمكن استخدامها لتحقيق أقصى قدر من التنوع، فمثلًا يمكنك استخدام قالب لإعداد مضيف وهمي جديد بسهولة ضمن خادم <a href="https://academy.hsoub.com/devops/servers/web/apache/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%B6%D8%A8%D8%B7-%D8%AE%D8%A7%D8%AF%D9%85-apache-r407/" rel="">أباتشي Apache</a> مع إعادة استخدام القالب نفسه لعمليات تثبيت خوادم متعددة. يجب أن يحتوي القالب على عناصر بديلة للقيم التي يمكن أن تتغير من مضيف إلى آخر مثل <code>NameServer</code> و <code>DocumentRoot</code> بدلًا من وجود قيم ثابتة وساكنة فقط.
</p>

<h3>
	التوسع Extensibility
</h3>

<p>
	يمكن أن تكون سكربتات الإعداد المسبق متخصصة جدًا في تلبية احتياجات ومتطلبات خادم معين، ولكن هناك العديد من الحالات التي يكون لديك فيها إعدادات خادم مماثلة أو أجزاء من إعداد يمكن مشاركتها بين خوادم متعددة، إذ توفر معظم أدوات الإعداد المسبق طرقًا يمكنك من خلالها بسهولة إعادة استخدام ومشاركة أجزاء أصغر من الإعداد المسبق بوصفها وحدات أو إضافات.
</p>

<p>
	يمكن بسهولة العثور على الوحدات والإضافات التابعة لجهات خارجية على الإنترنت، وخاصة لإعدادات الخادم الشائعة مثل تثبيت خادم ويب PHP، إذ تميل أدوات إدارة الضبط CM إلى إنشاء مجتمع قوي حولها وتشجع المستخدمين على مشاركة توسّعاتها المخصصة. يمكن أن يوفر لك استخدام التوسّعات التي يوفرها المستخدمون الآخرون الكثير من الوقت، ويمثل طريقة ممتازة لتعلم كيفية حل المستخدمين الآخرين للمشاكل الشائعة باستخدام الأداة التي تختارها.
</p>

<h2>
	اختيار أداة إدارة الضبط
</h2>

<p>
	هناك العديد من أدوات إدارة الضبط CM المتاحة في السوق، ولكلٍ منها مجموعة مختلفة من الميزات ومستويات تعقيد مختلفة، ومن هذه الأدوات Chef و Ansible و Puppet. التحدي الأول هو اختيار أداة مناسبة لاحتياجاتك، فهناك بعض الأشياء التي يجب أن تأخذها في الحسبان قبل الاختيار وسنوضّحها فيما يلي.
</p>

<h3>
	تعقيد البنية التحتية
</h3>

<p>
	تتطلب معظم أدوات إدارة الضبط حدًا أدنى من التسلسل الهرمي يتكون من جهاز متحكِّم وعقدة سيديرها المتحكِّم، فمثلًا تتطلب أداة Puppet تثبيت تطبيق وكيل Agent على كل عقدة وتثبيت تطبيق رئيسي Master على جهاز المتحكِّم. بينما تتمتع أداة Ansible ببنية لامركزية لا تتطلب تثبيت برامج إضافية على العقد، ولكنها تعتمد على <a href="https://academy.hsoub.com/devops/security/ssh/%D8%A3%D9%86%D9%81%D8%A7%D9%82-ssh%D8%8C-%D9%85%D8%A7%D9%87%D9%8A%D8%AA%D9%87%D8%A7-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D9%87%D8%A7-r76/" rel="">بروتوكول <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr></a> لتنفيذ مهام الإعداد المسبق. يمكن أن تبدو البنية التحتية المبسطة مناسبة بصورة أفضل للمشاريع الأصغر، ولكن يجب مراعاة جوانب أخرى مثل قابلية التوسع والأمان، والتي يمكن ألّا تفرضها الأداة.
</p>

<p>
	يمكن أن تحتوي بعض الأدوات على مزيدٍ من المكونات والأجزاء المتحركة، مما يزيد من تعقيد بنيتك التحتية، ويؤثر على مسار التعلم ويمكن أن يزيد من تكلفة التنفيذ الإجمالية.
</p>

<h3>
	مسار التعلم
</h3>

<p>
	توفر أدوات إدارة الضبط CM صياغة مخصصة، حيث تستخدم في بعض الأحيان لغة مجال محدَّد Domain Specific Language -أو DSL اختصارًا، ومجموعة من الميزات التي تشكل إطار عملها للأتمتة. تتطلب بعض الأدوات إتقان مسار تعليمي أعلى كما هو الحال مع لغات البرمجة التقليدية، ويمكن أن تؤثر متطلبات البنية التحتية على مدى تعقيد الأداة ومدى سرعة قدرتك على رؤية عائد استثمارك.
</p>

<h3>
	التكلفة
</h3>

<p>
	تقدم معظم أدوات إدارة الضبط CM إصدارات مجانية أو <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D8%A7%D9%84%D9%85%D9%82%D8%B5%D9%88%D8%AF-%D8%A8%D9%85%D8%B5%D8%B7%D9%84%D8%AD-%D9%85%D9%81%D8%AA%D9%88%D8%AD-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-open-source%D8%9F-r885/" rel="">مفتوحة المصدر</a> مع اشتراكات مدفوعة للحصول على ميزات وخدمات متقدمة، حيث تحتوي بعض الأدوات على قيود أكثر من غيرها، لذلك يمكن أن تدفع مقابل هذه الخدمات اعتمادًا على احتياجاتك الخاصة وكيفية نمو بنيتك التحتية.
</p>

<p>
	يجب أن تفكر في التدريب بوصفه تكلفة إضافية محتملة، ليس فقط من الناحية المالية، ولكن أيضًا فيما يتعلق بالوقت الذي سيكون ضروريًا ليواكب فريقك سرعة تطور الأداة التي ستختارها في النهاية.
</p>

<h3>
	الأدوات المتقدمة
</h3>

<p>
	تقدم معظم الأدوات خدمات مدفوعة يمكن أن تشمل الدعم والتوسّعات والأدوات المتقدمة كما ذكرنا سابقًا، لذا يجب أن تحلل احتياجاتك المحددة وحجم بنيتك التحتية وما إذا كانت هناك حاجة لاستخدام هذه الخدمات أم لا. تُعَد لوحات الإدارة مثلًا خدمة شائعة تقدمها هذه الأدوات، ويمكن أن تسهّل بصورة كبيرة عملية إدارة ومراقبة جميع خوادمك من نقطة مركزية، إذ يجب أن تفكر في خيارات الضرورة المستقبلية المحتمَلة حتى إن لم تكن بحاجة إلى مثل هذه الخدمات حاليًا.
</p>

<h3>
	المجتمع والدعم
</h3>

<p>
	يمكن أن يكون المجتمع القوي والمرحِّب موردًا مهمًا للدعم والتوثيق، حيث يكون المستخدمون سعيدين عادةً بمشاركة معرفتهم والتوسعات التي يجرونها (الوحدات والإضافات وسكربتات الإعداد المسبق) مع مستخدمين آخرين، مما يكون مفيدًا في تسريع مسار تعلمك وتجنب التكاليف الإضافية للدعم أو التدريب المدفوع.
</p>

<h2>
	نظرة عامة إلى أدوات إدارة الضبط الشائعة
</h2>

<p>
	يمنحك الجدول التالي نظرة عامة سريعة على الاختلافات الرئيسية بين ثلاث من أكثر أدوات إدارة الضبط شيوعًا المتوفرة في السوق اليوم وهي: Ansible و Puppet و Chef.
</p>

<table>
	<thead>
		<tr>
			<th>
				 
			</th>
			<th>
				أداة Ansible
			</th>
			<th>
				أداة Puppet
			</th>
			<th>
				أداة Chef
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				لغة كتابة السكربتات
			</td>
			<td>
				لغة YAML
			</td>
			<td>
				لغة DSL مستندة إلى <a href="https://wiki.hsoub.com/Ruby" rel="external">لغة روبي Ruby</a>
			</td>
			<td>
				لغة روبي
			</td>
		</tr>
		<tr>
			<td>
				البنية التحتية
			</td>
			<td>
				يطبّق الجهاز المتحكِّم Controller الضبط على العقد باستخدام بروتوكول <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr>
			</td>
			<td>
				يزامن جهاز الأداة Puppet الرئيسي Master الضبط على عقد Puppet
			</td>
			<td>
				تدفع محطات عمل الأداة Chef الضبط إلى خادم Chef الذي سيحدِّث عقد Chef
			</td>
		</tr>
		<tr>
			<td>
				تتطلب برمجيات متخصصة للعقد
			</td>
			<td>
				لا
			</td>
			<td>
				نعم
			</td>
			<td>
				نعم
			</td>
		</tr>
		<tr>
			<td>
				يوفر نقطة تحكم مركزية
			</td>
			<td>
				لا، إذ يمكن لأيّ حاسوب أن يكون متحكمًا
			</td>
			<td>
				نعم، باستخدام جهاز Puppet الرئيسي Master
			</td>
			<td>
				نعم، باستخدام خادم Chef
			</td>
		</tr>
		<tr>
			<td>
				مصطلحات السكربت
			</td>
			<td>
				دليل التشغيل Playbook/الأدوار Roles
			</td>
			<td>
				ملفات البيان Manifests/الوحدات Modules
			</td>
			<td>
				الوصفات Recipes/الأدلة Cookbooks
			</td>
		</tr>
		<tr>
			<td>
				ترتيب تنفيذ المهام
			</td>
			<td>
				تسلسلي
			</td>
			<td>
				غير تسلسلي
			</td>
			<td>
				تسلسلي
			</td>
		</tr>
	</tbody>
</table>

<h2>
	الخطوات التالية
</h2>

<p>
	رأينا حتى الآن كيفية عمل إدارة ضبط الخوادم، وما يجب مراعاته عند اختيار أداة لبناء بنية إدارة الضبط التحتية، وسنتعرّف في المقالات اللاحقة عمليًا على ثلاث أدوات شائعة لإدارة الضبط هي: Ansible و Puppet و Chef، حيث سنستخدم مثالًا بسيطًا لإعداد الخادم الذي يجب أن يكون مؤتمتًا بالكامل بواسطة كل أداة من هذه الأدوات لمنحك فرصة للموازنة بين هذه الأدوات بنفسك، إذ يتكون هذا الإعداد من خادم أوبنتو 18.04 يشغّل أباتشي Apache لاستضافة صفحة ويب بسيطة.
</p>

<h2>
	الخلاصة
</h2>

<p>
	يمكن لإدارة الضبط تحسين تكامل الخوادم بصورة كبيرة بمرور الوقت من خلال توفير إطار عمل لأتمتة العمليات وتتبع التغييرات التي أُجريت على بيئة النظام. سنرى في <a href="https://academy.hsoub.com/devops/deployment/ansible/%D9%85%D8%A8%D8%A7%D8%AF%D8%A6-%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%B6%D8%A8%D8%B7-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%AF%D9%85-configuration-management-%D9%83%D8%AA%D8%A7%D8%A8%D8%A9-%D8%AF%D9%84%D9%8A%D9%84-%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%A7%D9%84%D8%A3%D8%AF%D8%A7%D8%A9-ansible-r690/" rel="">المقال التالي</a> كيفية تطبيق استراتيجية إدارة الضبط عمليًا باستخدام أداة Ansible.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-configuration-management" rel="external nofollow">An Introduction to Configuration Management</a> لصاحبته Erika Heidi.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/%D8%AF%D9%84%D9%8A%D9%84-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D9%85%D8%AD%D9%84%D9%8A-%D8%AE%D8%B7%D9%88%D8%A9-%D8%A8%D8%AE%D8%B7%D9%88%D8%A9-r422/" rel="">دليل إعداد خادم ويب محلي خطوة بخطوة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/linux/%D9%85%D8%B1%D8%A7%D9%82%D8%A8%D8%A9-%D9%88%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%B9%D8%AF%D8%A9-%D8%AE%D9%88%D8%A7%D8%AF%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%B9%D8%A8%D8%B1-%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D9%85%D8%B1%D8%A6%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-cockpit-r683/" rel="">مراقبة وإدارة عدة خوادم لينكس عبر واجهة متصفح مرئية باستخدام Cockpit</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">689</guid><pubDate>Tue, 21 Mar 2023 13:07:00 +0000</pubDate></item><item><title>&#x623;&#x641;&#x636;&#x644; &#x645;&#x645;&#x627;&#x631;&#x633;&#x627;&#x62A; &#x645;&#x646;&#x647;&#x62C; &#x627;&#x644;&#x62A;&#x643;&#x627;&#x645;&#x644; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631; &#x648;&#x627;&#x644;&#x62A;&#x633;&#x644;&#x64A;&#x645; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631; CI/CD</title><link>https://academy.hsoub.com/devops/deployment/%D8%A3%D9%81%D8%B6%D9%84-%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA-%D9%85%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd-r687/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_03/--------CI-CD.png.128b701e1a07f11a1ee430848b881d05.png" /></p>
<p>
	يُعَد نهج التكامل والتسليم والنشر المستمر -المعروف باسم <span ipsnoautolink="true">CI/CD</span>- جزءًا لا يتجزأ من التطوير الحديث الذي يهدف إلى تقليل الأخطاء أثناء التكامل والنشر مع زيادة سرعة إنجاز <span ipsnoautolink="true">المشروع الرقمي</span>، حيث يمثل هذا النهج فلسفة ومجموعة من الممارسات التي يعزّزها استخدام أدوات قوية تؤكد على الاختبار المؤتمت في كل مرحلة من مراحل خط إنتاج Pipeline البرمجيات، وبالتالي يمكنك من خلال دمج هذه الأفكار ضمن ممارساتك تقليلُ الوقت المطلوب لتكامل التغييرات لإصدارٍ ما واختبار كل تغيير بدقة قبل نقله إلى مرحلة الإنتاج.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		يشير مصطلح CI إلى continuous integration أي التكامل المستمر، و CD إلى continuous delivery التسليم المستمر أو continuous deployment أي النشر المستمر.
	</p>
</blockquote>

<p>
	يتمتع <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd-r644/" rel="">نهج CI/CD</a> بالعديد من الفوائد، ولكن يتطلب التقديم الناجح لهذا النهج في أغلب الأحيان قدرًا كبيرًا من الأمور التي يجب أن تضعها في الحسبان، إذ يمكن أن يكون تحديد كيفية استخدام الأدوات والتغييرات التي قد تحتاجها في عملياتك أمرًا صعبًا بدون اتباع طريقة التجربة والخطأ، ولكن ستكون جميع عمليات التقديم مختلفة، لذا يمكن أن يساعدك الالتزام بأفضل الممارسات على تجنب المشاكل الشائعة والتحسن بصورة أسرع.
</p>

<p>
	سنرشدك في هذا المقال لتعلم مبادئ كيفية تقديم وصيانة منظومة CI/CD لخدمة احتياجات مؤسستك بأفضل طريقة، حيث سنوضح عددًا من الممارسات التي ستساعدك على تحسين فعالية خدمة منظومة CI/CD. لا تتردد في القراءة بالترتيب الموجود أو يمكنك الانتقال إلى المجالات التي تهمك مباشرةً.
</p>

<h2>
	حافظ على سرعة خطوط الإنتاج
</h2>

<p>
	تساعد خطوط إنتاج CI/CD في الاهتمام بالتغييرات ابتداءً من دورات الاختبار المؤتمت ثم بيئات التحضير Staging Environments وأخيرًا مرحلة الإنتاج، فكلما كانت خطوط الإنتاج تشمل مرحلة الاختبار، زادت ثقتك في أن التغييرات لن تحدث آثارًا جانبية غير متوقعة في مرحلة النشر والتسليم، ولكن يجب أن يمر كل تغيير بهذه العملية، لذا يُعَد الحفاظ على خطوط إنتاجك سريعة وموثوقة أمرًا مهمًا للغاية.
</p>

<p>
	يمكن أن يكون من الصعب الموازنة بين هذين المطلبين، فهناك بعض الخطوات المباشرة التي يمكنك اتخاذها لتحسين السرعة مثل توسيع بنية CI/CD التحتية وتحسين مرحلة الاختبارات، ولكن قد تضطر مع مرور الوقت إلى اتخاذ قرارات حاسمة بشأن القيمة النسبية للاختبارات المختلفة ومرحلة أو ترتيب إجرائها. لذا يُعَد تقليص مجموعة اختباراتك -من خلال إزالة الاختبارات ذات القيمة المنخفضة أو التي لا تدل على أمور جوهرية- أذكى طريقة في بعض الأحيان للحفاظ على السرعة التي تتطلبها خطوط الإنتاج المُستخدَمة بكثرة.
</p>

<p>
	تأكد عند اتخاذ هذه القرارات المهمة من فهم وتوثيق المقايضات التي تطبّقها، وتشاور مع أعضاء الفريق وأصحاب المصلحة لمواءمة افتراضات الفريق حول مسؤولية مجموعة الاختبار وما يجب التركيز عليه.
</p>

<div class="banner-container ipsBox ipsPadding">
	<div class="inner-banner-container">
		<p class="banner-heading">
			دورة إدارة تطوير المنتجات
		</p>

		<p class="banner-subtitle">
			احترف إدارة تطوير المنتجات الرقمية بدءًا من التخطيط وتحليل السوق وحتى إطلاق منتج مميز وناجح
		</p>

		<div>
			<a class="ipsButton ipsButton_large ipsButton_primary ipsButton_important" href="https://academy.hsoub.com/learn/product-development-management/" rel="">اشترك الآن</a>
		</div>
	</div>

	<div class="banner-img">
		<img alt="دورة إدارة تطوير المنتجات" src="https://academy.hsoub.com/learn/assets/images/courses/product-development-management.png">
	</div>
</div>

<h2>
	عزل وتأمين بيئة منظومة CI/CD
</h2>

<p>
	تمثل منظومة CI/CD إحدى أكثر البنى التحتية أهمية التي يجب حمايتها من وجهة نظر الأمان التشغيلي، إذ يجب تأمينها بما أن لديها وصولًا كاملًا إلى شيفرتك البرمجية الأساسية وثبوتياتك لنشرها في بيئات مختلفة، ويجب عزلها وقفلها قدر الإمكان نظرًا لقيمتها العالية بوصفها هدفًا للمهاجمين.
</p>

<p>
	يجب نشر أنظمة CI/CD على الشبكات الداخلية المحمية دون أن تكون مكشوفة لأطراف خارجية، لذلك يوصَى بإعداد شبكات <abbr title="Virtual Private Network | الشبكة الخاصة الافتراضية"><abbr title="Virtual Private Network | الشبكة الخاصة الافتراضية">VPN</abbr></abbr> أو أي تقنية أخرى للتحكم في الوصول إلى الشبكة للتأكد من أن المشغّلين الموثقين فقط هم من يمكنهم الوصول إلى نظامك.
</p>

<p>
	يمكن أن تحتاج منظومة CI/CD خاصتك إلى الوصول إلى عدة شبكات مختلفة لنشر الشيفرة البرمجية في بيئات مختلفة اعتمادًا على مدى تعقيد مخطط شبكتك، فإن لم تُؤمَّن أو تُعزَل بصورة صحيحة، فقد يتمكّن المهاجمون الذين يمكنهم الوصول إلى بيئة واحدة من استخدام تقنية قفز الجزيرة Island Hop -وهي تقنية تُستخدَم لتوسيع الوصول من خلال الاستفادة من قواعد الشبكات الداخلية الأكثر تساهلًا- للوصول إلى بيئات أخرى عبر نقاط ضعف خوادم CI/CD.
</p>

<p>
	تعتمد استراتيجيات العزل والأمان المطلوبة بصورة كبيرة على مخطط الشبكة وبنيتها التحتية ومتطلبات الإدارة والتطوير. يجب أن تضع في بالك أن أنظمة CI/CD هي أهداف ذات قيمة عالية وتتمتع في كثير من الحالات بدرجة كبيرة من الوصول إلى أنظمتك الأساسية الأخرى، وبالتالي ستساعد حماية الوصول الخارجي إلى <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخوادم</a> والتحكم في أنواع الوصول الداخلي المسموح به في تقليل مخاطر تعرض منظومة CI/CD للخطر.
</p>

<h2>
	جعل خط إنتاج CI/CD الطريقة الوحيدة للنشر في بيئة الإنتاج
</h2>

<p>
	تساعد الأدوات في أغلب الأحيان في فرض أفضل الممارسات للاختبار والنشر، مما يمكّن منظومة CI/CD من تحسين ممارسات التطوير وجودة الشيفرة البرمجية، إذ تتطلب ترقية الشيفرة البرمجية عبر خطوط إنتاج CI/CD أن يثبت كل تغيير التزامه بالمعايير والإجراءات المُوثّقة الخاصة بمؤسستك. تظهر حالات الفشل في خط إنتاج CI/CD مباشرةً ويتوقف تقدم الإصدار المتأثر إلى مراحل لاحقة من الدورة، إذ تحمي هذه الآلية البيئات الأكثر أهمية من الشيفرة البرمجية غير الموثوق بها.
</p>

<p>
	يمكن تحقيق هذه المزايا من خلال التأكد من أن كل تغيير في بيئة إنتاجك يمر عبر خط إنتاجك، إذ يجب أن يكون خط إنتاج CI/CD هو الآلية الوحيدة التي تدخل من خلالها الشيفرة البرمجية إلى بيئة الإنتاج. يمكن أن يحدث ذلك تلقائيًا في نهاية الاختبار الناجح مع ممارسات <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd-r644/" rel="">النشر المستمر</a>، أو من خلال الترقية اليدوية للتغييرات المُختبَرة التي تعتمدها وتتيحها منظومة CI/CD.
</p>

<p>
	تبدأ الفِرق باستمرار في استخدام خطوط إنتاجهم للنشر، ولكنها تجري استثناءات عند حدوث مشاكل مع وجود ضغط لحلها بسرعة. يجب التخفيف من وقت التوقف والمشاكل الأخرى في أسرع وقت ممكن، ولكن يجب أن نفهم أن منظومة CI/CD هي أداة جيدة لضمان ألّا تدخل تغييراتك أخطاءً أخرى أو تسبّب تعطل النظام.
</p>

<p>
	سيؤدي وضع إصلاحٍ ما لشيفرتك عبر خط الإنتاج (أو استخدام منظومة CI/CD للتراجع) إلى منع نشر الإصدار التالي للتطبيق من إزالة الإصلاحات العاجلة التي طبقتها يدويًا على البيئة الإنتاجية، إذ يحمي خط الإنتاج صلاحية عمليات النشر بغض النظر عمّا إذا كان ذلك إصدارًا منتظمًا أو مخططًا له أو إصلاحًا سريعًا لحل مشكلة جارية. يُعَد هذا الاستخدام لمنظومة CI/CD سببًا آخر للعمل على إبقاء خط إنتاجك سريعًا. 
</p>

<h2>
	الحفاظ على التطابق مع البيئة الإنتاجية ما أمكن
</h2>

<p>
	ترفع خطوط إنتاج CI/CD التغييرات من خلال سلسلة من مجموعات الاختبار وبيئات النشر، إذ تُنشَر التغييرات التي تجتاز متطلبات مرحلةٍ تلقائيًا أو توضَع في طابور للنشر اليدوي في بيئات أكثر تقييدًا، حيث تهدف المراحل المبكرة إلى إثبات أنه من المفيد مواصلة الاختبار ودفع التغييرات لتصبح مناسبة للبيئة الإنتاجية.
</p>

<p>
	يساعد أن تكون بيئة الاختبار مطابقة قدر الإمكان للبيئة الإنتاجية أن نضمن في المراحل اللاحقة أن تظهِر الاختبارات بدقة كيفية تصرّف التغيير في البيئة الإنتاجية. يمكن أن تسمح الاختلافات الكبيرة بين بيئة التحضير staging وبيئة الإنتاج production أن تظهر مشاكل لم يعثر عليها في مرحلة الاختبار، وكلما زادت الاختلافات بين بيئتك الإنتاجية وبيئة الاختبار، قلَّ قياس اختباراتك لكيفية أداء الشيفرة البرمجية عند إصدارها.
</p>

<p>
	يُتوقَّع وجود بعض الاختلافات بين بيئتي التحضير والإنتاج، ولكن يجب إبقاؤها قابلة للإدارة والتأكد من فهمها جيدًا، إذ تستخدم بعض المؤسسات عمليات النشر الأزرق والأخضر Blue-green Deployments لمبادلة حركة مرور الإنتاج بين بيئتين متطابقتين تقريبًا تتناوبان بين كونهما بيئتي تحضير وإنتاج، وتضمنت الاستراتيجيات الأقل حِدةً نشر الضبط والبنية التحتية نفسها من الإنتاج إلى بيئة التحضير، ولكن على نطاق أقل. قد تختلف عناصرٌ مثل نقاط نهاية الشبكة بين بيئاتك، ولكن يمكن أن يساعد تحديد المعاملات لهذا النوع من البيانات المتغيرة في التأكد من أن الشيفرة البرمجية متناسقة وأن الاختلافات البيئية مُحدَّدة جيدًا.
</p>

<h2>
	البناء مرة واحدة فقط وترقية النتائج عبر خط الإنتاج
</h2>

<p>
	الهدف الأساسي لخط إنتاج التكامل المستمر والتسليم المستمر CI/CD هو بناء الثقة في تغييراتك وتقليل فرصة حدوث تأثير غير متوقع، حيث ناقشنا أهمية الحفاظ على التطابق بين البيئات، ولكن يجب ضمان مزيد من الاهتمام في جميع المكونات، فإن تطلّب برنامجك خطوة بناء أو حزمٍ أو تجميع، فيجب تنفيذ هذه الخطوة مرة واحدة فقط وإعادة استخدام الخرج الناتج على طول خط الإنتاج بأكمله.
</p>

<p>
	يساعد هذا المقال في منع المشاكل التي تنشأ عند تصريف البرمجيات أو حزمها عدة مرات، مما يسمح بمنع تعارضات ضئيلة في المنتجات الثانوية الناتجة، إذ يمكن أن يعني بناء البرمجيات بصورة منفصلة في كل مرحلة جديدة أن الاختبارات في البيئات السابقة لم تستهدف البرمجيات نفسها الذي ستُنشَر لاحقًا، مما يؤدي إلى إبطال النتائج. يمكن تجنب هذه المشكلة من خلال أن تتضمن أنظمة CI عملية بناء بوصفها خطوة أولى في خط الإنتاج الذي ينشئ البرمجية ويحزمها في بيئة نظيفة، ويجب إصدار المنتج الثانوي الناتج وتحميله إلى نظام تخزين المنتجات الثانوية لتسحبها المراحل اللاحقة من خط الإنتاج، مما يضمن عدم تغيير البناء أثناء تقدمه عبر النظام.
</p>

<h2>
	إجراء أسرع الاختبارات في وقت مبكر
</h2>

<p>
	يُعَد إبقاء خط الإنتاج سريعًا بالكامل هدفًا عامًا ورائعًا، ولكن ستكون أجزاءٌ من مجموعة اختبارك حتمًا أسرع من غيرها. تعمل منظومة CI/CD بوصفها ممرًا لجميع التغييرات التي تدخل نظامك، لذا يُعَد اكتشاف حالات الفشل في أقرب وقت ممكن أمرًا مهمًا لتقليل الموارد المُخصَّصة لعمليات البناء التي تسبب مشاكلًا، ويمكن تحقيق ذلك من خلال تحديد أولويات الاختبارات الأسرع وإجرائها أولًا. احفظ الاختبارات المعقدة التي تستغرق وقتًا طويلًا حتى بعد التحقق من صحة البناء باستخدام اختبارات صغيرة سريعة التشغيل.
</p>

<p>
	تحتوي هذه الإستراتيجية على عدد من الفوائد التي يمكن أن تساعد في الحفاظ على صحة عملية CI/CD، وتشجعك على فهم تأثير أداء الاختبارات الفردية، وتسمح لك بإكمال معظم اختباراتك مبكرًا، وتزيد من احتمالية الفشل السريع، مما يعني أنه يمكن التراجع عن التغييرات التي تسبب مشاكلًا أو إصلاحها قبل وقف عمل أعضاء الفريق الآخرين.
</p>

<p>
	يعني تحديدُ أولويات الاختبار تشغيلَ اختبارات الوحدة لمشروعك أولًا، لأن هذه الاختبارات تميل إلى أن تكون سريعة ومعزولة وتركز على المكونات، وتمثّل اختبارات التكامل بعد ذلك المستوى التالي من التعقيد والسرعة، وتليها اختبارات على مستوى النظام، وأخيرًا اختبارات القبول التي تتطلب غالبًا مستوًى معينًا من التفاعل البشري.
</p>

<h2>
	تقليل التفريع في نظام التحكم في الإصدارات
</h2>

<p>
	يتمثل أحد المبادئ الرئيسية لمنظومة CI/CD في دمج التغييرات في المستودع المشترك الأساسي مبكرًا وفي مرات متعددة، مما يساعد في تجنب مشاكل التكامل المتعبة باستمرار عندما يحاول العديد من المطورين دمج تغييرات كبيرة ومتباينة ومتضاربة في الفرع الرئيسي للمستودع استعدادًا للنشر، إذ تُضبَط أنظمة CI/CD لمراقبة واختبار التغييرات الملتزمة بفرع واحد فقط أو عدة فروع.
</p>

<p>
	يمكن الاستفادة من المزايا التي يوفرها التكامل المستمر CI من خلال تحديد <a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A3%D9%88%D9%84-%D9%85%D8%B3%D8%AA%D9%88%D8%AF%D8%B9-%D9%84%D9%83-%D9%85%D9%86-%D8%AE%D9%84%D8%A7%D9%84-%D8%AC%D9%8A%D8%AA-git-r1594/" rel="">عدد الفروع ونطاقها في مستودعك</a>، حيث تشير معظم التطبيقات إلى أن المطورين يلتزمون مباشرة بالفرع الرئيسي main أو master أو يدمجون التغييرات من الفروع المحلية مرةً واحدةً على الأقل يوميًا.
</p>

<p>
	تحتوي الفروع التي لا تتعقّبها منظومة CI/CD على شيفرة برمجية غير مُختبَرة بغض النظر عن أهميتها أو وظيفتها، حيث يساعد التقليل من <a href="https://academy.hsoub.com/programming/workflow/git/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D8%A7%D9%84%D8%AA%D9%81%D8%B1%D9%8A%D8%B9-branching-%D9%81%D9%8A-git-r271/" rel="">التفريع</a> لتشجيع التكامل المبكر بين شيفرات المطورين المختلفة على الاستفادة من نقاط القوة في النظام، ويمنع المطورين من إبطال المزايا التي يوفرها.
</p>

<h2>
	إجراء الاختبارات محليًا قبل الالتزام بخط إنتاج CI/CD
</h2>

<p>
	يجب تشجيع المطورين على إجراء بعض الاختبارات محليًا قبل رفعها على المستودع المشترك فيما يتعلق بنقطة اكتشاف حالات الفشل مبكرًا، مما يجعل من الممكن اكتشاف بعض التغييرات التي تسبب مشاكلًا قبل وقف أعضاء الفريق الآخرين. ليس محتملًا أن تكون بيئة المطور المحلية قادرة على تشغيل مجموعة الاختبارات بأكملها في بيئة شبيهة ببيئة الإنتاج، ولكن تمنح هذه الخطوة الإضافية الأفراد مزيدًا من الثقة بأن التغييرات التي يجرونها تجتاز الاختبارات الأساسية وتستحق محاولة الاندماج مع الشيفرة البرمجية الأساسية الأكبر.
</p>

<p>
	يمكن التأكد من أن المطورين يمكنهم الاختبار بفعالية من تلقاء أنفسهم من خلال أن تكون مجموعة اختباراتك قابلة للتشغيل باستخدام أمر واحد يمكن تشغيله من أيّ بيئة، إذ يجب أن تستخدم منظومة CI/CD الأمر نفسه الذي يستخدمه المطورون على أجهزتهم المحلية لبدء الاختبارات على الشيفرة البرمجية المضافة في المستودع، ويمكن تنسيق ذلك في أغلب الأحيان من خلال توفير سكربت bash أو أداة makefile لأتمتة تشغيل أدوات الاختبار بطريقة تكرارية ويمكن التنبؤ بها.
</p>

<h2>
	إجراء الاختبارات في بيئات مؤقتة عندما يكون ذلك ممكنًا
</h2>

<p>
	يكون من الجيد في أغلب الأحيان استخدام بيئات اختبار نظيفة ومؤقتة عندما يكون ذلك ممكنًا للمساعدة في ضمان إجراء الاختبارات بالطريقة نفسها في مراحل مختلفة، وهذا يعني تشغيل الاختبارات ضمن <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A3%D8%A8%D8%B1%D8%B2-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D9%87%D9%8A%D9%85-%D8%A7%D9%84%D8%AA%D9%8A-%D9%8A%D8%AC%D8%A8-%D8%B9%D9%84%D9%8A%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%85%D8%A7%D9%85-%D8%A8%D9%87%D8%A7-%D8%B9%D9%86-%D8%A7%D9%84%D8%AD%D8%A7%D9%88%D9%8A%D8%A7%D8%AA-r561/" rel="">حاويات Containers</a> لتجريد الاختلافات بين الأنظمة المضيفة ولتوفير واجهة برمجة تطبيقات معيارية لربط المكونات مع بعضها البعض بمقاييس مختلفة. تعمل الحاويات بأدنى قد من المعلومات المُحتفظة minimal state لذا لا تطّلع عمليات التشغيل اللاحقة لمجموعة الاختبارات على الآثارَ الجانبية أو حالات الاختبارات المحفوظة التي يمكن أن تؤدي إلى إفساد النتائج.
</p>

<p>
	هناك فائدة أخرى لبيئات الاختبار الموضوعة ضمن حاويات، وهي قابلية نقل بنية اختبارك التحتية، إذ يمتلك المطورون باستخدام الحاويات وقتًا أسهل لتكرار الضبط الذي سيُستخدَم لاحقًا في خط الإنتاج دون الحاجة إلى إعداد البنية التحتية يدويًا وصيانتها أو التضحية بدقة أداء البيئة.
</p>

<p>
	يمكن إنشاء الحاويات بسهولة عند الحاجة ثم تدميرها، لذا يمكن للمستخدمين تقديم تنازلات أقل فيما يتعلق بدقة بيئة اختبارهم عند إجراء الاختبارات المحلية. يقيد استخدام الحاويات في بعض الجوانب بيئة التنفيذ للمساعدة في تقليل الاختلافات بين مراحل خطوط الإنتاج.
</p>

<h2>
	الخلاصة
</h2>

<p>
	سيكون كل تقديم لمنظومة CI/CD مختلفًا عن الآخر، ولكن سيساعدك اتباع بعض المبادئ الأساسية التي وضّحناها في هذا المقال على تجنب بعض العقبات الشائعة وتعزيز ممارسات الاختبار والتطوير، وسيساعد مزيج من العملية والأدوات والعادات في جعل تغييرات التطوير أكثر نجاحًا وتأثيرًا كما هو الحال مع معظم جوانب منهج التكامل المستمر والتسليم المستمر.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-ci-cd-best-practices" rel="external nofollow">An Introduction to CI/CD Best Practices</a> لصاحبه Justin Ellingwood.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-github-actions-%D9%84%D8%AA%D8%AD%D9%82%D9%8A%D9%82-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r645/" rel="">استخدام GitHub Actions لتحقيق التكامل المستمر والنشر المستمر</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D8%A7%D9%84%D8%AA%D9%88%D8%B3%D8%B9-%D8%A3%D9%83%D8%AB%D8%B1-%D9%81%D9%8A-%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r648/" rel="">التوسع أكثر في نهج التكامل والتسليم المستمر</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D8%AF%D9%85%D8%AA%D9%8A%D9%86-circleci-%D9%88coveralls-r1276/" rel="">إعداد التكامل المستمر والنشر المستمر باستخدام الخدمتين CircleCI وCoveralls</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">687</guid><pubDate>Wed, 08 Mar 2023 13:06:00 +0000</pubDate></item><item><title>&#x646;&#x634;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642; Svelte</title><link>https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-svelte-r681/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_01/1810260902_---Svelte---.png.a4bd664fd2b9e76259d16e652d50b337.png" /></p>
<p>
	تعلّمنا في <a href="https://academy.hsoub.com/programming/javascript/%D8%AF%D8%B9%D9%85-%D9%84%D8%BA%D8%A9-typescript-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-svelte-r1871/" rel="">المقال السابق</a> دعم إطار عمل Svelte للغة TypeScript وكيفية استخدام ذلك لجعل تطبيقك أقوى، كما سنتعرّف في هذا المقال على كيفية نشر تطبيقك عبر الإنترنت ومشاركة بعض موارد التعلم التي يجب الانتقال إليها لمواصلة رحلة التعلّم في إطار عمل Svelte.
</p>

<ul>
	<li>
		<strong>المتطلبات الأساسية</strong>: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> و<a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> و<a href="https://wiki.hsoub.com/JavaScript" rel="external">جافاسكربت JavaScript</a>، ومعرفة باستخدام <a href="https://academy.hsoub.com/programming/workflow/%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B3%D8%B7%D8%B1-%D8%A7%D9%84%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%85%D9%86-%D8%B7%D8%B1%D9%81-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-r1471" rel="">سطر الأوامر أو الطرفية</a>، وستحتاج طرفية مثبَّت عليها node وnpm لتصريف وبناء تطبيقك.
	</li>
	<li>
		<strong>الهدف</strong>: التعرّف على كيفية تحضير تطبيق Svelte لعملية الإنتاج والموارد التعليمية التي يجب الاطلاع عليها بعد ذلك.
	</li>
</ul>

<p>
	يمكن متابعة كتابة شيفرتك معنا، لذلك انسخ أولًا مستودع github -إذا لم تفعل ذلك مسبقًا- باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_9" style=""><span class="pln">git clone https</span><span class="pun">:</span><span class="com">//github.com/opensas/mdn-svelte-tutorial.git</span></pre>

<p>
	ثم يمكنك الوصول إلى حالة التطبيق الحالية من خلال تشغيل الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_11" style=""><span class="pln">cd mdn</span><span class="pun">-</span><span class="pln">svelte</span><span class="pun">-</span><span class="pln">tutorial</span><span class="pun">/</span><span class="lit">08</span><span class="pun">-</span><span class="pln">next</span><span class="pun">-</span><span class="pln">steps</span></pre>

<p>
	أو يمكنك تنزيل محتوى المجلد مباشرةً كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_13" style=""><span class="pln">npx degit opensas</span><span class="pun">/</span><span class="pln">mdn</span><span class="pun">-</span><span class="pln">svelte</span><span class="pun">-</span><span class="pln">tutorial</span><span class="pun">/</span><span class="lit">08</span><span class="pun">-</span><span class="pln">next</span><span class="pun">-</span><span class="pln">steps</span></pre>

<p>
	تذكَّر تشغيل الأمر <code>npm install &amp;&amp; npm run dev</code> لبدء تشغيل تطبيقك في وضع التطوير.
</p>

<h2>
	تصريف التطبيق
</h2>

<p>
	شغّلنا حتى الآن تطبيقنا في وضع التطوير باستخدام الأمر <code>npm run dev</code> الذي يخبر إطار عمل Svelte بتجميع المكونات وملفات جافاسكربت في الملف public/build/bundle.js وجميع أقسام <a href="https://academy.hsoub.com/programming/css/%d8%aa%d8%b9%d8%b1%d9%91%d9%81-%d8%b9%d9%84%d9%89-%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a7%d8%aa-css-r70/" rel="">CSS</a> الخاصة بالمكونات في الملف public/build/bundle.css، كما يشغّل خادم التطوير ويراقب التغييرات ويعيد تصريف التطبيق وتحديث الصفحة عند حدوث تغيير.
</p>

<p>
	سيكون الملفان bundle.js و bundle.css كما يلي (لاحظ وجود حجم الملف على اليسار):
</p>

<pre class="ipsCode"> 504 Jul 13 02:43 bundle.css
95981 Jul 13 02:43 bundle.js
</pre>

<p>
	يمكن تصريف التطبيق للإنتاج من خلال تشغيل الأمر <code>npm run build</code>، إذ لن يشغّل إطار عمل Svelte خادم ويب أو يستمر في مراقبة التغييرات في هذه الحالة، ولكن سيصغّر ويضغط ملفات جافاسكربت باستخدام الأداة <a href="https://terser.org/" rel="external nofollow">terser</a>.
</p>

<p>
	سيكون الملفان bundle.js و bundle.css كما يلي بعد تشغيل الأمر <code>npm run build</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_17" style=""><span class="pln"> </span><span class="lit">504</span><span class="pln"> </span><span class="typ">Jul</span><span class="pln"> </span><span class="lit">13</span><span class="pln"> </span><span class="lit">02</span><span class="pun">:</span><span class="lit">43</span><span class="pln"> bundle</span><span class="pun">.</span><span class="pln">css
</span><span class="lit">21782</span><span class="pln"> </span><span class="typ">Jul</span><span class="pln"> </span><span class="lit">13</span><span class="pln"> </span><span class="lit">02</span><span class="pun">:</span><span class="lit">43</span><span class="pln"> bundle</span><span class="pun">.</span><span class="pln">js</span></pre>

<p>
	جرّب تشغيل الأمر <code>npm run build</code> في المجلد الجذر لتطبيقك الآن، كما يمكن أن تتلقى تحذيرًا، لكن يمكنك تجاهله حاليًا.
</p>

<p>
	يبلغ حجم تطبيقنا بالكامل الآن 21 كيلوبايت و8.3 كيلوبايت عند ضغطه بتنسيق gzip، كما لا توجد أوقات تشغيل أو اعتماديات إضافية لتنزيلها وتحليلها وتنفيذها والاستمرار في عملها في الذاكرة، إذ حلّل إطار عمل Svelte المكونات وصرّف الشيفرة البرمجية إلى لغة جافاسكربت الصرفة Vanilla JavaScript.
</p>

<h2>
	عملية التصريف في إطار Svelte
</h2>

<p>
	سيستخدِم إطار عمل Svelte الأداة <a href="https://rollupjs.org/guide/en/" rel="external nofollow">rollup</a> بوصفها أداة تحزيم للوحدات افتراضيًا عندما تنشئ تطبيقًا جديدًا باستخدام الأمر الآتي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_20" style=""><span class="pln"> npx degit sveltejs</span><span class="pun">/</span><span class="pln">template my</span><span class="pun">-</span><span class="pln">svelte</span><span class="pun">-</span><span class="pln">project</span></pre>

<p>
	<strong>ملاحظة</strong>: يوجد قالب رسمي لاستخدام <a href="https://academy.hsoub.com/programming/workflow/%D8%AF%D9%84%D9%8A%D9%84-webpack-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-r866/" rel="">Webpack</a> والعديد من <a href="https://github.com/sveltejs/integrations#bundler-plugins" rel="external nofollow">الإضافات التي يديرها المجتمع</a> لأدوات التحزيم الأخرى.
</p>

<p>
	يمكنك أن ترى في الملف package.json أنّ السكربتات <code>dev</code> و<code>start</code> تستدعي أداة التحزيم Rollup:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_22" style=""><span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="str">"build"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"rollup -c"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"dev"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"rollup -c -w"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"start"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"sirv public"</span><span class="pln">
</span><span class="pun">},</span></pre>

<p>
	نمرّر في السكربت <code>dev</code> الوسيط <code>‎-w</code> الذي يخبر أداة Rollup بمراقبة الملفات وإعادة البناء عند إجراء التغييرات، فإذا ألقينا نظرةً على الملف rollup.config.js، فيمكننا رؤية أنّ مصرِّف Svelte هو مجرد إضافة من Rollup:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_24" style=""><span class="kwd">import</span><span class="pln"> svelte from </span><span class="str">'rollup-plugin-svelte'</span><span class="pun">;</span><span class="pln">
</span><span class="com">// …</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> terser </span><span class="pun">}</span><span class="pln"> from </span><span class="str">'rollup-plugin-terser'</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> production </span><span class="pun">=</span><span class="pln"> </span><span class="pun">!</span><span class="pln">process</span><span class="pun">.</span><span class="pln">env</span><span class="pun">.</span><span class="pln">ROLLUP_WATCH</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">export</span><span class="pln"> </span><span class="kwd">default</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  input</span><span class="pun">:</span><span class="pln"> </span><span class="str">'src/main.js'</span><span class="pun">,</span><span class="pln">
  output</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    sourcemap</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln">
    format</span><span class="pun">:</span><span class="pln"> </span><span class="str">'iife'</span><span class="pun">,</span><span class="pln">
    name</span><span class="pun">:</span><span class="pln"> </span><span class="str">'app'</span><span class="pun">,</span><span class="pln">
    file</span><span class="pun">:</span><span class="pln"> </span><span class="str">'public/build/bundle.js'</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  plugins</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    svelte</span><span class="pun">({</span><span class="pln">
      </span><span class="com">// تفعيل عمليات فحص وقت التشغيل عندما لا تكون في وضع الإنتاج</span><span class="pln">
      dev</span><span class="pun">:</span><span class="pln"> </span><span class="pun">!</span><span class="pln">production</span><span class="pun">,</span><span class="pln">
      </span><span class="com">// ‫سنستخرج أيّ مكون CSS في ملف منفصل، وهذا أفضل للأداء</span><span class="pln">
      css</span><span class="pun">:</span><span class="pln"> css </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        css</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="str">'public/build/bundle.css'</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}),</span></pre>

<p>
	سترى لاحقًا في الملف نفسه كيف تقلل أداة Rollup من حجم السكربتات في وضع الإنتاج وتطلق خادمًا محليًا في وضع التطوير:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_26" style=""><span class="pln">   </span><span class="com">// ‫استدعِ الأمر "npm run start" في وضع التطوير بمجرد إنشاء الحزمة</span><span class="pln">
    </span><span class="pun">!</span><span class="pln">production </span><span class="pun">&amp;&amp;</span><span class="pln"> serve</span><span class="pun">(),</span><span class="pln">

    </span><span class="com">// راقب المجلد‫ public وحدّث المتصفح عند حدوث تغييرات عندما لا تكون في وضع الإنتاج</span><span class="pln">
    </span><span class="pun">!</span><span class="pln">production </span><span class="pun">&amp;&amp;</span><span class="pln"> livereload</span><span class="pun">(</span><span class="str">'public'</span><span class="pun">),</span><span class="pln">

    </span><span class="com">// ‫إذا أردت البناء للإنتاج (أي استخدمت npm run build بدلًا من npm run dev)، فطبّق عملية التصغير</span><span class="pln">
    production </span><span class="pun">&amp;&amp;</span><span class="pln"> terser</span><span class="pun">()</span><span class="pln">
  </span><span class="pun">],</span></pre>

<p>
	هناك العديد من <a href="https://github.com/rollup/awesome" rel="external nofollow">إضافات Rollup</a> التي تتيح لك تخصيص سلوكها، ومنها الإضافة <a href="https://github.com/sveltejs/svelte-preprocess" rel="external nofollow">svelte-preprocess</a> التي يهتم بها فريق Svelte، حيث تعالج هذه الإضافة مسبقًا العديد من اللغات المختلفة في ملفات Svelte مثل PostCSS و SCSS و Less و CoffeeScript و SASS و TypeScript.
</p>

<h2>
	نشر تطبيق Svelte
</h2>

<p>
	لا يُعَدّ تطبيق Svelte من وجهة نظر خادم الويب أكثر من مجموعة من ملفات <a href="https://academy.hsoub.com/programming/html/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-html-r1687/" rel="">HTML</a> و <a href="https://academy.hsoub.com/programming/css/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%84%D8%BA%D8%A9-css-r1688/" rel="">CSS</a> و <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-javascript-r664/" rel="">JavaScript</a>، فكل ما تحتاجه هو <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم ويب</a> قادر على تقديم ملفات ثابتة أو ساكنة، مما يعني أنه لديك الكثير من الخيارات للاختيار من بينها، إذًا لنلقِ نظرةً على بعض منها.
</p>

<p>
	<strong>ملاحظة</strong>: يمكن تطبيق القسم التالي على أيّ موقع ويب ساكن من جانب العميل يتطلب خطوة بناء، وليس فقط على تطبيقات Svelte.
</p>

<h3>
	النشر باستخدام Vercel
</h3>

<p>
	يُعَدّ استخدام <a href="https://vercel.com/" rel="external nofollow">Vercel</a> من أسهل الطرق لنشر تطبيق Svelte، ويُعَدّ Vercel منصةً سحابيةً مصمَّمةً خصيصًا للمواقع الساكنة، وتتمتع بدعم معظم أدوات الواجهة الأمامية الشائعة مثل إطار عمل Svelte.
</p>

<p>
	اتبع الخطوات التالية لنشر تطبيقك:
</p>

<ol>
	<li>
		سجّل للحصول على حساب <a href="https://vercel.com/signup" rel="external nofollow">Vercel</a>.
	</li>
	<li>
		انتقل إلى جذر تطبيقك وشغّل الأمر <code>npx vercel</code>، إذسيُطلَب منك في المرة الأولى إدخال عنوان بريدك الإلكتروني واتباع الخطوات الواردة في البريد الإلكتروني المرسل إلى هذا العنوان لأغراض أمنية.
	</li>
	<li>
		شغّل الأمر <code>npx vercel</code> مرةً أخرى، وسيُطلَب منك الإجابة على بعض الأسئلة كما يلي:
	</li>
</ol>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_32" style=""><span class="pun">&gt;</span><span class="pln"> npx vercel
</span><span class="typ">Vercel</span><span class="pln"> CLI </span><span class="lit">19.1</span><span class="pun">.</span><span class="lit">2</span><span class="pln">
</span><span class="pun">?</span><span class="pln"> </span><span class="typ">Set</span><span class="pln"> up and deploy </span><span class="str">"./mdn-svelte-tutorial"</span><span class="pun">?</span><span class="pln"> </span><span class="pun">[</span><span class="pln">Y</span><span class="pun">/</span><span class="pln">n</span><span class="pun">]</span><span class="pln"> y
</span><span class="pun">?</span><span class="pln"> </span><span class="typ">Which</span><span class="pln"> scope </span><span class="kwd">do</span><span class="pln"> you want to deploy to</span><span class="pun">?</span><span class="pln"> opensas
</span><span class="pun">?</span><span class="pln"> </span><span class="typ">Link</span><span class="pln"> to existing project</span><span class="pun">?</span><span class="pln"> </span><span class="pun">[</span><span class="pln">y</span><span class="pun">/</span><span class="pln">N</span><span class="pun">]</span><span class="pln"> n
</span><span class="pun">?</span><span class="pln"> </span><span class="typ">What</span><span class="str">'s your project'</span><span class="pln">s name</span><span class="pun">?</span><span class="pln"> mdn</span><span class="pun">-</span><span class="pln">svelte</span><span class="pun">-</span><span class="pln">tutorial
</span><span class="pun">?</span><span class="pln"> </span><span class="typ">In</span><span class="pln"> which directory is your code located</span><span class="pun">?</span><span class="pln"> </span><span class="pun">./</span><span class="pln">
</span><span class="typ">Auto</span><span class="pun">-</span><span class="pln">detected </span><span class="typ">Project</span><span class="pln"> </span><span class="typ">Settings</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Svelte</span><span class="pun">):</span><span class="pln">
</span><span class="pun">-</span><span class="pln"> </span><span class="typ">Build</span><span class="pln"> </span><span class="typ">Command</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`</span><span class="pln">npm run build</span><span class="pun">`</span><span class="pln"> or </span><span class="pun">`</span><span class="pln">rollup </span><span class="pun">-</span><span class="pln">c</span><span class="pun">`</span><span class="pln">
</span><span class="pun">-</span><span class="pln"> </span><span class="typ">Output</span><span class="pln"> </span><span class="typ">Directory</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">public</span><span class="pln">
</span><span class="pun">-</span><span class="pln"> </span><span class="typ">Development</span><span class="pln"> </span><span class="typ">Command</span><span class="pun">:</span><span class="pln"> sirv </span><span class="kwd">public</span><span class="pln"> </span><span class="pun">--</span><span class="pln">single </span><span class="pun">--</span><span class="pln">dev </span><span class="pun">--</span><span class="pln">port $PORT
</span><span class="pun">?</span><span class="pln"> </span><span class="typ">Want</span><span class="pln"> to override the settings</span><span class="pun">?</span><span class="pln"> </span><span class="pun">[</span><span class="pln">y</span><span class="pun">/</span><span class="pln">N</span><span class="pun">]</span><span class="pln"> n
   </span><span class="typ">Linked</span><span class="pln"> to opensas</span><span class="pun">/</span><span class="pln">mdn</span><span class="pun">-</span><span class="pln">svelte</span><span class="pun">-</span><span class="pln">tutorial </span><span class="pun">(</span><span class="pln">created </span><span class="pun">.</span><span class="pln">vercel</span><span class="pun">)</span><span class="pln">
   </span><span class="typ">Inspect</span><span class="pun">:</span><span class="pln"> https</span><span class="pun">:</span><span class="com">//vercel.com/opensas/mdn-svelte-tutorial/[...] [1s]</span><span class="pln">
</span><span class="pun"><span class="ipsEmoji">✅</span></span><span class="pln">  </span><span class="typ">Production</span><span class="pun">:</span><span class="pln"> https</span><span class="pun">:</span><span class="com">//mdn-svelte-tutorial.vercel.app [copied to clipboard] [19s]</span><span class="pln">
   </span><span class="typ">Deployed</span><span class="pln"> to production</span><span class="pun">.</span><span class="pln"> </span><span class="typ">Run</span><span class="pln"> </span><span class="pun">`</span><span class="pln">vercel </span><span class="pun">--</span><span class="pln">prod</span><span class="pun">`</span><span class="pln"> to overwrite later </span><span class="pun">(</span><span class="pln">https</span><span class="pun">:</span><span class="com">//vercel.link/2F).</span><span class="pln">
   </span><span class="typ">To</span><span class="pln"> change the domain or build command</span><span class="pun">,</span><span class="pln"> go to https</span><span class="pun">:</span><span class="com">//zeit.co/opensas/mdn-svelte-tutorial/settings</span></pre>

<ol start="4">
	<li>
		اقبل جميع الإعدادات الافتراضية وسيكون كل شيء على ما يرام.
	</li>
	<li>
		انتقل إلى عنوان "Production" في متصفحك بمجرد الانتهاء من النشر، وسترى تطبيقك منشورًا.
	</li>
</ol>

<p>
	كما يمكنك <a href="https://vercel.com/import/svelte" rel="external nofollow">استيراد مشروع Svelte git إلى Vercel</a> من <a href="https://github.com/" rel="external nofollow">GitHub</a> أو <a href="https://about.gitlab.com/" rel="external nofollow">GitLab</a> أو <a href="https://bitbucket.org/product" rel="external nofollow">BitBucket</a>.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك تثبيت Vercel بطريقة عامة باستخدام الأمر <code>npm i -g vercel</code> حتى لا تضطر إلى استخدام <code>npx</code> لتشغيله.
</p>

<h3>
	النشر التلقائي على صفحات GitLab Pages
</h3>

<p>
	يمكن استضافة الملفات الساكنة من خلال استخدام خدمات عبر الإنترنت تسمح لك بنشر موقعك تلقائيًا عندما ترفع التغييرات إلى مستودع git، وتتضمن معظم هذه الخدمات إنشاء خط أنابيب نشر يُشغَّل في كل عملية <code>git push</code>، كما تهتم ببناء موقع الويب ونشره.
</p>

<p>
	سننشر فيما يلي تطبيق قائمة المهام على <a href="https://about.gitlab.com/stages-devops-lifecycle/pages/" rel="external nofollow">GitLab Pages</a>:
</p>

<p>
	يجب أولًا <a href="https://gitlab.com/users/sign_up" rel="external nofollow">التسجيل في GitLab</a> ثم <a href="https://gitlab.com/projects/new" rel="external nofollow">إنشاء مشروع جديد</a>، وسَمّ مشروعك الجديد باسم قصير وسهل مثل mdn-svelte-todo، إذ سيكون لديك عنوان url يشير إلى مستودع GitLab git الجديد مثل
</p>

<pre class="ipsCode" id="ips_uid_582_34"> git@gitlab.com:[your-user]/[your-project].git</pre>

<p>
	ثانيًا، يُفضَّل إضافة ملف <code>‎.gitignore</code> لإخبار جيت git بالملفات التي يجب استبعادها من عملية التحكم بالمصدر قبل البدءأ برفع المحتوى إلى مستودع git، إذ سنخبر git في حالتنا باستبعاد الملفات الموجودة في المجلد <code>node_modules</code> من خلال إنشاء ملف <code>‎.gitignore</code> في المجلد الجذر لمشروعك المحلي الذي يحتوي على ما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_36" style=""><span class="pln">node_modules</span><span class="pun">/</span></pre>

<p>
	ثالثًا، لنعد الآن إلى GitLab، إذ سيرحب GitLab بك برسالة تشرح الخيارات المختلفة لرفع ملفاتك الموجودة مسبقًا بعد إنشاء مستودع جديد، لذا اتبع الخطوات المدرجة ضمن عنوان رفع مجلد موجود مسبقًا Push an existing folder:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_582_38" style=""><span class="pln">cd your_root_directory # انتقل إلى المجلد الجذر لمشروعك
git init
git remote add origin https://gitlab.com/[your-user]/mdn-svelte-todo.git
git add .
git commit -m "Initial commit"
git push -u origin main</span></pre>

<p>
	<strong>ملاحظة</strong>: يمكنك استخدام بروتوكول <code>git</code> بدلًا من بروتوكول <code>https</code> الذي يُعَدّ أسرع ولا يجعلك تكتب اسم المستخدِم وكلمة المرور في كل مرة تدخل فيها إلى مستودعك الأصلي، لكن يجب إنشاء زوج <a href="https://academy.hsoub.com/devops/security/ssh/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%AE%D9%88%D8%A7%D8%AF%D9%8A%D9%85-ssh-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D8%A7%D8%A1-%D9%88%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD-r55/" rel="">مفاتيح <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة"><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></abbr></a> لاستخدامه، وسيكون عنوان URL الأصلي كما يلي:
</p>

<pre class="ipsCode" id="ips_uid_582_42">git@gitlab.com:[your-user]/mdn-svelte-todo.git</pre>

<p>
	هيّأنا مستودع git محلي عبر التعليمات السابقة، ثم ضبطنا الأصل البعيد حيث سنرفع شيفرتنا ليكون مستودعنا على GitLab، ثم يجب وضع جميع الملفات في مستودع git المحلي وبعد ذلك نرفعها إلى الأصل البعيد على GitLab.
</p>

<p>
	يستخدِم GitLab أداةً مبنيةً مسبقًا تسمى GitLab CI/CD لبناء موقعك ونشره على خادم GitLab Pages، إذ يُنشَأ تسلسل السكربتات الذي تشغّله الأداة GitLab CI/CD لإنجاز هذه المهمة من ملف يُدعى <code>‎.gitlab-ci.yml</code> الذي يمكنك إنشاؤه وتعديله حسب الرغبة، كما ستجعل وظيفة معينة تسمى <code>pages</code> في ملف الإعداد GitLab على دراية بأنك تنشر موقع GitLab Pages.
</p>

<p>
	لنجرب ذلك:
</p>

<p>
	أولًا، أنشئ الملف <code>‎.gitlab-ci.yml</code> ضمن جذر مشروعك وضَع فيه المحتوى التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_44" style=""><span class="pln">image</span><span class="pun">:</span><span class="pln"> node</span><span class="pun">:</span><span class="pln">latest
pages</span><span class="pun">:</span><span class="pln">
  stage</span><span class="pun">:</span><span class="pln"> deploy
  script</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">-</span><span class="pln"> npm install
    </span><span class="pun">-</span><span class="pln"> npm run build
  artifacts</span><span class="pun">:</span><span class="pln">
    paths</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> </span><span class="kwd">public</span><span class="pln">
  only</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">-</span><span class="pln"> main</span></pre>

<p>
	طلبنا من GitLab استخدام صورة مع أحدث إصدار من Node لبناء تطبيقنا، ثم صرّحنا عن الوظيفة <code>pages</code>، لتفعيل GitLab Pages، وكلما كان هناك عملية رفع إلى المستودع، فسيشغّل GitLab الأمرين <code>npm install</code> و <code>npm run build</code> لبناء تطبيقنا، كما نطلب من GitLab نشر محتويات المجلد <code>public</code>، ونُعِد GitLab في السطر الأخير لإعادة نشر تطبيقنا فقط عندما يكون هناك عملية رفع إلى الفرع الرئيسي.
</p>

<p>
	ثانيًا، يجب إنشاء مراجع لملفات جافاسكربت وCSS في الملف ذي المسار النسبي <code>public/index.html</code>، بما أنّ تطبيقنا سيُنشَر في مجلد فرعي مثل الآتي:
</p>

<pre class="ipsCode" id="ips_uid_582_46"> https://your-user.gitlab.io/mdn-svelte-todo</pre>

<p>
	ويمكن ذلك من خلال إزالة الشرطات المائلة <code>/</code> من العناوين:
</p>

<ul>
	<li>
		<code>‎/global.css</code>
	</li>
	<li>
		<code>‎/build/bundle.css</code>
	</li>
	<li>
		<code>‎/build/bundle.js</code>
	</li>
</ul>

<p>
	كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_48" style=""><span class="pun">&lt;</span><span class="pln">title</span><span class="pun">&gt;</span><span class="typ">Svelte</span><span class="pln"> </span><span class="typ">To</span><span class="pun">-</span><span class="typ">Do</span><span class="pln"> list</span><span class="pun">&lt;/</span><span class="pln">title</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">link rel</span><span class="pun">=</span><span class="str">'icon'</span><span class="pln"> type</span><span class="pun">=</span><span class="str">'image/png'</span><span class="pln"> href</span><span class="pun">=</span><span class="str">'favicon.png'</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">link rel</span><span class="pun">=</span><span class="str">'stylesheet'</span><span class="pln"> href</span><span class="pun">=</span><span class="str">'global.css'</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">link rel</span><span class="pun">=</span><span class="str">'stylesheet'</span><span class="pln"> href</span><span class="pun">=</span><span class="str">'build/bundle.css'</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script defer src</span><span class="pun">=</span><span class="str">'build/bundle.js'</span><span class="pun">&gt;&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	ثالثًا، يجب الآن فقط رفع التغييرات إلى GitLab من خلال تشغيل الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_582_50" style=""><span class="pun">&gt;</span><span class="pln"> git add </span><span class="kwd">public</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html
</span><span class="pun">&gt;</span><span class="pln"> git add </span><span class="pun">.</span><span class="pln">gitlab</span><span class="pun">-</span><span class="pln">ci</span><span class="pun">.</span><span class="pln">yml
</span><span class="pun">&gt;</span><span class="pln"> git commit </span><span class="pun">-</span><span class="pln">m </span><span class="str">"Added .gitlab-ci.yml file and fixed index.html absolute paths"</span><span class="pln">
</span><span class="pun">&gt;</span><span class="pln"> git push
</span><span class="typ">Counting</span><span class="pln"> objects</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> done</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Delta</span><span class="pln"> compression using up to </span><span class="lit">8</span><span class="pln"> threads</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Compressing</span><span class="pln"> objects</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100</span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="lit">5</span><span class="pun">/</span><span class="lit">5</span><span class="pun">),</span><span class="pln"> done</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Writing</span><span class="pln"> objects</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100</span><span class="pun">%</span><span class="pln"> </span><span class="pun">(</span><span class="lit">5</span><span class="pun">/</span><span class="lit">5</span><span class="pun">),</span><span class="pln"> </span><span class="lit">541</span><span class="pln"> bytes </span><span class="pun">|</span><span class="pln"> </span><span class="lit">541.00</span><span class="pln"> </span><span class="typ">KiB</span><span class="pun">/</span><span class="pln">s</span><span class="pun">,</span><span class="pln"> done</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Total</span><span class="pln"> </span><span class="lit">5</span><span class="pln"> </span><span class="pun">(</span><span class="pln">delta </span><span class="lit">3</span><span class="pun">),</span><span class="pln"> reused </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">delta </span><span class="lit">0</span><span class="pun">)</span><span class="pln">
</span><span class="typ">To</span><span class="pln"> gitlab</span><span class="pun">.</span><span class="pln">com</span><span class="pun">:</span><span class="pln">opensas</span><span class="pun">/</span><span class="pln">mdn</span><span class="pun">-</span><span class="pln">svelte</span><span class="pun">-</span><span class="pln">todo</span><span class="pun">.</span><span class="pln">git
   </span><span class="lit">7dac9f3.</span><span class="pun">.</span><span class="lit">5725f46</span><span class="pln">  main </span><span class="pun">-&gt;</span><span class="pln"> main</span></pre>

<p>
	سيعرض GitLab رمزًا يوضح تقدّم هذه الوظيفة كلما كانت هناك وظيفة تعمل، كما سيسمح لك النقر عليه بفحص الخرج.
</p>

<p style="text-align: center;">
	<img alt="01-gitlab-pages-deploy.png" class="ipsImage ipsImage_thumbnailed" data-fileid="117084" data-ratio="16.46" data-unique="bx7ray3bc" width="893" src="https://academy.hsoub.com/uploads/monthly_2023_01/01-gitlab-pages-deploy.png.6e2803535a4ede892db9b8514be966a2.png">
</p>

<p>
	كما يمكنك التحقق من تقدم الوظائف الحالية والسابقة من قائمة "CI / CD" ثم الخيار "Jobs" الخاص بمشروع GitLab.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="117085" href="https://academy.hsoub.com/uploads/monthly_2023_01/02-gitlab-pages-job.png.2c08007ae821e2cd7d69532b63eb2469.png" rel=""><img alt="02-gitlab-pages-job.png" class="ipsImage ipsImage_thumbnailed" data-fileid="117085" data-ratio="61.33" data-unique="jegrimy6m" width="900" src="https://academy.hsoub.com/uploads/monthly_2023_01/02-gitlab-pages-job.thumb.png.99a0a7d44b3f9121c12adfaf9109c035.png"></a>
</p>

<p>
	سيكون تطبيقك متاحًا على <code><a href="https://your-user.gitlab.io/mdn-svelte-todo/%E2%80%8E" ipsnoembed="false" rel="external nofollow">https://your-user.gitlab.io/mdn-svelte-todo/‎</a></code> بمجرد انتهاء GitLab من بناء ونشر تطبيقك، ويكون في حالتنا <code><a href="https://opensas.gitlab.io/mdn-svelte-todo/%E2%80%8E" ipsnoembed="false" rel="external nofollow">https://opensas.gitlab.io/mdn-svelte-todo/‎</a></code>، كما يمكنك التحقق من عنوان URL لصفحتك في واجهة مستخدِم GitLab من قائمة Settings ثم الخيار "Pages".
</p>

<p>
	سيُعاد بناء التطبيق تلقائيًا ونشره في GitLab Pages كلما رفعت تغييرات إلى مستودع GitLab باستخدام هذا الإعداد.
</p>

<h2>
	موارد إضافية لتعلم إطار عمل Svelte
</h2>

<p>
	سنمنحك الآن بعض الموارد والمشاريع للتحقق منها ومواصلة مشوار تعلّمك إطار عمل Svelte.
</p>

<h3>
	توثيق Svelte
</h3>

<p>
	يجب عليك بالتأكيد زيارة <a href="https://svelte.dev/" rel="external nofollow">الصفحة الرئيسية لإطار Svelte</a> حيث ستجد العديد من المقالات التي تشرحه، فإذا لم تفعل ذلك سابقًا، فاطّلع على <a href="https://svelte.dev/tutorial/basics" rel="external nofollow">برنامج Svelte التعليمي التفاعلي</a>، وقد غطينا فعليًا معظم محتوياته في هذه السلسلة من المقالات، لذا لن يستغرق الأمر وقتًا طويلًا لإكماله، ويمكنك الرجوع إلى <a href="https://svelte.dev/docs" rel="external nofollow">توثيق Svelte <abbr title="Application Programming Interface | واجهة برمجية"><abbr title="Application Programming Interface | واجهة برمجية">API</abbr></abbr></a> و<a href="https://svelte.dev/examples/hello-world#hello-world" rel="external nofollow">الأمثلة المتاحة</a>.
</p>

<h3>
	مشاريع ذات صلة
</h3>

<p>
	هناك مشاريع أخرى متعلقة بإطار عمل Svelte تستحق المراجعة ومنها:
</p>

<ul>
	<li>
		<a href="https://sapper.svelte.dev/" rel="external nofollow">Sapper</a>: إطار تطبيق مدعوم من Svelte يوفِّر التصيير من جانب الخادم SSR وتقسيم الشيفرة البرمجية والتوجيه المستند إلى الملفات والدعم دون اتصال والمزيد، كما يُعَدّ بمثابة Next.js لإطار عمل Svelte، فإذا أردت تطوير تطبيق ويب معقد إلى حد ما، فيجب عليك بالتأكيد إلقاء نظرة على هذا المشروع.
	</li>
	<li>
		<a href="https://svelte-native.technology/" rel="external nofollow">Svelte Native</a>: إطار عمل تطبيقات الهاتف المحمول مدعوم من Svelte، ويُعَدّ بمثابة React Native لإطار عمل Svelte.
	</li>
	<li>
		<a href="https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode" rel="external nofollow">Svelte for VS Code</a>: إضافة VS Code المدعوم رسميًا للعمل مع ملفات <code>‎.svelte</code>.
	</li>
</ul>

<h2>
	الخلاصة
</h2>

<p>
	تهانينا، فقد أكملت سلسلة مقالات تعلم إطار عمل Svelte، حيث انتقلنا من مرحلة عدم المعرفة بإطار Svelte إلى إنشاء تطبيق كامل ونشره.
</p>

<ul>
	<li>
		تعلمنا مفهوم Svelte وما يميزه عن أطر عمل الواجهة الأمامية الأخرى.
	</li>
	<li>
		رأينا كيفية إضافة سلوك ديناميكي إلى موقع الويب، وكيفية تنظيم تطبيقنا في مكونات وطرق مختلفة لمشاركة المعلومات فيما بينها.
	</li>
	<li>
		استفدنا من نظام Svelte التفاعلي وتعلمنا كيفية تجنب الأخطاء الشائعة.
	</li>
	<li>
		رأينا بعض المفاهيم والتقنيات المتقدمة للتفاعل مع <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-dom-r644/" rel="">عناصر DOM</a> ولتوسيع إمكانيات عناصر HTML برمجيًا باستخدام الموجّه <code>use</code>.
	</li>
	<li>
		رأينا بعد ذلك كيفية استخدام المخازن للعمل مع مستودع بيانات مركزي، وأنشأنا مخزننا المُخصَّص لاستمرار بيانات تطبيقنا في تخزين الويب.
	</li>
	<li>
		كما ألقينا نظرةً على دعم Svelte للغة TypeScript.
	</li>
</ul>

<p>
	تعلمنا في هذا المقال خيارين من الخيارات لنشر تطبيقنا في عملية الإنتاج ورأينا كيفية إعداد خط أنابيب أساسي لنشر تطبيقنا على GitLab عند كل تغيير ثم قدمنا قائمةً بموارد Svelte للمضي قدمًا في تعلم Svelte.
</p>

<p>
	يجب أن تكون لديك قاعدة قوية يمكنك من خلالها البدء في تطوير تطبيقات ويب احترافية باستخدام إطار عمل Svelte بعد الانتهاء من هذه السلسلة من المقالات.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next" rel="external nofollow">Deployment and next steps</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D9%82%D8%B3%D9%8A%D9%85-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-svelte-%D8%A5%D9%84%D9%89-%D9%85%D9%83%D9%88%D9%86%D8%A7%D8%AA-r1823/" rel="">تقسيم تطبيق Svelte إلى مكونات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%82%D8%A7%D8%A6%D9%85%D8%A9-%D9%85%D9%87%D8%A7%D9%85-%D8%A8%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-svelte-r1811/" rel="">إنشاء تطبيق قائمة مهام باستعمال إطار عمل Svelte</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A8%D8%AF%D8%A1-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-svelte-%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D9%8A%D8%A8-r1810/" rel="">بدء استخدام إطار العمل Svelte لبناء تطبيقات ويب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AA%D9%81%D8%A7%D8%B9%D9%84%D9%8A%D8%A9-%D9%88%D8%AF%D9%88%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D9%8A%D8%A7%D8%A9-%D9%88%D8%B3%D9%87%D9%88%D9%84%D8%A9-%D9%88%D8%B5%D9%88%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%8A%D9%86-%D9%81%D9%8A-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-svelte-r1863/" rel="">التفاعلية ودورة الحياة وسهولة وصول المستخدمين في إطار عمل Svelte</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">681</guid><pubDate>Wed, 22 Feb 2023 17:00:01 +0000</pubDate></item><item><title>&#x646;&#x634;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x627;&#x644;&#x627;&#x62C;&#x62A;&#x645;&#x627;&#x639;&#x627;&#x62A; &#x62C;&#x64A;&#x62A;&#x633;&#x64A; Jitsi &#x645;&#x62D;&#x644;&#x64A;&#x627; &#x639;&#x644;&#x649; &#x62E;&#x627;&#x62F;&#x645; &#x62E;&#x627;&#x635; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x62F;&#x648;&#x643;&#x631;</title><link>https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A7%D9%84%D8%A7%D8%AC%D8%AA%D9%85%D8%A7%D8%B9%D8%A7%D8%AA-%D8%AC%D9%8A%D8%AA%D8%B3%D9%8A-jitsi-%D9%85%D8%AD%D9%84%D9%8A%D8%A7-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%AE%D8%A7%D8%B5-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AF%D9%88%D9%83%D8%B1/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_01/682174187_----Jitsi------.jpg.65db0a81349d8429adbe784d40d36af2.jpg" /></p>
<p>
	تطبيق الويب <a href="https://jitsi.org/jitsi-meet/" rel="external nofollow">Jitsi Meet</a> هو تطبيق اجتماعات مرئية مفتوح المصدر يمكن استضافته محليًا، ويعتبر بديلًا جيدًا عن الخدمات الأخرى مثل Google Meet أو Zoom، حيث يمكن ربط Jitsi Meet مع أدوات أخرى مفتوحة المصدر مثل <a href="https://nextcloud.com/" rel="external nofollow">Nextcloud</a> أو <a href="https://rocket.chat/" rel="external nofollow">Rocket.Chat</a> أو ‎Synapse (Matrix implementation)‎ التي تتكامل مع بعضها لتوفير حل شامل، يمكن أيضًا <a href="https://meet.jit.si/" rel="external nofollow">استخدام Jitsi Meet مجانًا</a> على خوادمهم لكن بمزايا محدودة، وللمزايا الإضافية يمكن الانضمام إلى <a href="https://jaas.8x8.vc/" rel="external nofollow">Jitsi كخدمة</a> المقدم من مطوري Jitsi، يمكن بدلًا من ذلك استضافة البرنامج على خادم خاص محلي، حيث من السهل نشر برنامج Jitsi، سنعرض تلك الخطوات ونشرحها ونشرح طريقة استضافته خلف وكيل عكسي وبدون ذلك.
</p>

<h2>
	المستلزمات
</h2>

<p>
	يجب القيام ببعض التحضيرات قبل البدء، حيث يفضل امتلاك معرفة مبدئية عن دوكر والحاويات، وامتلاك اسم نطاق خاص، حيث أن عملية الوصول لخادم التطبيق في هذا المقال لن تكون اعتمادًا على عنوان IP، بل سنشرح خطوات النشر باستخدام اسم نطاق حقيقي أو اسم نطاق فرعي مع تفعيل بروتوكول الاتصال الآمن HTTPS، ويمكن خلال مرحلة الاختبار الوصول للتطبيق عبر مسار بالبنية:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5468_6" style=""><span class="pln">HTTP</span><span class="pun">:</span><span class="com">//[some IP]:[some port]‎</span></pre>

<p>
	أيضًا امتلاك <a href="https://academy.hsoub.com/devops/linux/%D8%A3%D8%AA%D9%85%D8%AA%D8%A9-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D9%84%D9%8A-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-%D8%A8%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1-1804-r443/" rel="">خادم لينكس</a> حقيقي أو مُستضاف ضمن السحابة، حيث يمكن استخدام خادم لينكس على عدة استضافات مثل Linode أو DigitalOcean أو Vultr أو UpCloud، بينما استخدام AWS يمكن أن يعتمد بشكل كبير على النظام الأساسي لذا لن نتحدث عنه، حيث تنصح <a href="https://community.jitsi.org/t/recommended-server-specs-for-2020/32041" rel="external nofollow">التوصيات الرسمية</a> للبرنامج بتوفر ذاكرة <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">للخادم </a>بحجم 4 جيجابايت ومعالج ثنائي النوى لخدمة بين 10 و 20 مستخدم.
</p>

<p>
	أيضًا تجهيز إعدادات الوكيل العكسي، وذلك في حال أردنا وضع التطبيق خلف وكيل عكسي لنشر عدة تطبيقات أخرى بجانبه على نفس الخادم، لكن إذا كان التطبيق هو الوحيد الذي سيعمل على الخادم فلا حاجة لنا باستخدام الوكيل العكسي في تلك الحالة.
</p>

<h2>
	تعديل سجلات DNS
</h2>

<p>
	لا يكفي شراء وامتلاء اسم نطاق، يجب التأكد من إدراج سجلات DNS له أيضًا، سنستعمل ضمن هذا المقال اسم النطاق التالي "openexperiment.in"، وتأكد عند اتباعك للخطوات ضمن هذا المقال من تبديل اسم النطاق إلى اسم النطاق الخاص بك، حيث بعد امتلاك اسم النطاق ونشر الخادم (خادم فقط من دون برنامج Jitsi)، يجب جلب عناوين IP للخادم (كل من عنواني IPv4 و IPv6) ونضيف سجلات من النوع A و AAAA لكل منها على الترتيب، وبعد الانتهاء من ذلك يجب إضافة سجل من النوع CNAME، يمكن إضافة نطاق فرعي محدد، أو تعيين ذلك بمحرف بدل في حال كنا سنستضيف البرنامج على النطاق الأساسي.
</p>

<p>
	الصورة التالية توضح ما ذكرناه، قمنا بتغطية عناوين IP بما أنها خاصة بالخادم الذي جهزناه لهذا المثال ويجب تعيين عناوين IP الخاصة بالخادم الخاص بك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="png" data-fileid="117499" href="https://academy.hsoub.com/uploads/monthly_2023_01/dns-records-jitsi.png.a3d5f904099e0d34bf87ed9a3f5e2a9a.png" rel=""><img alt="تعديل سجلات DNS" class="ipsImage ipsImage_thumbnailed" data-fileid="117499" data-ratio="64.77" data-unique="br1m6s32q" style="width: 650px; height: auto;" width="650" src="https://academy.hsoub.com/uploads/monthly_2023_01/dns-records-jitsi.thumb.png.555d219202813c1fb5ebd4dd30b908a5.png"> </a>
</p>

<p>
	قد تحتاج للانتظار قليلًا ليتم تطبيق التغييرات ضمن DNS، يمكن استخدام الأمر ping للتأكد من تطبيق التغييرات، عبر تنفيذ الأمر <code>ping</code> على اسم النطاق إلى أن نحصل ضمن الخرج على عنوان IP الخاص بالخادم كالتالي (أيضًا أخفينا العنوان الحقيقي واستبدلناه بمحارف x):
</p>

<pre class="ipsCode" id="ips_uid_5468_12">❯ ping openexperiment.in -4
PING openexperiment.in (xxx.xxx.xxx.xxx) 56(84) bytes of data.
^C64 bytes from xxx.xxx.xxx.xxx: icmp_seq=1 ttl=55 time=36.6 ms

--- openexperiment.in ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 36.610/36.610/36.610/0.000 ms</pre>

<p>
	يمكن أيضًا استخدام الأمر <code>dig</code> للتحقق من سجلات DNS كالتالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5468_16" style=""><span class="pln">dig openexperiment</span><span class="pun">.</span><span class="pln">in </span><span class="pun">+</span><span class="pln">nocmd </span><span class="pun">+</span><span class="pln">nocomments</span></pre>

<p>
	ليظهر لنا خرج مشابه للتالي:
</p>

<pre class="ipsCode">❯ dig openexperiment.in +nocmd +nocomments
;openexperiment.in.   IN  A
openexperiment.in.  2970  IN  A xxx.xxx.xxx.xxx
;; Query time: 1 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Sun Mar 07 11:38:20 IST 2021
;; MSG SIZE  rcvd: 62
</pre>

<h2>
	فهم مكونات Jitsi Meet
</h2>

<p>
	من المفيد فهم مكونات البرنامج ودورها وأهميتها إلى جانب المكونات التي سنستخدمها لنشر التطبيق، يمكنك تجاوز هذه الفقرة إلى فقرة النشر إذا أردت:
</p>

<ul>
	<li>
		<strong>jitsi/web:latest :</strong> وهي واجهة الويب لبرنامج Jitsi Meet التي تستخدمها عبر المتصفح، وهي مرفقة داخل الصورة، مع <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8%D8%8C-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%83%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D9%88%D9%8A%D8%A8-r1/" rel="">خادم ويب Nginx</a>.
	</li>
	<li>
		<strong>jitsi/prosody:latest :</strong> وهو خادم XMPP المسؤول عن الاتصالات المرئية أو الصوتية أو المحادثات النصية، وهو يعتبر أساس Jitsi.
	</li>
	<li>
		<strong>jitsi/jicofo:latest :</strong> وهو مكون من خادم XMPP مسؤول عن إدارة جلسات الفيديو بين المشاركين وجسر الفيديو، بمعنى آخر هو من يدير الاجتماع، وهو عنصر آخر ضروري من Jitsi.
	</li>
	<li>
		<strong>jitsi/jvb:latest :</strong> مكون جسر فيديو Jitsi مسؤول عن نقل قنوات الفيديو القادمة إلى كل المشاركين.
	</li>
</ul>

<p>
	هذه هي المكونات الضرورية لعمل نسخة Jitsi المنشورة وهي فقط ما سنستخدمه ضمن هذا المقال، يوجد مكونات أخرى مثل Jibri و Jigasi ولكنها اختيارية ولن نتطرق إليها، إذا كنت تنوي نشر البرنامج خلف وكيل عكسي تأكد من إعداده أولًا قبل تنفيذ الخطوات التالية.
</p>

<h2>
	استنساخ مستودع docker-jitsi-meet
</h2>

<p>
	يحوي <a href="https://github.com/jitsi/docker-jitsi-meet" rel="external nofollow">المستودع</a> على كل الملفات التي سنحتاجها للنشر مع بعض التعديلات عليها، نستنسخ المستودع ونغير المسار الحالي إلى مسار مجلد المستودع كالتالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5468_19" style=""><span class="pln">git clone https</span><span class="pun">:</span><span class="com">//github.com/jitsi/docker-jitsi-meet jitsi</span><span class="pln">
cd jitsi</span></pre>

<h2>
	تعديل متغيرات البيئة
</h2>

<p>
	بما أننا نستخدم دوكر يجب تغيير بعض متغيرات البيئة، نستخدم نسخة ملف المثال لمتغيرات البيئة env.example المرفق داخل المستودع كقالب عبر تنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5468_21" style=""><span class="pln">cp env</span><span class="pun">.</span><span class="pln">example </span><span class="pun">.</span><span class="pln">env</span></pre>

<p>
	نفتح الملف <code>‎.env</code> ونلاحظ أول ستة متغيرات بيئة معرّفة داخله، بما أن مكونات البرنامج عبارة عن خوادم يتم تشغيلها ضمن حاويات، يجب على تلك الخوادم التعريف عن نفسها أثناء الاتصال ببعضها البعض، وهذا هو هدف تلك النصوص السرية، حيث يجب على العملاء الاستيثاق قبل إنشاء الاتصال،ولا يتم تعيين تلك النصوص السرية يدويًا، بل يوجد نص برمجي يسهل تلك العملية، يمكن تنفيذه داخل مجلد المشروع كالتالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5468_23" style=""><span class="pun">./</span><span class="pln">gen</span><span class="pun">-</span><span class="pln">passwords</span><span class="pun">.</span><span class="pln">sh</span></pre>

<p>
	بعد إنهاء تنفيذه نلاحظ أنه تم ملئ النصوص السرية ضمن ملف <code>‎.env</code>، ولن نحتاج كل تلك النصوص السرية لعملية النشر التي نقوم بها ويمكننا تركها دون حذف أو تعليق.
</p>

<p>
	سنشرح الآن ثلاثة أقسام فرعية لإتمام عملية النشر، الأول لتعيين المتغيرات المشتركة لكلا طريقتي النشر مع وكيل عكسي وبدونه، والثاني للوكيل العكسي، والأخير للطريقة من دون استخدام الوكيل العكسي، كل تلك المتغيرات سيتم تعديلها ضمن الملف ‎.env .
</p>

<h2>
	المتغيرات المشتركة لكل من طريقة الوكيل العكسي وبدونه
</h2>

<p>
	المتغيرات التالية هي المشتركة بين طريقتي النشر، ونضيفها ضمن ملف <code>‎.env</code>:
</p>

<ul>
	<li>
		<strong>CONFIG:</strong> يمكننا تغيير قيمة هذا المتغير لكنها غير ضرورية، قيمته تعبر عن مجلد ضمن المضيف سيتم ربطه وتركيبه ضمن الحاويات لتخزين البيانات داخله، يمكنك تغيير القيمة حسب ما تريد.
	</li>
	<li>
		<strong>PUBLIC_URL:</strong> اسم النطاق الذي سيتم استضافة Jitsi ضمنه مع البروتوكول، في مثالنا تكون قيمة هذا المتغير هي <a href="https://meet.openexperiment.in" ipsnoembed="false" rel="external nofollow">https://meet.openexperiment.in</a>
	</li>
	<li>
		<strong>ENABLE_AUTH:</strong> في حال أردنا تفعيل الاستيثاق، فيجب على المستخدم إدخال اسم المستخدم وكلمة السر الخاصة بحسابه المسجل قبل أن يتمكن من الانضمام أو إنشاء اجتماع، إذا أردت تفعيل تلك الميزة يمكنك إلغاء تعليق هذا السطر وتعيين قيمته إلى 1.
	</li>
	<li>
		<strong>AUTH_TYPE:</strong> إذا عينت قيمة المتغير ENABLE_AUTH إلى 1، فيجب تعيين قيمة هذا المتغير إلى internal، يمكن أيضًا الاستيثاق عبر LDAP أو JWT ولكن لن نتطرق إليهما في هذا المقال.
	</li>
	<li>
		<strong>RESTART_POLICY:</strong> سياسة إعادة تشغيل الحاويات، حيث أن القيمة الافتراضية لها هي <code>unless-stopped</code>، ويفضل تغييرها إلى <code>always</code> أو <code>on-failure</code>.
	</li>
	<li>
		<strong>TZ:</strong> المنطقة الزمنية للنظام، إذا كان الخادم يعمل على المنطقة الزمنية UTC فلا داعي لتغيير هذه القيمة.
	</li>
</ul>

<h2>
	متغيرات طريقة النشر بدون الوكيل العكسي
</h2>

<p>
	إذا لم تكن تستخدم وكيل عكسي يجب إضافة المتغيرات التالية إلى ملف <code>env.</code>:
</p>

<ul>
	<li>
		<strong>HTTP_PORT</strong>, <strong>HTTPS_PORT:</strong> عين تلك القيم إلى 80 و 443 بالترتيب، وهي المنافذ التي ستُربط مع الحاوية.
	</li>
	<li>
		<strong>ENABLE_LETSENCRYPT:</strong> عين قيمتها إلى 1 في حال أردت تفعيل HTTPS.
	</li>
	<li>
		<strong>LETSENCRYPT_DOMAIN</strong> و <strong>LETSENCRYPT_EMAIL:</strong> اسم النطاق الذي ستستضاف عليه النسخة وعنوان البريد لإرسال إشعارات متعلقة بشهادة الحماية.
	</li>
	<li>
		<strong>ENABLE_HTTP_REDIRECT:</strong> عند تعيين قيمتها إلى 1 سيتم إعادة توجيه طلبات <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a> إلى HTTPS.
	</li>
	<li>
		<strong>ENABLE_HSTS:</strong> عند تعيين قيمتها إلى 1 سيتم إجبار المتصفح على استخدام اتصالات موثوقة فقط.
	</li>
</ul>

<h2>
	متغيرات مطلوبة للوكيل العكسي
</h2>

<p>
	إذا كنت ستستخدم وكيل عكسي يجب إضافة المتغيرات التالية إلى ملف <code>env.</code>:
</p>

<ul>
	<li>
		<strong>DISABLE_HTTPS:</strong> بما أن اتصال HTTPS سيتم معالجته من قبل خادم ويب الوكيل العكسي، فلا حاجة لفعيل HTTPS ضمن Jitsi، نعين قيمة هذا المتغير إلى 1.
	</li>
	<li>
		<strong>ENABLE_HTTP_REDIRECT:</strong> عينها إلى 0 لأنها غير ضرورية، حيث أن اتصالات HTTP و HTTPS ستعالج من قبل الوكيل العكسي.
	</li>
	<li>
		<strong>VIRTUAL_HOST</strong> و <strong>LETSENCRYPT_HOST:</strong> هذه المتغيرات غير موجودة ضمن الملف بشكل افتراضي، فيجب إضافتها وتعيين قيمها باسم النطاق الذي ستستضاف عليه النسخة.
	</li>
</ul>

<h2>
	تعديل ملف compose (فقط للوكيل العكسي)
</h2>

<p>
	نفتح ملف <code>docker-compose.yml</code> ضمن أي محرر نصوص، ونعدل خدمة خادم الويب فقط، عبر اتباع الخطوات التالية:
</p>

<ul>
	<li>
		إزالة قسم ports، فلا حاجة لربط أي منافذ من الحاوية إلى المضيف.
	</li>
	<li>
		إضافة شبكة أخرى، نفس الشبكة المستخدمة ضمن إعدادات الوكيل العكسي.
	</li>
	<li>
		تعريف الشبكة في نهاية الملف كالتالي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5468_26" style=""><span class="pln">networks</span><span class="pun">:</span><span class="pln">
  net</span><span class="pun">:</span><span class="pln">
    external</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span></pre>

<p>
	وذلك بفرض اسم الشبكة هو <code>net</code>، يمكنك تغييره بحسب اسم الشبكة الذي لديك.
</p>

<ul>
	<li>
		إضافة متغيري البيئة <code>VIRTUAL_HOST</code> و <code>LETSENCRYPT_HOST</code> كالتالي:
	</li>
</ul>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5468_28" style=""><span class="pun">-</span><span class="pln"> VIRTUAL_HOST
</span><span class="pun">-</span><span class="pln"> LETSENCRYPT_HOST</span></pre>

<h2>
	نشر الحاوية
</h2>

<p>
	بعد الانتهاء من كل التعديلات يمكننا نشر Jitsi عبر تنفيذ الأمر <code>docker-compose up -d</code>، وتأكد بعدها أن حاويات الوكيل العكسي تعمل إذا كنت تستخدم طريقتها، بقي الآن عملية إنشاء مستخدمين جدد لخادم Jitsi.
</p>

<h2>
	إنشاء المستخدمين
</h2>

<p>
	إذا فعلت ميزة الاستيثاق ضمن الإعدادات السابقة، فيجب تسجيل المستخدمين قبل أن يتمكنوا من استخدام Jitsi، يمكن تنفيذ ذلك بسهولة، عبر التوجه إلى مجلد المستودع الذي قمنا باستنساخه وتنفيذ أمر مشابه للتالي مع تبديل اسم المستخدم USERNAME وكلمة السر PASSWORD:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5468_30" style=""><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose exec prosody prosodyctl </span><span class="pun">--</span><span class="pln">config</span><span class="pun">=/</span><span class="pln">config</span><span class="pun">/</span><span class="pln">prosody</span><span class="pun">.</span><span class="pln">cfg</span><span class="pun">.</span><span class="pln">lua </span><span class="kwd">register</span><span class="pln"> </span><span class="pun">[</span><span class="pln">USERNAME</span><span class="pun">]</span><span class="pln"> meet</span><span class="pun">.</span><span class="pln">jitsi </span><span class="pun">[</span><span class="pln">PASSWORD</span><span class="pun">]</span></pre>

<p>
	يمكن حذف مستخدم باستخدام الأمر <code>unregister</code> كالتالي مع تبديل اسم المستخدم بالاسم الصحيح:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5468_32" style=""><span class="pln">docker</span><span class="pun">-</span><span class="pln">compose exec prosody prosodyctl </span><span class="pun">--</span><span class="pln">config</span><span class="pun">=/</span><span class="pln">config</span><span class="pun">/</span><span class="pln">prosody</span><span class="pun">.</span><span class="pln">cfg</span><span class="pun">.</span><span class="pln">lua unregister </span><span class="pun">[</span><span class="pln">USERNAME</span><span class="pun">]</span><span class="pln"> meet</span><span class="pun">.</span><span class="pln">jitsi</span></pre>

<p>
	الآن يمكنك الذهاب إلى واجهة Jitsi Meet عبر المتصفح من خلال عنوان اسم النطاق الذي سجلناه للتطبيق:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileext="jpg" data-fileid="117500" href="https://academy.hsoub.com/uploads/monthly_2023_01/jitsi-meet-home.jpg.cb2727eff64c18379463324a87bb16d6.jpg" rel=""><img alt="إنشاء المستخدمين في Jitsi Meet" class="ipsImage ipsImage_thumbnailed" data-fileid="117500" data-ratio="53.29" data-unique="w9vq104c6" style="width: 700px; height: auto;" width="900" src="https://academy.hsoub.com/uploads/monthly_2023_01/jitsi-meet-home.thumb.jpg.e7625fbfa6f5f307d38ee0af6cf10cfa.jpg"> </a>
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://linuxhandbook.com/self-host-jitsi-meet/" rel="external nofollow">How to Self Host Jitsi Meet With Docker</a> لصاحبه Debdut Chakraborty.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">مدخل إلى خادم الويب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AF%D9%88%D9%83%D8%B1-docker-r607/" rel="">مدخل إلى دوكر Docker</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D8%B9%D9%84%D9%8A%D9%83-%D9%85%D8%B9%D8%B1%D9%81%D8%AA%D9%87-%D8%B9%D9%86-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D9%84%D8%A3%D8%AC%D9%87%D8%B2%D8%A9-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-r456/" rel="">ما عليك معرفته عن الفرق بين دوكر والأجهزة الافتراضية</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">682</guid><pubDate>Sat, 04 Feb 2023 17:00:00 +0000</pubDate></item><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x62A;&#x637;&#x628;&#x64A;&#x642; ASGI Django &#x644;&#x644;&#x646;&#x634;&#x631; &#x645;&#x639; Postgres &#x648;&#x62E;&#x627;&#x62F;&#x645; Nginx &#x645;&#x639; Uvicorn</title><link>https://academy.hsoub.com/devops/deployment/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-asgi-django-%D9%84%D9%84%D9%86%D8%B4%D8%B1-%D9%85%D8%B9-postgres-%D9%88%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%85%D8%B9-uvicorn-r666/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_12/639abdbc8f581_--ASGI-Django---Postgres--Nginx--Uvicorn.png.708f1e19dc23a77f1dd5bb6223a97b86.png" /></p>

<p>
	يُعد جانغو Django إطار عمل ويب قوي يساعدك على إطلاق مشروعك سواءٌ كان تطبيق <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r211/" rel="">بايثون Python</a> أو موقع ويب، ويتضمن خادم تطوير بسيط يُستخدم لاختبار الشيفرة محليًا ولكن من المعلوم أنه عندما يتعلق الأمر بالإطلاق الاحترافي للتطبيق للشركات أو الأعمال فإننا سنحتاج دومًا إلى خادم ويب قوي وأكثر أمانًا.
</p>

<p>
	الطريقة التقليدية لنشر تطبيق جانغو هي استخدام واجهة بوابة خادم الويب Web Server Gateway Interface -أو اختصارًا WSGI- لكن مع ظهور بايثون 3 وتوفُّر ميزة دعم التنفيذ غير المتزامن أصبح بإمكانك تنفيذ تطبيقات بايثون من خلال المستَدعيات غير المتزامنة asynchronous callables مستخدمًا واجهة بوابة خادم غير متزامنة Asynchronous Server Gateway Interface -أو اختصارًا ASGI - ومع كون تقنية ASGI خلفًا لتقنية WSGI فمواصفات ASGI في بايثون قد ورثتها من WSGI وبالتالي يمكن أن تكون بديلًا عنها.
</p>

<p>
	يوفّر جانغو النمط "غير متزامن في الخارج async outside، متزامن في الداخل sync inside" الذي يسمح بأن تكون الشيفرة متزامنة داخليًًا بينما يعالج خادم ASGI الطلبات بصورةٍ غير متزامنة، وبالتالي يمكن <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">لخادم الويب</a> معالجة العديد من الأحداث الواردة من والصادرة إلى كل التطبيقات بمجرد السماح له أن يمتلك مستدعًى callable غير متزامن. يبقى تطبيق جانغو متزامنًا بهدف الحفاظ على التوافقية العكسية backward compatibility وتجنُّب تعقيد الحوسبة التفرعية؛ وهذا يعني أيضًا أن <a href="https://academy.hsoub.com/programming/python/django/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-django%C2%A0-r353/" rel="">تطبيق جانغو</a> الذي تعمل عليه لا يلزمه أي تغييرات لينتقل من WSGI إلى ASGI.
</p>

<p>
	ستعمل في هذا الدليل على تثبيت وضبط بعض المكونات على نظام أوبنتو 20.04 لدعم وخدمة تطبيقات جانغو؛ إذ ستثبّت قاعدة بيانات PostgreSQL بدلًا من SQLite المثبتة افتراضيًا، وستهيء أيضًا خادم تطبيقات غوني كورن Gunicorn المقترن بالخادم يوفي كورن Uvicorn، الذي يُعد أحد تنفيذات ASGI، وذلك بهدف ربطه مع تطبيقاتك بصورةٍ غير متزامنة. بعد ذلك، ستعدّ الخادم إنجن إكس Nginx ليعكس الخادم الوكيل proxy لخادم التطبيقات غوني كورن، الأمر الذي يعطيك وصولًا إلى مزاياه الأمنية ومزايا الأداء لخدمة تطبيقاتك.
</p>

<h2>
	المتطلبات الأساسية
</h2>

<p>
	ستحتاج لإكمال هذه المقالة إلى ما يلي:
</p>

<ul>
<li>
		خادم أوبنتو 20.04 server واحد معدٌّ باتباع التعليمات الواردة في <a href="https://academy.hsoub.com/devops/linux/%D8%A7%D9%84%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A3%D9%88%D9%84%D9%8A%D8%A9-%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r431/" rel="">دليل إعداد خادم مبدئي لنظام أوبنتو</a>، ويتضمن مستخدمًا غيرَ جذري non-root، لكنه يتمتع بصلاحيات sudo.
	</li>
</ul>
<h2>
	الخطوة الأولى - تثبيت الحزم البرمجية من مستودعات أدوات أوبنتو
</h2>

<p>
	ستعمل بدايةً على تنزيل وتثبيت جميع العناصر التي تحتاجها من مستودعات أدوات أوبنتو، وستستخدم مدير حزم بايثون <code>pip</code> لتثبيت المكونات الإضافية في وقتٍ لاحق.
</p>

<p>
	ستحتاج أولًا لتحديث دليل الحزم المحلي <code>apt</code>، وتبدأ بعدها في تنزيل الحزم وتثبيتها، إذ تعتمد الحزم التي تثبتها على رقم إصدار بايثون الذي سيستخدمه مشروعك.
</p>

<p>
	نفّذ الأمر التالي لتثبيت حزم النظام المطلوبة لمشروعك:
</p>

<pre class="ipsCode prettyprint lang-php prettyprinted" id="ips_uid_8767_9" style="">
<span class="pln">$ sudo apt update
$ sudo apt install python3</span><span class="pun">-</span><span class="pln">venv libpq</span><span class="pun">-</span><span class="pln">dev postgresql postgresql</span><span class="pun">-</span><span class="pln">contrib nginx curl</span></pre>

<p>
	سيثبّت هذا الأمر مكتبات بايثون اللازمة لإعداد بيئة افتراضية، ونظام قاعدة بيانات Postgres، والمكتبات اللازمة للتفاعل معها، <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8%D8%8C-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%83%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D9%88%D9%8A%D8%A8-r1/" rel="">وخادم الويب إنجن إكس Nginx</a>.
</p>

<p>
	ستنشئ في الخطوة التالية قاعدة بيانات PostgreSQL ومستخدمًا لها من أجل استخدامها في تطبيق جانغو الذي ستعمل عليه.
</p>

<h2>
	الخطوة الثانية - إنشاء قاعدة بيانات PostgreSQL ومستخدم لها
</h2>

<p>
	ستنشئ الآن قاعدة بيانات ومستخدمًا لها من أجل استخدامهما في تطبيق جانغو الذي ستعمل عليه.
</p>

<p>
	يستخدم نظام قاعدة بيانات Postgres في الحالة الطبيعية مخطط استيثاق يُدعى <strong>استيثاق النظير peer authentication</strong> من أجل الاتصالات المحلية، ويقضي هذا النوع من الاستيثاق بأنه إذا طابق اسم مستخدم username نظام التشغيل اسم مستخدم صالح ضمن قاعدة بيانات Postgres، فسيُعطَى عندئذ صلاحية الدخول إلى قاعدة البيانات دون الحاجة إلى استيثاق إضافي.
</p>

<p>
	لذلك، أثناء تثبيت Postgres، يُنشأ مستخدم نظام التشغيل باسم <code>postgres</code> ليطابق اسم المستخدم الإداري لنظام إدارة قواعد البيانات PostgreSQL، وهو أيضًا باسم <code>postgres</code>، إذ ستحتاج هذا المستخدم لإنجاز المهام الإدارية، علمًا بأنه يمكنك استخدام <code>sudo</code> وتمرير الخيار <code>u-</code> مع اسم المستخدم.
</p>

<p>
	الآن سجّل الدخول إلى جلسة Postgres تفاعلية من خلال الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_12" style="">
<span class="pln">$ sudo </span><span class="pun">-</span><span class="pln">u postgres psql</span></pre>

<p>
	سيظهر لك محث PostgreSQL وهذا يعني أن صار بإمكانك تجهيز متطلباتك.
</p>

<p>
	أولًا، أنشئ قاعدة بيانات للمشروع الذي تعمل عليه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_14" style="">
<span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> CREATE DATABASE myproject</span><span class="pun">;</span></pre>

<p>
	<strong>ملاحظة</strong>: تنتهي كل تعليمة من تعليمات Postgres بفاصلة منقوطة، ولذا إذا واجهتك أي مشاكل فتحقق من أنك ختمت كل التعليمات بفواصل منقوطة.
</p>

<p>
	بعد ذلك، أنشئ مستخدم قاعدة بيانات لمشروعك، واختر كلمة مرور قوية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_16" style="">
<span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> CREATE USER myprojectuser WITH PASSWORD </span><span class="str">'password'</span><span class="pun">;</span></pre>

<p>
	ستعدّل بعد ذلك بعض معاملات الاتصال للمستخدم الذي أنشأته للتو، والهدف من هذه التعديلات هو تسريع عمليات قاعدة البيانات بحيث لا تضطر للاستعلام عن القيم الصحيحة ثم إعدادها في كل مرة يُؤسَّس فيها اتصال.
</p>

<p>
	ستضع التشفير الافتراضي بالقيمة "UTF-8"، وهي التي يتوقعها جانغو، وستحدّد مخطط عزل الإجراءات الافتراضي بالقيمة "read committed" التي تؤدي إلى حجب عمليات القراءة من الإجراءات التي لم تودع بعد uncommitted transactions. أخيرًا، ستحدد المنطقة الزمنية، والقيمة الافتراضية لها في جانغو هي "UTC". جميع هذه التوصيات هي من القائمين على <a href="https://docs.djangoproject.com/en/3.0/ref/databases/#optimizing-postgresql-s-configuration" rel="external nofollow">مشروع جانغو</a>.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_18" style="">
<span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> ALTER ROLE myprojectuser SET client_encoding TO </span><span class="str">'utf8'</span><span class="pun">;</span><span class="pln">
postgres</span><span class="pun">=#</span><span class="pln"> ALTER ROLE myprojectuser SET default_transaction_isolation TO </span><span class="str">'read committed'</span><span class="pun">;</span><span class="pln">
postgres</span><span class="pun">=#</span><span class="pln"> ALTER ROLE myprojectuser SET timezone TO </span><span class="str">'UTC'</span><span class="pun">;</span></pre>

<p>
	يمكنك الآن منح المستخدم الجديد الوصولَ إلى قاعدة البيانات الجديدة ليتمكن من إدارتها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_20" style="">
<span class="pln">postgres</span><span class="pun">=#</span><span class="pln"> GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser</span><span class="pun">;</span></pre>

<p>
	وعقب انتهائك، اخرج من محث PostgreSQL بالتعليمة التالية:
</p>

<pre class="ipsCode">
postgres=# \q
</pre>

<p>
	وبهذا نكون أعددنا نظام Postgres بحيث يتمكن جانغو من الاتصال به وإدارة معلومات <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قاعدة البيانات</a> فيه.
</p>

<h2>
	الخطوة الثالثة- إنشاء بيئة بايثون افتراضية للمشروع
</h2>

<p>
	الآن بعد أن أصبح لديك قاعدة بيانات أصبح بإمكانك تجهيز ما تبقى من متطلبات المشروع. ستثبت فيما يلي متطلبات <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a> ضمن بيئة افتراضية بهدف تسهيل الإدارة.
</p>

<p>
	أنشئ أولًا مجلدًا تحفظ فيه ملفات المشروع وانتقل إلى داخله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_22" style="">
<span class="pln">$ mkdir </span><span class="pun">~/</span><span class="pln">myprojectdir
$ cd </span><span class="pun">~/</span><span class="pln">myprojectdir</span></pre>

<p>
	ثم استخدم أداة البيئة الافتراضية المبنية مسبقًا من بايثون لإنشاء بيئة افتراضية جديدة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_25" style="">
<span class="pln">$ python3 </span><span class="pun">-</span><span class="pln">m venv myprojectenv</span></pre>

<p>
	سينشئ هذا مجلدًا اسمه "myprojectenv" ضمن المجلد "myprojectdir"، إذ سيثبت داخله إصدارًا محليًا من بايثون وإصدارًا محليًا من <code>pip</code>. يمكنك استخدامه لتثبيت وضبط بيئة بايثون معزولة عن المشاريع الأخرى.
</p>

<p>
	ستحتاج قبل تثبيت متطلبات بايثون للمشروع إلى تفعيل البيئة الافتراضية باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_30" style="">
<span class="pln">$ source myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

<p>
	يجب أن يتغير شكل المحث الآن ليشير إلى أنك تعمل داخل البيئة الافتراضية لبايثون. سيبدو هذا مشابهًا لما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_32" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln">user@host</span><span class="pun">:~/</span><span class="pln">myprojectdir$</span></pre>

<p>
	لا بُد من تثبيت جانغو داخل البيئة الافتراضية، إذ يسمح تثبيته داخل بيئة مخصصة لمشروعك للمشاريع الأخرى وبيئاتها أن تُعالج بمعزلٍ عن بعضها بعضًا. الآن وبعد تفعيل البيئة الافتراضية، استخدم النسخة المحلية من الأمر <code>pip</code> لتثبيت كلٍّ من جانغو وغوني كورن ويوفي كورن وموائم PostgreSQL الذي اسمه "psycopg2".
</p>

<p>
	<strong>ملاحظة:</strong> استخدم <code>pip</code> بدلًا من <code>pip3</code> عند تفعيل البيئة الافتراضية، أي عندما يكون المُحث مسبوقًا بالاسم "(myprojectenv)"، حتى لو كنت تستخدم بايثون 3، إذ يُطلق دائمًا يطلق على نسخة الأداة للبيئة الافتراضية اسم <code>pip</code> بغض النظر عن إصدار بايثون.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_34" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ pip install django gunicorn uvicorn psycopg2</span><span class="pun">-</span><span class="pln">binary</span></pre>

<p>
	وبهذا يكون أصبح لديك كل البرمجيات اللازمة لبدء مشروع جانغو.
</p>

<h2>
	الخطوة الرابعة - إنشاء وضبط مشروع جانغو جديد
</h2>

<p>
	يمكنك الآن بعد الانتهاء من تثبيت مكونات بايثون أن تنشئ ملفات مشروع جانغو الفعلية.
</p>

<h3>
	إنشاء مشروع جانغو
</h3>

<p>
	نظرًا لأن مجلد المشروع موجود فعلًا، يمكنك أن تطلب من جانغو أن يثبت الملفات فيه، إذ سينشئ مجلدًا في المستوى الثاني مع الشيفرة الفعلية، وهو أمر طبيعي، ويضع سكريبت script الإدارة في هذا المجلد. الفكرة الرئيسية وراك ذلك هو أن تعرّف المجلد صراحةً بدلًا من ترك المجال لجانغو باتخاذ قرارات لها علاقةٌ بمجلدك الحالي نيابةً عنك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_36" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ django</span><span class="pun">-</span><span class="pln">admin startproject myproject </span><span class="pun">~/</span><span class="pln">myprojectdir</span></pre>

<p>
	الآن، لا بُدّ أن يحتوي مجلد المشروع ("myprojectdir/~" في مقالتنا) على المكونات التالية:
</p>

<ul>
<li>
		"myprojectdir/manage.py/~": سكربت إدارة مشروع جانغو.
	</li>
	<li>
		"/myprojectdir/myproject/~": حزمة مشروع جانغو التي يجب أن تحتوي على الملفات: "init__.py__" و "asgi.py" و "settings.py" و "urls.py" و "wsgi.py".
	</li>
	<li>
		"/myprojectdir/myprojectenv/~": مجلد البيئة الافتراضية الذي أنشأته من قبل.
	</li>
</ul>
<h3>
	ضبط إعدادات المشروع
</h3>

<p>
	ستحتاج بعد إنشاء ملفات المشروع إلى ضبط بعض الإعدادات. افتح ملف الإعدادات بأي محرر نصوص تشاء، واستخدمنا هنا محرر النصوص نانو nano:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_38" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ nano </span><span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ابحث في الملف عن الموجه <code>ALLOWED_HOSTS</code> الذي يعرّف قائمةً بعناوين وأسماء النطاقات للخادم التي يمكن أن تُستخدم للاتصال بنسخة جانغو. لذا ستؤدي أي طلبات واردة بترويسة مضيف وغير موجودة ضمن تلك القائمة إلى ظهور استثناء exception، ويطلب جانغو منك هذا منعًا لصنف معين من الثغرات الأمنية.
</p>

<p>
	أدرج داخل الأقواس المربعة قائمة عناوين IP أو أسماء النطاقات المرافقة لخادم جانغو، وضع كل عنصر بين علامتي اقتباس مع الفصل بين كل عنصرين بفاصلة. للسماح بنطاق كامل وأي نطاقات فرعية، ضع نقطةً في بداية العنصر. لفهم هذا الكلام وضعنا في الشيفرة التالية بعض الأمثلة وسبقناها بعلامة التعليق لتُعامل مثل تعليقات.
</p>

<p>
	<strong>ملاحظة:</strong> تأكد من إضافة <code>localhost</code> ضمن القائمة لأنك ستحتاج إلى خادم وكيل من أجل الاتصالات من خلال نسخة إنجن إكس محلية.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_40" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="com"># The simplest case: just add the domain name(s) and IP addresses of your Django server</span><span class="pln">
</span><span class="com"># ALLOWED_HOSTS = [ 'example.com', '203.0.113.5']</span><span class="pln">
</span><span class="com"># To respond to 'example.com' and any subdomains, start the domain with a dot</span><span class="pln">
</span><span class="com"># ALLOWED_HOSTS = ['.example.com', '203.0.113.5']</span><span class="pln">
ALLOWED_HOSTS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'your_server_domain_or_IP'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'second_domain_or_IP'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.,</span><span class="pln"> </span><span class="str">'localhost'</span><span class="pun">]</span></pre>

<p>
	ثم اعثر على القسم الذي يضبط الوصول إلى قاعدة البيانات، إذ سيبدأ بـكلمة "DATABASES". الضبط الموجود في الملف يخص قاعدة بيانات SQLite التي عدَلنا عن استخدامها إلى البديل PostgreSQL لذا لا بد من ضبط الإعدادات.
</p>

<p>
	غيّر الإعدادات بمعلومات قاعدة بيانات PostgreSQL، وأخبر جانغو بأن يستخدم موائم <code>psycopg2</code> الذي ثبتَّه بواسطة <code>pip</code>. سيُطلب منك التزويد باسم قاعدة البيانات واسم مستخدم قاعدة البيانات وكلمة المرور لمستخدم قاعدة البيانات، ثم تحديد أن قاعدة البيانات موجودة على الجهاز المحلي؛ أما إعدادات المنفذ PORT، فيمكنك تركها سلسلةً نصيةً فارغة.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_42" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

DATABASES </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'default'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'ENGINE'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'django.db.backends.postgresql_psycopg2'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'NAME'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'myproject'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'USER'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'myprojectuser'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'PASSWORD'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'password'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'HOST'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'localhost'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'PORT'</span><span class="pun">:</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	انتقل بعد ذلك إلى آخر الملف وأضف إعدادًا يشير إلى الموضع الذي يجب أن توضع فيه الملفات الساكنة، وهذا ضروري لكي يتمكن إنجن إكس من معالجة الطلبات على هذه العناصر. يطلب السطر التالي من جانغو أن يضعهم في مجلد اسمه "static" في مجلد المشروع الأساسي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_44" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">

STATIC_URL </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/static/'</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> os
STATIC_ROOT </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">BASE_DIR</span><span class="pun">,</span><span class="pln"> </span><span class="str">'static/'</span><span class="pun">)</span></pre>

<p>
	احفظ وأغلق الملف عندما تنتهي.
</p>

<h3>
	إتمام الإعداد الأولي للمشروع
</h3>

<p>
	يمكنك الآن تهجير migrate مخطط قاعدة البيانات الأولي إلى قاعدة بيانات PostgreSQL باستخدام سكريبت الإدارة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_46" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ </span><span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py makemigrations
</span><span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ </span><span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py migrate</span></pre>

<p>
	أنشئ مستخدمًا إدرايًّا للمشروع على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_48" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ </span><span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py createsuperuser</span></pre>

<p>
	ستحتاج إلى التزويد باسم مستخدم وبريد إلكتروني وتختار كلمة مرور ثم تؤكدها.
</p>

<p>
	يمكنك أن تجمع كل المحتوى الساكن في موقع المجلد الذي هيّأته وذلك على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_50" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ </span><span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py collectstatic</span></pre>

<p>
	ستحتاج إلى تأكيد العملية. بعد ذلك ستُوضع الملفات الساكنة في مجلد اسمه "static" ضمن مجلد المشروع.
</p>

<p>
	يُفترض إذا كنت قد اتبعت دليل إعداد الخادم الأولي أن يكون لديك جدار ناري UFW يحمي خادمك، وستحتاج للسماح بالوصول إلى المنفذ الذي تستخدمه إذا أردت اختبار خادم التطوير.
</p>

<p>
	أنشئ استثناءً للمنفذ 8000 على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_53" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ sudo ufw allow </span><span class="lit">8000</span></pre>

<p>
	يمكنك الآن وأخيرًا اختبار مشروعك بتشغيل خادم تطوير جانغو على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_55" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ </span><span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py runserver </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span></pre>

<p>
	اذهب إلى اسم نطاق الخادم الذي تستخدمه أو عنوان IP متبوعًا برقم المنفذ 8000 في متصفحك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_57" style="">
<span class="pln">http</span><span class="pun">://</span><span class="pln">server_domain_or_IP</span><span class="pun">:</span><span class="lit">8000</span></pre>

<p>
	يفترض الآن أن تظهر لك صفحة فهرس جانغو الافتراضية:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="114093" href="https://academy.hsoub.com/uploads/monthly_2022_12/002-new_django.index.png.e8405e10d0656f9930dfeca8e30402ca.png" rel=""><img alt="صفحة فهرس جانغو الافتراضية" class="ipsImage ipsImage_thumbnailed" data-fileid="114093" data-unique="tspc2wc8s" src="https://academy.hsoub.com/uploads/monthly_2022_12/002-new_django.index.thumb.png.f4c60956294ab9290b78bceb74ad5c0f.png" style="width: 650px; height: auto;"></a>
</p>

<p>
	سيُطلب منك إدخال اسم المستخدم وكلمة المرور اللذين أنشأتهما بالأمر <code>createsuperuser</code>،إذا ألحقت "admin/" بنهاية <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> في شريط العنوان :
</p>

<p style="text-align: center;">
	<img alt="إدخال اسم المستخدم وكلمة المرور المنشأتين بالأمر createsuperuser" class="ipsImage ipsImage_thumbnailed" data-fileid="114094" data-unique="32w9g8tw3" src="https://academy.hsoub.com/uploads/monthly_2022_12/003-admin_login.png.61cca10fb7313bb45aa11de1c4ccbf6f.png" style="width: 350px; height: auto;"></p>

<p>
	يمكنك بعد انتهاء التحقق الوصولُ إلى الواجهة الافتراضية لإدارة جانغو:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="114095" href="https://academy.hsoub.com/uploads/monthly_2022_12/004-admin_interface.png.14f3ec3f9409a69087d6a025ce597f9f.png" rel=""><img alt="الواجهة الافتراضية لإدارة جانغو" class="ipsImage ipsImage_thumbnailed" data-fileid="114095" data-unique="ontgfss0c" src="https://academy.hsoub.com/uploads/monthly_2022_12/004-admin_interface.thumb.png.ca7dbee71c329a3c7974a71c280e3eeb.png" style="width: 650px; height: auto;"></a>
</p>

<p>
	اضغط على المفتاحين "CTRL+C" بعد انتهاء عملية الاستكشاف في النافذة الطرفية لإيقاف تشغيل خادم التطوير.
</p>

<h3>
	اختبار قدرة خادم غوني كورن على تخديم المشروع
</h3>

<p>
	سنستخدم في مقالتنا هذه خادم "غوني كورن Gunicorn" جنبًا إلى جنب مع خادم "يوفي كورن Uvicorn" لتشغيل التطبيق. ومع أن غوني كورن يُستخدم عادةً لنشر تطبيقات WSGI لكنه يُزوَّد بواجهة قابلة للتوصيل pluggable لتأمين نشر ASGI، وهو يفعل هذا من خلال السماح لك باستهلاك صنف عامل يظهر بواسطة خادم ASGI (وهو يوفي كورن). ونظرًا لأن الخادم "غوني كورن" هو منتج أعلى نضجًا من الخادم "يوفي كورن" ويزود بضبطٍ أكثر منه، ينصح أنصار الخادم "يوفي كورن" باستخدام الخادم "غوني كورن" مع الصنف العامل للخادم "يوفي كورن" لأنه خادم ومدير عمليات ذي مزايا مكتملة.
</p>

<p>
	ستفحص خادم غوني كورن قبل مغادرة البيئة الافتراضية لتتأكد من أن بإمكانه تخديم التطبيق.
</p>

<p>
	الآن لكي تستخدم الأصناف العاملة لخادم يوفي كورن مع خادم غوني كورن، ادخل إلى مجلد المشروع واستخدم الأمر <code>gunicorn</code> التالي لتحميل وحدة ASGI للمشروع.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_64" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ cd </span><span class="pun">~/</span><span class="pln">myprojectdir
</span><span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ gunicorn </span><span class="pun">--</span><span class="pln">bind </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> myproject</span><span class="pun">.</span><span class="pln">asgi </span><span class="pun">-</span><span class="pln">w </span><span class="lit">4</span><span class="pln"> </span><span class="pun">-</span><span class="pln">k uvicorn</span><span class="pun">.</span><span class="pln">workers</span><span class="pun">.</span><span class="typ">UvicornWorker</span></pre>

<p>
	سيشغّل هذا الأمر خادم غوني كورن على نفس الواجهة التي كان يعمل عليها خادم تطوير جانغو. يمكنك الآن العودة واختبار التطبيق من جديد.
</p>

<p>
	<strong>ملاحظة:</strong> ليس مطلوبًا استخدام خادم غوني كورن لتشغيل تطبيق ASGI، ولكي تستخدم فقط خادم يوفي كورن، استعن بالأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_66" style="">
<span class="pln">uvicorn myproject</span><span class="pun">.</span><span class="pln">asgi</span><span class="pun">:</span><span class="pln">application </span><span class="pun">--</span><span class="pln">host </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pln"> </span><span class="pun">--</span><span class="pln">port </span><span class="lit">8080</span></pre>

<p>
	<strong>ملاحظة</strong>: لن يكون في واجهة الإدارة أي <a href="https://academy.hsoub.com/programming/css/%d8%aa%d8%b9%d8%b1%d9%91%d9%81-%d8%b9%d9%84%d9%89-%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a7%d8%aa-css-r70/" rel="">تنسيقات CSS</a> مطبقة، لأن الخادم غوني كورن لا يعرف كيف يعثر على محتوى <a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> الساكن المسؤول عن هذا.
</p>

<p>
	إذا كانت صفحة ترحيب جانغو لا تزال ظاهرة حتى بعد تنفيذ هذه الأوامر، فإن هذا يعدّ تأكيدًا على أن الخادم غوني كورن قد خدّم الصفحة فعلًا وأنه يعمل على النحو المطلوب، وما قد حصل هو أنك عندما استخدمت الأمر <code>gunicorn</code>، فقد مرّرت وحدة module إلى خادم غوني كورن بتحديد مسار المجلد ذي الصلة إلى الملف asgi.py الخاص بـجانغو والذي يعدّ نقطة الدخول إلى تطبيقك، باستخدام صيغة وحدة بايثون. عُرّف داخل هذا الملف دالةٌ اسمها "application"، ستُستخدم للتواصل مع التطبيق.
</p>

<p>
	إذا أردت معرفة المزيد عن مواصفات ASGI، زر <a href="https://asgi.readthedocs.io/en/latest/specs/main.html" rel="external nofollow">الموقع الرسمي للخادم ASGI</a>.
</p>

<p>
	اضغط بعد انتهائك من الاختبار على المفتاحين "CTRL+C" في نافذة الطرفية لإيقاف تشغيل خادم غوني كورن.
</p>

<p>
	وبهذا تكون انتهيت من ضبط تطبيق جانغو، ويمكنك الآن الخروج من البيئة الافتراضية بكتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_72" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln"> $ deactivate</span></pre>

<p>
	عندئذٍ سيُحذف مؤشر البيئة الافتراضية في محث النافذة التي تعمل فيها.
</p>

<h2>
	الخطوة الخامسة - إنشاء مقبس systemd وملفات خدمة لـخادم غوني كورن
</h2>

<p>
	تأكدتَ في القسم السابق من قدرة خادم غوني كورن على التفاعل مع تطبيق جانغو، وستنفّذ في الخطوة الحالية طريقةً أقوى لتشغيل وإطفاء خادم التطبيقات عبر إنشاء خدمة systemd وملفات المقبس socket files.
</p>

<p>
	سيُنشأ مقبس خادم غوني كورن عند الإقلاع وسيستمع إلى الاتصالات، وعندما يحدث اتصال ستشغِّل خدمة systemd تلقائيًّا عمليةَ الخادم غوني كورن لتولي مهمة التعامل مع الاتصال.
</p>

<p>
	الآن ابدأ بإنشاء ملف مقبس systemd لخادم غوني كورن وافتحه بمحرر النصوص الذي تريد وبصلاحيات <code>sudo</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_74" style="">
<span class="pln">$ sudo nano </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	ستنشئ في داخل الملف القسم <code>[Unit]</code> لوصف المقبس والقسم <code>[Socket]</code> لتعريف موضع المقبس والقسم <code>[Install]</code> للتأكد أن المقبس يُنشأ في الوقت المناسب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_76" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">gunicorn socket

</span><span class="pun">[</span><span class="typ">Socket</span><span class="pun">]</span><span class="pln">
</span><span class="typ">ListenStream</span><span class="pun">=/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock

</span><span class="pun">[</span><span class="typ">Install</span><span class="pun">]</span><span class="pln">
</span><span class="typ">WantedBy</span><span class="pun">=</span><span class="pln">sockets</span><span class="pun">.</span><span class="pln">target</span></pre>

<p>
	احفظ الملف، ثم أغلقه عندما تنتهي. بعد ذلك أنشئ ملف خدمة systemd للخادم غوني كورن، وافتحه بصلاحيات <code>sudo</code> في محرر النصوص الذي تستخدمه، وتنبه إلى أنه يجب أن يطابق اسمُ ملف الخدمة استثناءَ اسم ملف المقبس للامتداد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_78" style="">
<span class="pln">$ sudo nano </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">service</span></pre>

<p>
	ابدأ بالقسم <code>[Unit]</code> الذي يستخدم لتحديد البيانات الوصفية metadata والاعتماديات. ستحدد هنا وصفًا لخدمتك وتخبر نظام التهيئة init system أن يشغل هذا فقط عند الوصول إلى مستوى التشبيك المستهدف، ونظرًا لأن الخدمة تعتمد على المقبس والموجود وصفه في ملف المقبس، فستحتاج إلى تضمين الموجّه <code>Requires</code> للإشارة إلى تلك العلاقة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_80" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">gunicorn daemon
</span><span class="typ">Requires</span><span class="pun">=</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target</span></pre>

<p>
	افتح بعد ذلك القسم <code>[Service]</code>، إذ ستحدّد في هذا القسم المستخدم والمجموعة اللتين تريد أن تُشغّل العملية من أجلهما. ستمنح حساب المستخدم النظامي ملكيةَ العملية لامتلاكه كافة الملفات اللازمة، وستعطي ملكية المجموعة للمجموعة "www-data" ليتمكنَ الخادم إنجن إكس من التواصل بسهولة مع خادم غوني كورن.
</p>

<p>
	بعد ذلك، ستعدّ المجلد العامل وتحدد الأمر الذي ستستخدمه لتشغيل الخدمة، إذ ستحتاج في حالتنا هذه لتحديد المسار الكامل للملف التنفيذي لخادم غوني كورن المثبت ضمن البيئة الافتراضية. ستربِط العملية مع مقبس يونيكس <code>Unix</code> الذي أنشأته ضمن المجلد "run/" لتتمكن العملية من التواصل مع الخادم إنجن إكس، وستُرسل كل سجلات البيانات إلى جهاز الخرج القياسي لتتمكن العملية <code>journald</code> من جمع سجلات الخادم غوني كورن. يمكنك أيضًا أن تحدد أي إعدادات من اختيارك للخادم غوني كورن. وفيما يلي مثالٌ عن تحديد ثلاث عمليات عاملة:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_88" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">gunicorn daemon
</span><span class="typ">Requires</span><span class="pun">=</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target

</span><span class="pun">[</span><span class="typ">Service</span><span class="pun">]</span><span class="pln">
</span><span class="typ">User</span><span class="pun">=</span><span class="pln">sammy
</span><span class="typ">Group</span><span class="pun">=</span><span class="pln">www</span><span class="pun">-</span><span class="pln">data
</span><span class="typ">WorkingDirectory</span><span class="pun">=</span><span class="str">/home/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir
</span><span class="typ">ExecStart</span><span class="pun">=</span><span class="str">/home/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn \
          </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> \
          </span><span class="pun">-</span><span class="pln">k uvicorn</span><span class="pun">.</span><span class="pln">workers</span><span class="pun">.</span><span class="typ">UvicornWorker</span><span class="pln"> \
          </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> \
          </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:</span><span class="str">/run/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock \
          myproject</span><span class="pun">.</span><span class="pln">asgi</span><span class="pun">:</span><span class="pln">application</span></pre>

<p>
	القسم الأخير هو <code>[Install]</code>، الذي سيخبر systemd عما سيربطه مع هذه الخدمة إذا اخترنا تشغيلها عند الإقلاع. سترغب غالبًا بتشغيل هذه الخدمة عندما يكون نظام المتعدد المستخدمين في حالة عمل:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_86" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">gunicorn daemon
</span><span class="typ">Requires</span><span class="pun">=</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target

</span><span class="pun">[</span><span class="typ">Service</span><span class="pun">]</span><span class="pln">
</span><span class="typ">User</span><span class="pun">=</span><span class="pln">sammy
</span><span class="typ">Group</span><span class="pun">=</span><span class="pln">www</span><span class="pun">-</span><span class="pln">data
</span><span class="typ">WorkingDirectory</span><span class="pun">=</span><span class="str">/home/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir
</span><span class="typ">ExecStart</span><span class="pun">=</span><span class="str">/home/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn \
          </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> \
          </span><span class="pun">-</span><span class="pln">k uvicorn</span><span class="pun">.</span><span class="pln">workers</span><span class="pun">.</span><span class="typ">UvicornWorker</span><span class="pln"> \
          </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> \
          </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:</span><span class="str">/run/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock \
          myproject</span><span class="pun">.</span><span class="pln">asgi</span><span class="pun">:</span><span class="pln">application

</span><span class="pun">[</span><span class="typ">Install</span><span class="pun">]</span><span class="pln">
</span><span class="typ">WantedBy</span><span class="pun">=</span><span class="pln">multi</span><span class="pun">-</span><span class="pln">user</span><span class="pun">.</span><span class="pln">target</span></pre>

<p>
	وبهذا يكون ملف خدمة systemd قد اكتمل. بإمكانك الآن أن تحفظه وتغلقه.
</p>

<p>
	يمكنك الآن تشغيل وتفعيل مقبس الخادم غوني كورن، إذ سينشئ هذا ملف المقبس في المسار "run/gunicorn.sock/" الآن وعند الإقلاع. عندما يُنشأ اتصال مع ذلك المقبس، ستشغّل systemd الخدمة "gunicorn.service" تلقائيًا لمعالجتها.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_90" style="">
<span class="pln">$ sudo systemctl start gunicorn</span><span class="pun">.</span><span class="pln">socket
$ sudo systemctl enable gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	الآن وبعد أن أنشأت ملفات المقبس وخدمة systemd، بإمكانك التأكد من نجاح ما أنجزته من خلال التأكد من وجود ملف المقبس.
</p>

<h2>
	الخطوة السادسة - التأكد من وجود ملف مقبس الخادم غوني كورن
</h2>

<p>
	ستتحقق في هذه الخطوة من ملف مقبس خادم غوني كورن. ابدأ أولًا بفحص حالة العملية لمعرفة ما إذا كانت قادرة على البدء:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_92" style="">
<span class="pln">$ sudo systemctl status gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	سيبدو خرج العملية السابقة قريبًا من التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_94" style="">
<span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">socket </span><span class="pun">-</span><span class="pln"> gunicorn socket
     </span><span class="typ">Loaded</span><span class="pun">:</span><span class="pln"> loaded </span><span class="pun">(/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket</span><span class="pun">;</span><span class="pln"> enabled</span><span class="pun">;</span><span class="pln"> vendor prese</span><span class="pun">&gt;</span><span class="pln">
     </span><span class="typ">Active</span><span class="pun">:</span><span class="pln"> active </span><span class="pun">(</span><span class="pln">listening</span><span class="pun">)</span><span class="pln"> since </span><span class="typ">Fri</span><span class="pln"> </span><span class="lit">2020</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">26</span><span class="pln"> </span><span class="lit">17</span><span class="pun">:</span><span class="lit">53</span><span class="pun">:</span><span class="lit">10</span><span class="pln"> UTC</span><span class="pun">;</span><span class="pln"> </span><span class="lit">14s</span><span class="pln"> ago
   </span><span class="typ">Triggers</span><span class="pun">:</span><span class="pln"> </span><span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">service
     </span><span class="typ">Listen</span><span class="pun">:</span><span class="pln"> </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock </span><span class="pun">(</span><span class="typ">Stream</span><span class="pun">)</span><span class="pln">
      </span><span class="typ">Tasks</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">limit</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1137</span><span class="pun">)</span><span class="pln">
     </span><span class="typ">Memory</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0B</span><span class="pln">
     </span><span class="typ">CGroup</span><span class="pun">:</span><span class="pln"> </span><span class="pun">/</span><span class="pln">system</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	ثم تحقق من وجود ملف "gunicorn.sock" ضمن المجلد "run/":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_96" style="">
<span class="pln">$ file </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock</span></pre>

<p>
	وسيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_98" style="">
<span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock</span><span class="pun">:</span><span class="pln"> socket</span></pre>

<p>
	إذا أشار الأمر <code>systemctl status</code> إلى أن خطأً ما قد حدث، أو إذا لم تجد الملف gunicorn.sock في المجلد، سيدّل هذا على أن مقبس غوني كورن لم يُنشأ على نحوٍ صحيح. تفّقد سجلات مقبس غوني كورن بالأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_100" style="">
<span class="pln">$ sudo journalctl </span><span class="pun">-</span><span class="pln">u gunicorn</span><span class="pun">.</span><span class="pln">socket </span></pre>

<p>
	ألق نظرةً أخرى على الملف في الرابط التالي لإصلاح أيّ مشاكل قبل المتابعة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_102" style="">
<span class="str">"etc/systemd/system/gunicorn.socket/"</span></pre>

<h2>
	الخطوة السابعة - فحص تنشيط المقبس
</h2>

<p>
	ستفحص في هذه الخطوة تنشيط المقبس، فإذا كنت في الوقت الحالي قد شغّلت فقط الوحدة <code>gunicorn.socket</code>، فلن تكون الخدمة <code>gunicorn.service</code> نشطةً لأن المقبس لم يكن قد استلم أي اتصالات بعد، ويمكنك فحص هذه الحالة بالأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_105" style="">
<span class="pln">$ sudo systemctl status gunicorn</span></pre>

<p>
	ويكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_109" style="">
<span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">service </span><span class="pun">-</span><span class="pln"> gunicorn daemon
   </span><span class="typ">Loaded</span><span class="pun">:</span><span class="pln"> loaded </span><span class="pun">(/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">service</span><span class="pun">;</span><span class="pln"> disabled</span><span class="pun">;</span><span class="pln"> vendor preset</span><span class="pun">:</span><span class="pln"> enabled</span><span class="pun">)</span><span class="pln">
   </span><span class="typ">Active</span><span class="pun">:</span><span class="pln"> inactive </span><span class="pun">(</span><span class="pln">dead</span><span class="pun">)</span></pre>

<p>
	لاختبار آلية تنشيط المقبس يمكنك إرسال اتصال إلى المقبس عبر الأمر <code>curl</code> على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_111" style="">
<span class="pln">$ curl </span><span class="pun">--</span><span class="pln">unix</span><span class="pun">-</span><span class="pln">socket </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock localhost</span></pre>

<p>
	يفترض أن تتلقى الخرج على صيغة <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> من تطبيقك في الطرفية، والذي يشير إلى أن خادم غوني كورن قد شُغّل وأنه كان قادرًا على تخديم تطبيق جانغو الذي تعمل عليه. يمكن فحص ما إذا كانت خدمة غوني كورن في وضع التشغيل بكتابة الأمر التالي:
</p>

<pre class="ipsCode">
$ sudo systemctl status gunicorn
</pre>

<p>
	وسيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_114" style="">
<span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">service </span><span class="pun">-</span><span class="pln"> gunicorn daemon
     </span><span class="typ">Loaded</span><span class="pun">:</span><span class="pln"> loaded </span><span class="pun">(/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">service</span><span class="pun">;</span><span class="pln"> disabled</span><span class="pun">;</span><span class="pln"> vendor preset</span><span class="pun">:</span><span class="pln"> enabled</span><span class="pun">)</span><span class="pln">
     </span><span class="typ">Active</span><span class="pun">:</span><span class="pln"> active </span><span class="pun">(</span><span class="pln">running</span><span class="pun">)</span><span class="pln"> since </span><span class="typ">Thu</span><span class="pln"> </span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> UTC</span><span class="pun">;</span><span class="pln"> </span><span class="lit">13s</span><span class="pln"> ago
</span><span class="typ">TriggeredBy</span><span class="pun">:</span><span class="pln"> </span><span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">socket
   </span><span class="typ">Main</span><span class="pln"> <abbr title="Process IDentifier | معرّف العملية أو البرنامج">PID</abbr></span><span class="pun">:</span><span class="pln"> </span><span class="lit">11682</span><span class="pln"> </span><span class="pun">(</span><span class="pln">gunicorn</span><span class="pun">)</span><span class="pln">
      </span><span class="typ">Tasks</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">(</span><span class="pln">limit</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4682</span><span class="pun">)</span><span class="pln">
     </span><span class="typ">Memory</span><span class="pun">:</span><span class="pln"> </span><span class="lit">98.5M</span><span class="pln">
     </span><span class="typ">CGroup</span><span class="pun">:</span><span class="pln"> </span><span class="pun">/</span><span class="pln">system</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">service
             </span><span class="pun">├─</span><span class="lit">11682</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">python3 </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> </span><span class="pun">-</span><span class="pln">k uvicorn</span><span class="pun">.</span><span class="pln">workers</span><span class="pun">.</span><span class="typ">UvicornWorker</span><span class="pln"> </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock myproject</span><span class="pun">.</span><span class="pln">asgi</span><span class="pun">:</span><span class="pln">application
             </span><span class="pun">├─</span><span class="lit">11705</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">python3 </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> </span><span class="pun">-</span><span class="pln">k uvicorn</span><span class="pun">.</span><span class="pln">workers</span><span class="pun">.</span><span class="typ">UvicornWorker</span><span class="pln"> </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock myproject</span><span class="pun">.</span><span class="pln">asgi</span><span class="pun">:</span><span class="pln">application
             </span><span class="pun">├─</span><span class="lit">11707</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">python3 </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> </span><span class="pun">-</span><span class="pln">k uvicorn</span><span class="pun">.</span><span class="pln">workers</span><span class="pun">.</span><span class="typ">UvicornWorker</span><span class="pln"> </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock myproject</span><span class="pun">.</span><span class="pln">asgi</span><span class="pun">:</span><span class="pln">application
             </span><span class="pun">└─</span><span class="lit">11708</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">python3 </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> </span><span class="pun">-</span><span class="pln">k uvicorn</span><span class="pun">.</span><span class="pln">workers</span><span class="pun">.</span><span class="typ">UvicornWorker</span><span class="pln"> </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock myproject</span><span class="pun">.</span><span class="pln">asgi</span><span class="pun">:</span><span class="pln">application

</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11705</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11705</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> ASGI </span><span class="str">'lifespan'</span><span class="pln"> protocol appears unsupported</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11705</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11705</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Application</span><span class="pln"> startup complete</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11707</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11707</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Started</span><span class="pln"> server process </span><span class="pun">[</span><span class="lit">11707</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11707</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11707</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Waiting</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> application startup</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11707</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11707</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> ASGI </span><span class="str">'lifespan'</span><span class="pln"> protocol appears unsupported</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11707</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11707</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Application</span><span class="pln"> startup complete</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11708</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11708</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Started</span><span class="pln"> server process </span><span class="pun">[</span><span class="lit">11708</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11708</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11708</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Waiting</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> application startup</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11708</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11708</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> ASGI </span><span class="str">'lifespan'</span><span class="pln"> protocol appears unsupported</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> django gunicorn</span><span class="pun">[</span><span class="lit">11708</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2021</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">10</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">03</span><span class="pun">:</span><span class="lit">30</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">11708</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Application</span><span class="pln"> startup complete</span><span class="pun">.</span></pre>

<p>
	إذا كان الخرج من <code>curl</code> أو الخرج من <code>systemctl status</code> يشير إلى أن مشكلة ما قد حدثت فراجع السجلات للحصول على تفاصيل أكثر كما يلي:
</p>

<pre class="ipsCode">
$ sudo journalctl -u gunicorn
</pre>

<p>
	افحص الملف "etc/systemd/system/gunicorn.service/" بحثًا عن أي مشكلات، وإذا أجريت أي تغييرات على هذا الملف، فأعد تحميل العملية الخفية daemon لكي تعيد قراءة تعريف الخدمة، وأعد تشغيل العملية غوني كورن بكتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_116" style="">
<span class="pln">$ sudo systemctl daemon</span><span class="pun">-</span><span class="pln">reload
$ sudo systemctl restart gunicorn</span></pre>

<p>
	تأكد من فحص المسائل المذكورة أعلاه بحثًا عن أي أخطاء قبل المتابعة.
</p>

<h2>
	الخطوة الثامنة - ضبط إنجن إكس لتمرير الخادم الوكيل إلى غوني كورن
</h2>

<p>
	ستحتاج الآن بعد أن فرغت من إعداد غوني كورن إلى ضبط إنجن إكس Nginx لتمرير البيانات إلى العملية. ستعد في هذه الخطوة إنجن إكس أمام غوني كورن لتستفيد من آليات معالجة الاتصال عالية الأداء والمزايا الأمنية سهلة التنفيذ التي يتمتع بها.
</p>

<p>
	ابدأ العمل بإنشاء وفتح كتلة خادم جديد في مجلد sites-available الخاص بخادم إنجن إكس:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_118" style="">
<span class="pln">$ sudo nano </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">available</span><span class="pun">/</span><span class="pln">myproject</span></pre>

<p>
	افتتح في الداخل كتلة خادم جديدة، وحدّد أن على هذه الكتلة الاستماع إلى المنفذ الاعتيادي 80 والاستجابة لاسم نطاق الخادم أو عنوان IP، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_122" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name server_domain_or_IP</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ثم اطلب من إنجن إكس أن يتجاهل أي مشاكل بخصوص العثور على أيقونة مفضلة favicon، وأخبره أيضًا أين سيجد الأصول الساكنة static assets التي جمعتها في المجلد "myprojectdir/static/~". تمتلك جميع هذه الملفات سابقة URI prefix معيارية هي "static/"، لذا يمكنك أن تنشئ كتلة موقع location لوضع هذه الطلبات:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_134" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name server_domain_or_IP</span><span class="pun">;</span><span class="pln">

    location </span><span class="pun">=</span><span class="pln"> </span><span class="pun">/</span><span class="pln">favicon</span><span class="pun">.</span><span class="pln">ico </span><span class="pun">{</span><span class="pln"> access_log off</span><span class="pun">;</span><span class="pln"> log_not_found off</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
    location </span><span class="pun">/</span><span class="kwd">static</span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        root </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أخيرًا أنشئ كتلة مواقع" location / {}‎" لوضع سائر الطلبات الأخرى، وضمِّن داخل هذا الموقع الملف المعياري proxy_params المُضمَّن مع تثبيت إنجن إكس، ثم مرّر حركة المرور traffic مباشرةً إلى مقبس غوني كورن :
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_127" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name server_domain_or_IP</span><span class="pun">;</span><span class="pln">

    location </span><span class="pun">=</span><span class="pln"> </span><span class="pun">/</span><span class="pln">favicon</span><span class="pun">.</span><span class="pln">ico </span><span class="pun">{</span><span class="pln"> access_log off</span><span class="pun">;</span><span class="pln"> log_not_found off</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
    location </span><span class="pun">/</span><span class="kwd">static</span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        root </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        include proxy_params</span><span class="pun">;</span><span class="pln">
        proxy_pass http</span><span class="pun">:</span><span class="com">//unix:/run/gunicorn.sock;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	احفظ الملف وأغلقه عند انتهائك، ويمكنك الآن تفعيل الملف بربطه بالمجلد "sites-enabled":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_137" style="">
<span class="pln">$ sudo ln </span><span class="pun">-</span><span class="pln">s </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">available</span><span class="pun">/</span><span class="pln">myproject </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">enabled</span></pre>

<p>
	تأكد من خلو ضبط إنجن إكس من الأخطاء الكتابية بكتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_139" style="">
<span class="pln">$ sudo nginx </span><span class="pun">-</span><span class="pln">t</span></pre>

<p>
	إذا لم يُبلَّغ عن أي أخطاء، استمر وأعد تشغيل إنجن إكس من خلال كتابة:
</p>

<pre class="ipsCode">
$ sudo systemctl restart nginx
</pre>

<p>
	أخيرًا ستحتاج إلى فتح الجدار الناري أمام حركة المرور العادية على المنفذ 80، ونظرًا لعدم وجود حاجة إلى الوصول إلى خادم التطوير، فيمكنك إزالة القاعدة لفتح المنفذ 8000 أيضًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_141" style="">
<span class="pln">$ sudo ufw delete allow </span><span class="lit">8000</span><span class="pln">
$ sudo ufw allow </span><span class="str">'Nginx Full'</span></pre>

<p>
	يفترض أن تكون الآن قادرًا على الذهاب إلى نطاق خادمك أو عنوان IP له لرؤية صفحة الترحيب لجانغو التي تظهر عليها صورة الصاروخ.
</p>

<p>
	<strong>ملاحظة</strong>: الخطوة التالية بعد أن تفرغ من إعداد إنجن إكس هي تأمين حركة المرور الذاهبة إلى الخادم باستخدام <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr>/<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr>؛ وهذا مهم إذ ستُرسل جميع المعلومات بما فيها كلمات المرور عبر الشبكة بصيغة نص صرف plain text دون استخدامه، وأبسط طريقة للحصول على <a href="https://academy.hsoub.com/devops/servers/%d9%83%d9%8a%d9%81%d9%8a%d8%a9-%d8%aa%d8%ab%d8%a8%d9%8a%d8%aa-%d8%b4%d9%87%d8%a7%d8%af%d8%a9-ssl-%d9%85%d9%86-%d8%b3%d9%84%d8%b7%d8%a9-%d8%b4%d9%87%d8%a7%d8%af%d8%a7%d8%aa-%d8%aa%d8%ac%d8%a7%d8%b1%d9%8a%d8%a9-%d8%a7%d9%84%d9%85%d9%81%d8%a7%d9%87%d9%8a%d9%85-%d8%a7%d9%84%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a9-r146/" rel="">شهادة <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></a> لتأمين المرور إذا كان لديك اسم نطاق هي استخدام موقع <a href="https://letsencrypt.org/" rel="external nofollow">Let's Encrypt</a>. اتبع <a href="https://academy.hsoub.com/devops/servers/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8-%D8%B4%D9%87%D8%A7%D8%AF%D8%A9-ssl-%D9%85%D8%AC%D8%A7%D9%86%D9%8A%D8%A9-%D8%B9%D8%A8%D8%B1-%D8%AE%D8%AF%D9%85%D8%A9-lets-encrypt-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D9%84%D9%8A%D9%86%D9%83%D8%B3-r151/" rel="">هذا الدليل</a> لإعداد Let's Encrypt مع إنجن إكس على أوبنتو 20.04، واتبع خطوات الإجراء باستخدام كتلة خادم إنجن إكس التي أنشأتها في هذه المقالة.
</p>

<h2>
	الخطوة التاسعة - استكشاف أخطاء إنجن إكس وغوني كورن وإصلاحها
</h2>

<p>
	إذا لم تُظهِر الخطوة الأخيرة تطبيقك، فستحتاج حينئذٍ إلى استكشاف الأخطاء في التثبيت وإصلاحها. وفيما يلي نعرض أبرز الأخطاء التي يمكن أن تظهر:
</p>

<h3>
	يظهر إنجن إكس الصفحة الافتراضية بدلًا من إظهار تطبيق جانغو
</h3>

<p>
	إذا كان إنجن إكس يعرض الصفحة الافتراضية بدلًا من الإحالة إلى تطبيقك، فهذا يعني غالبًا أنك تحتاج إلى ضبط قيمة "server_name" ضمن الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_145" style="">
<span class="str">"etc/nginx/sites-available/myproject/"</span></pre>

<p>
	بحيث يشير إلى عنوان IP أو اسم النطاق للخادم؛ إذ يستخدم إنجن إكس القيمة "server_name لتحديد كتلة الخادم التي سيستخدمها للاستجابة للطلبات. إذا استلمت صفحة إنجن إكس الافتراضية، فهذه إشارة إلى أن إنجن إكس لم يكن قادرًا على مطابقة الطلب مع كتلة الخادم صراحةً ولذلك انسحب إلى الكتلة الافتراضية المعرّفة في الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_147" style="">
<span class="str">"etc/nginx/sites-available/default/"</span></pre>

<p>
	يجب أن يكون اسم الخادم "server_name" في كتلة خادم المشروع محددًا أكثر من الموجود في كتلة الخادم الافتراضي الذي جرى اختياره.
</p>

<h3>
	يعرض إنجن إكس الخطأ 502 Bad Gateway Error بدلا من عرض تطبيق جانغو
</h3>

<p>
	يشير الخطأ 502 إلى أن إنجن إكس غير قادر على إحالة الطلب. كثيرًا ما تُظهِر مشكلات الضبط نفسها بإبراز الخطأ 502، لذلك فإننا نحتاج إلى مزيد من المعلومات لنستشكف الأخطاء ونصلحها بالطريقة الصحيحة.
</p>

<p>
	وأول مكان يجب أن نبحث فيه عن مزيد من المعلومات هو سجل أخطاء إنجن إكس، الذي سيخبرك عمومًا عن الظروف التي سببت المشكلات أثناء حدث الإحالة. تابع سجلات الخطأ لإنجن إكس بكتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_159" style="">
<span class="pln">$ sudo tail </span><span class="pun">-</span><span class="pln">F </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">error</span><span class="pun">.</span><span class="pln">log</span></pre>

<p>
	والآن أنشئ طلبًا جديدًا في متصفحك لتوليد خطأ جديد (حاول إنعاش refresh الصفحة). يفترض أن تستلم رسالة خطأ جديدة تظهر في السجل، وإذا نظرت إلى الرسالة فيفترض أن تساعدك على تضييق نطاق المشكلة.
</p>

<p>
	قد تستلم الرسالة التالية:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_155" style="">
<span class="pln">connect</span><span class="pun">()</span><span class="pln"> to unix</span><span class="pun">:</span><span class="str">/run/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock failed </span><span class="pun">(</span><span class="lit">2</span><span class="pun">:</span><span class="pln"> </span><span class="typ">No</span><span class="pln"> such file </span><span class="kwd">or</span><span class="pln"> directory</span><span class="pun">)</span></pre>

<p>
	يشير هذا إلى أن إنجن إكس لم يستطع العثور على الملف "gunicorn.sock" في الموضع المُعطى. ينبغي أن تقارن موقع "proxy_pass" المعرف ضمن الملف "etc/nginx/sites-available/myproject/" مع الموقع الفعلي للملف "gunicorn.sock" الذي ولدته وحدة "gunicorn.socket" التابعة لخدمة systemd.
</p>

<p>
	إذا لم تعثر على الملف ضمن المجلد، فهذا يعني غالبًا أن ملف مقبس خدمة systemd لم يستطع إنشاءه، لذلك عُد إلى القسم الذي فحصنا فيه ملف المقبس لغوني كورن لكي تتابع في خطوات استكشاف أخطاء غوني كورن وإصلاحها.
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_153" style="">
<span class="pln">connect</span><span class="pun">()</span><span class="pln"> to unix</span><span class="pun">:</span><span class="str">/run/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock failed </span><span class="pun">(</span><span class="lit">13</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Permission</span><span class="pln"> denied</span><span class="pun">)</span></pre>

<p>
	يشير هذا إلى أن إنجن إكس لم يستطع الاتصال بمقبس غوني كورن بسبب مشكلات تتعلق بالصلاحيات، إذ يمكن أن يحدث هذا عندما يستخدِم الإجراءُ المستخدِم الجذر بدلًا من مستخدم "sudo"؛ فمع قدرة systemd على إنشاء ملف مقبس غوني كورن، لا يكون إنجن إكس قادرًا على الوصول إليه.
</p>

<p>
	يمكن أن يحدث هذا إذا كان هناك أذونات محدودة في أي نقطة بين المجلد الجذر "/" والملف "gunicorn.sock". يمكنك مراجعة الأذونات وقيم الملكية لملف المقبس وكل من مجلداته الآباء بتمرير المسار المطلق المؤدي إلى ملف المقبس إلى الأمر <code>namei</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_165" style="">
<span class="pln">$ namei </span><span class="pun">-</span><span class="pln">l </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock</span></pre>

<p>
	وسيكون الخرج على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_167" style="">
<span class="pln">f</span><span class="pun">:</span><span class="pln"> </span><span class="str">/run/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock
drwxr</span><span class="pun">-</span><span class="pln">xr</span><span class="pun">-</span><span class="pln">x root root </span><span class="pun">/</span><span class="pln">
drwxr</span><span class="pun">-</span><span class="pln">xr</span><span class="pun">-</span><span class="pln">x root root run
srw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln"> root root gunicorn</span><span class="pun">.</span><span class="pln">sock</span></pre>

<p>
	يظهر في الخرج أذونات كل مكون من مكونات المجلد، ويمكنك بالنظر إلى الأذونات (العمود الأول) والمالك (العمود الثاني) ومالك المجموعة (العمود الثالث)، أن تكتشف نوع الوصول المسموح لملف المقبس.
</p>

<p>
	يملك ملف المقبس وكل المجلدات التي تؤدي إلى ملف المقبس في المثال الوارد أعلاه أذونات قراءة وتنفيذ (ينتهي عمود الأذونات للمجلدات بالخيارات <code>r-x</code> بدلًا من <code>---</code>). ينبغي أن تكون العملية إنجن إكس قادرة على الوصول إلى المقبس بنجاح.
</p>

<p>
	إذا لم تمتلك أي من المجلدات التي تؤدي إلى المقبس أذونات قراءة وتنفيذ عامّة، لن يتمكن إنجن إكس من الوصول إلى المقبس دون السماح بأذونات قراءة وتنفيذ عامة، أو التأكد من أن ملكية المجموعات معطاة لمجموعة يكون إنجن إكس جزءًا منها.
</p>

<h3>
	جانغو يعرض رسالة could not connect to server: Connection refused
</h3>

<p>
	إحدى الرسائل المتوقع أن تتلقاها من جانغو عند محاولة الوصول إلى أجزاء من التطبيق في متصفح الويب هي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_169" style="">
<span class="typ">OperationalError</span><span class="pln"> at </span><span class="pun">/</span><span class="pln">admin</span><span class="pun">/</span><span class="pln">login</span><span class="pun">/</span><span class="pln">
could </span><span class="kwd">not</span><span class="pln"> connect to server</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Connection</span><span class="pln"> refused
    </span><span class="typ">Is</span><span class="pln"> the server running on host </span><span class="str">"localhost"</span><span class="pln"> </span><span class="pun">(</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> accepting
    TCP</span><span class="pun">/</span><span class="pln">IP connections on port </span><span class="lit">5432</span><span class="pun">?</span></pre>

<p>
	تشير هذه الرسالة إلى أن جانغو لا يستطيع الاتصال بقاعدة بيانات Postgres. تأكد من أن نسخة Postgres تعمل بكتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_173" style="">
<span class="pln">$ sudo systemctl status postgresql</span></pre>

<p>
	إذا لم تكن تعمل، يمكنك تشغيلها وتفعيلها تلقائيًا عند الإقلاع -إذا لم تكن مضبوطة مسبقًا لذلك- من خلال كتابة ما يلي:
</p>

<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_175" style="">
<span class="pln">$ sudo systemctl start postgresql
$ sudo systemctl enable postgresql</span></pre>

<p>
	إذا بقي لديك مشكلات، تأكد من أن إعدادات قاعدة البيانات المعرفة في الملف التالي صحيحة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_179" style="">
<span class="str">"myprojectdir/myproject/settings.py/~"</span></pre>

<h2>
	المزيد من استكشاف الأخطاء وإصلاحها
</h2>

<p>
	لمزيد من استكشاف الأخطاء وإصلاحها، يمكنك الاستعانة بالسجلات لأنها تساعد في تضييق نطاق الأسباب الجذرية. افحص كلًا منها بالترتيب وابحث عن رسائل تشير إلى مناطق المشكلة.
</p>

<p>
	يمكن أن تساعدك السجلات التالية:
</p>

<ul>
<li>
		افحص سجلات العملية إنجن إكس من خلال كتابة الأمر التالي:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_182" style="">
<span class="pln">sudo journalctl </span><span class="pun">-</span><span class="pln">u nginx</span></pre>

<ul>
<li>
		افحص سجلات الوصول إلى إنجن إكس من خلال كتابة الأمر التالي:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_184" style="">
<span class="pln">sudo less </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">access</span><span class="pun">.</span><span class="pln">log</span></pre>

<ul>
<li>
		افحص سجلات أخطاء إنجن إكس من خلال كتابة الأمر التالي:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_187" style="">
<span class="pln">sudo less </span><span class="pun">/</span><span class="kwd">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">error</span><span class="pun">.</span><span class="pln">log</span></pre>

<ul>
<li>
		افحص سجلات تطبيق غوني كورن من خلال كتابة الأمر التالي:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_189" style="">
<span class="pln">sudo journalctl </span><span class="pun">-</span><span class="pln">u gunicorn</span></pre>

<ul>
<li>
		افحص سجلات مقبس غوني كورن من خلال كتابة الأمر التالي:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-sql prettyprinted" id="ips_uid_8767_194" style="">
<span class="pln">sudo journalctl </span><span class="pun">-</span><span class="pln">u gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	أثناء تحديثك للضبط أو التطبيق الخاص بك، ستحتاج غالبًا لإعادة تشغيل العمليات لتُضبط مع التغييرات التي أجريتها.
</p>

<p>
	إذا حدّثتَ تطبيق جانغو يمكنك إعادة تشغيل عملية "غوني كورن" لتطبيق التغييرات من خلال كتابة:
</p>

<pre class="ipsCode">
$ sudo systemctl restart gunicorn
</pre>

<p>
	إذا غيرت مقبس غوني كورن أو ملفات الخدمة، أعد تحميل العفريت daemon ثم أعد تشغيل العملية من خلال كتابة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_196" style="">
<span class="pln">$ sudo systemctl daemon</span><span class="pun">-</span><span class="pln">reload
$ sudo systemctl restart gunicorn</span><span class="pun">.</span><span class="pln">socket gunicorn</span><span class="pun">.</span><span class="pln">service</span></pre>

<p>
	إذا غيّرت ضبط كتلة الخادم إنجن إكس، افحص الضبط ثم إنجن إكس من خلال كتابة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8767_198" style="">
<span class="pln">$ sudo nginx </span><span class="pun">-</span><span class="pln">t </span><span class="pun">&amp;&amp;</span><span class="pln"> sudo systemctl restart nginx</span></pre>

<p>
	ستساعدك هذه الأوامر لتطبيق التغييرات التي تجريها أثناء الضبط.
</p>

<h2>
	الخاتمة
</h2>

<p>
	أعددت في هذا الدليل مشروع جانغو لخادم ASGI في بيئته الافتراضية الخاصة، وهيأت خادمي غوني كورن ويوفي كورن لترجمة طلبات العميل بنسق غير متزامن ليتمكن جانغو من التعامل مع هذه الطلبات. بعد ذلك، أعددت إنجن إكس ليأخذ دور خادم وكيل عكسي لمعالجة اتصالات العميل وخدمة المشروع الصحيح اعتماداً على طلب العميل.
</p>

<p>
	يبسّط جانغو عملية إنشاء الكائنات والتطبيقات من خلال تزويدك بكثير من القطع المشتركة، مما يسمح لك بالتركيز على العناصر المتفردة unique. وهكذا بالاستفادة من سلسلة الأدوات العامة التي وردت في هذه المقالة، أصبح بإمكانك خدمة التطبيقات التي ستنشئها مستقبلًا من خادم وحيد.
</p>

<p>
	ترجمة - وبتصرف - للمقالة <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-asgi-django-app-with-postgres-nginx-and-uvicorn-on-ubuntu-20-04" rel="external nofollow">How To Set Up an ASGI Django App with Postgres, Nginx, and Uvicorn on Ubuntu 20.04</a> لصاحبيها Mason Egger و Erin Glass.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-r1625/" rel="">البدء مع إطار العمل جانغو لإنشاء تطبيق ويب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%AA%D9%88%D8%B5%D9%8A%D9%84%D9%87-%D8%A8%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r1626/" rel="">إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%AC%D8%A7%D9%87%D8%B2-%D9%84%D9%84%D9%86%D8%B4%D8%B1-%D9%85%D8%B9-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgres-%D9%88%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%88-gunicorn-r663/" rel="">إعداد تطبيق جانغو جاهز للنشر مع قاعدة بيانات Postgres وخادم Nginx و Gunicorn</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">666</guid><pubDate>Thu, 15 Dec 2022 07:04:38 +0000</pubDate></item><item><title>&#x625;&#x639;&#x62F;&#x627;&#x62F; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62C;&#x627;&#x646;&#x63A;&#x648; &#x62C;&#x627;&#x647;&#x632; &#x644;&#x644;&#x646;&#x634;&#x631; &#x645;&#x639; &#x642;&#x627;&#x639;&#x62F;&#x629; &#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; Postgres &#x648;&#x62E;&#x627;&#x62F;&#x645; Nginx &#x648; Gunicorn</title><link>https://academy.hsoub.com/devops/deployment/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%AC%D8%A7%D9%87%D8%B2-%D9%84%D9%84%D9%86%D8%B4%D8%B1-%D9%85%D8%B9-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgres-%D9%88%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%88-gunicorn-r663/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_11/637493c78920d_----Postgres--Nginx--Gunicorn.jpg.1098bb262b410d787ec0847a8581fc70.jpg" /></p>

<p>
	جانغو هو أشهر أطر العمل البرمجية المكتوبة <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r211/" rel="">بلغة بايثون</a>، إذ يساعدك هذا الإطار المميز على تطوير تطبيقات الويب ووضعها في الخدمة بسرعة ويُسر، ويحتوي جانغو خادم تطوير مُضمّن يتيح لك اختبار الكود محليًا والتأكد من عمله إلّا أنه مبسط يلبي أغراض التجربة فقط، فأي تطبيق يُطلق في بيئة العمل الفعلية مهما بلغت بساطته يتطلب <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم ويب</a> بمواصفات أعلى وحماية أكبر.
</p>

<p>
	سنُحاكي في هذا المقال بيئة عمل كاملة لتطبيق جانغو مبنية على خادم أوبونتو إصدار 20.04، ونعرض كيفية تثبيت وإعداد المكونات اللازمة لعمله على الخادم، بدءًا من بناء <a href="https://academy.hsoub.com/files/18-%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A-%D8%A5%D9%84%D9%89-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgresql/" rel="">قاعدة بيانات PostgreSQL</a> بدلًا من SQLite (قاعدة بيانات جانغو الافتراضية)، ومن ثم إعداد خادم التطبيق من نوع Gunicorn، وصولًا إلى تهيئة <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D9%91%D8%A9-%D8%B6%D8%A8%D8%B7-nginx-%D9%84%D9%84%D8%B9%D9%85%D9%84-%D9%83%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-%D9%88%D9%83%D9%88%D8%B3%D9%8A%D8%B7-%D8%B9%D9%83%D8%B3%D9%8A-%D9%84%D9%80-apache-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-ubuntu-1804-r396/" rel="">خادم وكيل عكسي</a> من نوع Nginx يعمل أمام Gunicorn ويقدم لتطبيقنا كل ما يملكه من مميزات تدعم أمانه وأدائه.
</p>

<h2>
	متطلبات بيئة العمل والأهداف
</h2>

<p>
	ستحتاج إلى خادم بنظام تشغيل أوبونتو وقد استعملنا في هذا المقال الإصدار 20.04، مع مستخدم –ليس مسؤول- ولكنه يتمتع بصلاحيات عالية <code>sudo</code>، بالإضافة إلى تفعيل جدار الحماية الافتراضي على الخادم، ويمكنك الاستعانة بمقال <a href="https://academy.hsoub.com/devops/linux/%D8%A7%D9%84%D8%AA%D9%87%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D8%A3%D9%88%D9%84%D9%8A%D8%A9-%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r431/" rel="">التهيئة الأولية لخادم أوبونتو</a>.
</p>

<p>
	سنثبت جانغو في بيئةٍ افتراضية لما تقدمه من مرونة تتيح لنا التعامل مع التطبيق ومتطلباته الخارجية بطريقة منفصلة أي كل على حدة، وسنبدأ بإعداد قاعدة البيانات وبمجرد الانتهاء منها وتشغيل التطبيق، سنثبت Gunicorn ليتعامل مع طلبات العملاء الواردة عبر <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a> ويحولها إلى أوامر بايثون يعالجها التطبيق، ومن ثم سنشغل خادم وسيط عكسي هو Nginx أمام Gunicorn للاستفادة من قدراته العالية في التعامل الفعال والآمن مع حركة البيانات الواردة إلى التطبيق.
</p>

<p>
	لنبدأ التطبيق العملي!
</p>

<h2>
	تثبيت الحزم اللازمة من مستودعات أوبونتو
</h2>

<p>
	سنُنثبت معظم المكونات اللازمة للعمل من مستودعات أوبونتو، ومن ثم سنستخدم <code>pip</code> مدير حزم بايثون لتثبيت مكونات أخرى خاصة بالتطبيق.
</p>

<p>
	البداية دومًا مع تحديث <code>apt</code> دليل الحزم المحلي لأوبونتو، ومن بعدها سنبدأ بتحميل الحزم اللازمة وتثبيتها بما يتوافق مع إصدار بايثون الذي تستخدمه فالأوامر تختلف قليلًا بين بايثون 2 و 3، علمًا أن جانغو 1.11 هو أحدث إصدار من الإصدارات التي تدعم بايثون 2، لذا ننصحك بترقية إطار العمل الذي تعتمده واستخدام بايثون 3 إن كنت تنشئ مشروعًا جديدًا.
</p>

<p>
	إن كنت تستخدم بايثون 3 اكتب الأوامر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_9" style="">
<span class="pln">sudo apt update
sudo apt install python3</span><span class="pun">-</span><span class="pln">pip python3</span><span class="pun">-</span><span class="pln">dev libpq</span><span class="pun">-</span><span class="pln">dev postgresql postgresql</span><span class="pun">-</span><span class="pln">contrib nginx curl</span></pre>

<p>
	أما أوامر بايثون 2 فهي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_11" style="">
<span class="pln">sudo apt update
sudo apt install python</span><span class="pun">-</span><span class="pln">pip python</span><span class="pun">-</span><span class="pln">dev libpq</span><span class="pun">-</span><span class="pln">dev postgresql postgresql</span><span class="pun">-</span><span class="pln">contrib nginx curl</span></pre>

<p>
	ثبتنا بموجب الأوامر السابقة <code>pip</code> وملفات تطوير جانغو اللازمة لبناء Gunicorn لاحقًا و نظام قواعد البيانات Postgres مع المكاتب اللازمة للتفاعل معه وأخيرًا خادم الويب Nginx.
</p>

<h2>
	إنشاء قاعدة البيانات PostgreSQL ومستخدم التطبيق
</h2>

<p>
	يمتلك نظام قواعد بيانات Postgres ميزةً افتراضية تسمى توثيق النِّدّ Peer Authentication وهي خاصة بمصادقة الاتصالات المحلية، وتعني أن وجود مستخدم في نظام التشغيل يطابق اسمه اسم مستخدم Postgres فعال، يمنح هذا المستخدم -أي مستخدم نظام التشغيل- القدرة على الدخول إلى Postgres دون الحاجة إلى توثيق.
</p>

<p>
	وقد أُنشئ مستخدم لنظام التشغيل اسمه <code>postgres</code> ليتوافق مع مستخدم <code>postgres</code> المدير لنظام PostgreSQL، وسنستفيد من هذا المستخدم للمهام الإدارية، مع إمكانية استخدام sudo وتمرير اسم المستخدم بعد الراية <code>u-</code>.
</p>

<p>
	سجل الدخول إلى Postgres عبر الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_13" style="">
<span class="pln">sudo </span><span class="pun">-</span><span class="pln">u postgres psql</span></pre>

<p>
	ستظهر لك نافذة أوامر PostgreSQL، اكتب فيها تعليمة إنشاء قاعدة بيانات المشروع:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_16" style="">
<span class="pln">CREATE DATABASE myproject</span><span class="pun">;</span></pre>

<p>
	<strong>تنويه</strong>: تذكر أن تنهي كافة أوامر Postgres بفاصلة منقوطة لتضمن التنفيذ السليم.
</p>

<p>
	أنشئ بعدها مستخدم قاعدة البيانات واختر له كلمة سر قوية، وفق الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_18" style="">
<span class="pln">CREATE USER myprojectuser WITH PASSWORD </span><span class="str">'password'</span><span class="pun">;</span></pre>

<p>
	اضبط الآن القيم الافتراضية لمحددات اتصال المستخدم المنشأ بالتوافق مع <a href="https://docs.djangoproject.com/en/2.0/ref/databases/#optimizing-postgresql-s-configuration" rel="external nofollow">توصيات جانغو</a> بحيث لا يتطلب الأمر الاستعلام عنها وإعادة تعيينها مع كل تأسيس جديد للاتصال ما سينعكس إيجابًا على تسريع عمليات قاعدة البيانات، والمحددات المطلوب ضبطها هي: الترميز <code>encoding</code> الذي يأخذ القيمة الافتراضية <code>UTF-8</code>، وأسلوب عزل العملية <code>transaction isolation scheme</code> الذي يوضع "read committed" ومهمته منع قراءة العمليات غير المكتملة uncommitted transactions، وأخيرًا المنطقة الزمنية نثبتها على <code>UTC</code>، وفق التعليمات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_20" style="">
<span class="pln">ALTER ROLE myprojectuser SET client_encoding TO </span><span class="str">'utf8'</span><span class="pun">;</span><span class="pln">
ALTER ROLE myprojectuser SET default_transaction_isolation TO </span><span class="str">'read committed'</span><span class="pun">;</span><span class="pln">
ALTER ROLE myprojectuser SET timezone TO </span><span class="str">'UTC'</span><span class="pun">;</span></pre>

<p>
	وأخيرًا امنح مستخدم قاعدة البيانات المنشأ صلاحيات الإدارة الكاملة لقاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_23" style="">
<span class="pln">GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser</span><span class="pun">;</span></pre>

<p>
	يمكنك الآن الخروج من موجه أوامر PostgreSQL بكتابة الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_25" style="">
<span class="pln">\q</span></pre>

<h2>
	إنشاء بيئة بايثون الافتراضية
</h2>

<p>
	بعد أن جهزنا قاعدة بيانات التطبيق سننتقل الآن لتثبيت كافة متطلبات بايثون اللازمة له ضمن بيئة افتراضية سهلة الإدارة، ولكن أولًا علينا تثبيت <code>virtualenv</code> بعد تحديث <code>pip</code>، ستختلف أوامر التثبيت اختلافًا بسيطًا بين بايثون 2 وبايثون 3.
</p>

<p>
	إن كنت تستخدم بايثون 3 اكتب الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_27" style="">
<span class="pln">sudo </span><span class="pun">-</span><span class="pln">H pip3 install </span><span class="pun">--</span><span class="pln">upgrade pip
sudo </span><span class="pun">-</span><span class="pln">H pip3 install virtualenv</span></pre>

<p>
	أما لبايثون 2 فاكتب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_29" style="">
<span class="pln">sudo </span><span class="pun">-</span><span class="pln">H pip install </span><span class="pun">--</span><span class="pln">upgrade pip
sudo </span><span class="pun">-</span><span class="pln">H pip install virtualenv</span></pre>

<p>
	أنشئ مجلدًا باسم مشروعك، ثم انتقل إلى داخله:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_31" style="">
<span class="pln">mkdir </span><span class="pun">~/</span><span class="pln">myprojectdir
cd </span><span class="pun">~/</span><span class="pln">myprojectdir</span></pre>

<p>
	وأنشئ البيئة الافتراضية ضمنه بكتابة الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_34" style="">
<span class="pln">virtualenv myprojectenv</span></pre>

<p>
	بموجب الأوامر السابقة، سيُنشَأ ضمن مجلد مشروعك <code>myprojectdir</code> مجلدًا فرعيًا للبيئة الافتراضية اسمه <code>myprojectenv</code>، وسيُثبت بداخله نسخة محلية من بايثون ومدير الحزم <code>pip</code> محلي.
</p>

<p>
	تؤمن هذه البيئة الافتراضية بيئة بايثون معزولة لمشروعنا ولكنها تحتاج لتفعيل قبل البدء باستخدامها وتنزيل ما يلزم ضمنها، والتفعيل وفق الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_36" style="">
<span class="pln">source myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">activate</span></pre>

<p>
	سيتغير بعدها محث سطر الأوامر ليشير إلى أنك تعمل في البيئة الافتراضية وسيبدو مثل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_38" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln">user@host</span><span class="pun">:~/</span><span class="pln">myprojectdir$</span></pre>

<p>
	يمكنك الآن باستخدام <code>pip</code> تثبيت جانغو و Gunicorn و <code>psycopg2</code> الخاص بالتخاطب مع قاعدة البيانات التي بنيناها:
</p>

<p>
	<strong>تنويه</strong>: يُستخدم pip دومًا داخل البيئة الافتراضية وليس pip3 بغض النظر عن إصدار بايثون المعتمد.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_40" style="">
<span class="pln">pip install django gunicorn psycopg2</span><span class="pun">-</span><span class="pln">binary</span></pre>

<p>
	يفترض الآن تثبيت كل المتطلبات اللازمة لبدء مشروع جانغو Django جديد.
</p>

<h2>
	إنشاء وإعداد مشروع جانغو جديد
</h2>

<p>
	يمكننا الآن إنشاء ملفات المشروع بعد أن أعددنا متطلبات بايثون اللازمة له ضمن البيئة الافتراضية.
</p>

<h3>
	إنشاء مشروع جانغو Django
</h3>

<p>
	لدينا بالفعل مجلدًا لمشروعنا هو myprojectdir ولذا سنكتبه في أمر إنشاء المشروع حتى يثبت جانغو الملفات ضمنه بدلًا من اختياره مجلدًا آخر تلقائيًا، وعند تنفيذ الأمر سيُنشئ مجلدًا فرعيًا ضمن مجلد المشروع يحمل نفس الاسم ليحتوي على الشيفرة البرمجية أو كود التطبيق ويضاف فيه أيضًا السكربت الخاص بالإدارة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_42" style="">
<span class="pln">django</span><span class="pun">-</span><span class="pln">admin startproject myproject </span><span class="pun">~/</span><span class="pln">myprojectdir</span></pre>

<p>
	وبناءً على ذلك سيتضمن مجلد المشروع (وهو في حالتنا <code>myprojectdir/~</code>) العناصر التالية:
</p>

<ul>
<li>
		<code>myprojectdir/manage.py/~</code>: سكربت الإدارة الخاص بجانغو.
	</li>
	<li>
		<code>/myprojectdir/myproject/~</code>: حزمة مشروع جانغو، ويجب أن تتضمن الملفات التالية:
	</li>
	<li>
		‎init__.py__
	</li>
	<li>
		settings.py
	</li>
	<li>
		urls.py
	</li>
	<li>
		asgi.py
	</li>
	<li>
		wsgi.py
	</li>
	<li>
		<code>/myprojectdir/myprojectenv/~</code>: مجلد البيئة الافتراضية المنشأة في الخطوة السابقة.
	</li>
</ul>
<h3>
	ضبط إعدادات مشروع جانغو
</h3>

<p>
	سنعمل في هذه الفقرة على ثلاث إعدادات هي قائمة العناوين وأسماء النطاقات المسموح باستخدامها للاتصال مع جانغو، وقاعدة البيانات المعتمدة، ومسار ملفات التطبيق الساكنة.
</p>

<p>
	افتح ملف الإعدادات باستعمال محرر النصوص لنبدأ بهذه التعديلات بالترتيب:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_44" style="">
<span class="pln">nano </span><span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	ابحث عن المحدد <code>ALLOWED_HOSTS</code> إذ سنبدأ تعديلاتنا به، ويحميك هذا المحدد من الاتصالات غير المشروعة، ووظيفته تعريف الخوادم المسموح باستخدامها للاتصال بالتطبيق، وتُعرف باستخدام عنوان IP الخادم أو اسم النطاق أو اسم النطاق الفرعي، وأي طلب يرد للتطبيق يحمل في ترويسته اسم مضيف غير معرف ضمن هذا المحدد فسيُمنع.
</p>

<p>
	تُكتب قائمة عناوين IP أو أسماء النطاقات المسموح لها بالاتصال ضمن الأقواس المربعة، وكل عنوان أو اسم منها ضمن اشارتي اقتباس مفردتين، ويفصل بين كل قيمة وأخرى فاصلة، ولو رغبت بالإشارة إلى النطاق الرئيس وكل ما يحوي من نطاقات فرعية فاستخدم النقطة قبل اسم النطاق، انظر الأمثلة التالية لمزيد من الإيضاح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_46" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="com"># The simplest case: just add the domain name(s) and IP addresses of your Django server</span><span class="pln">
</span><span class="com"># ALLOWED_HOSTS = [ 'example.com', '203.0.113.5']</span><span class="pln">
</span><span class="com"># To respond to 'example.com' and any subdomains, start the domain with a dot</span><span class="pln">
</span><span class="com"># ALLOWED_HOSTS = ['.example.com', '203.0.113.5']</span><span class="pln">
ALLOWED_HOSTS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="str">'your_server_domain_or_IP'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'second_domain_or_IP'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.,</span><span class="pln"> </span><span class="str">'localhost'</span><span class="pun">]</span></pre>

<p>
	<strong>تنويه</strong>: تأكد من إضافة الخادم المحلي <code>localhost</code> على قائمة الأجهزة المسموح لها بالاتصال إذ إننا سننشئ عليه وكيل محلي Nginx.
</p>

<p>
	المحدد التالي للضبط هو <code>DATABASES</code> الخاص بقواعد البيانات، ابحث عنه ضمن ملف الإعدادات وسنُعدّله ليناسب قاعدة بيانات PostgreSQL التي أعددناها بدلًا من SQLite قاعدة البيانات الافتراضية لجانغو.
</p>

<p>
	تتمثل تعديلاتنا عليه في جعل جانغو يستخدم المحول <code>psycopg2</code> (سبق أن ثبتناه باستخدام <code>pip</code>) ليناسب التخاطب مع قاعدة البيانات الجديدة PostgreSQL، ومن ثم كتابة اسم قاعدة البيانات الصحيح واسم مستخدم قاعدة البيانات وكلمة المرور الخاصة به، وأيضًا تحديد مضيف قاعدة البيانات وهو في حالتنا الخادم المحلي، أما بالنسبة لبوابة الاتصال مع قاعدة البيانات <code>port</code> فيمكنك تركها فارغة، انظر التعليمات أدناه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_48" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
DATABASES </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'default'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'ENGINE'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'django.db.backends.postgresql_psycopg2'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'NAME'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'myproject'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'USER'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'myprojectuser'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'PASSWORD'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'password'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'HOST'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'localhost'</span><span class="pun">,</span><span class="pln">
        </span><span class="str">'PORT'</span><span class="pun">:</span><span class="pln"> </span><span class="str">''</span><span class="pun">,</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span></pre>

<p>
	أما التعديل الأخير على الملف settings.py فهو إضافة مسار ملفات التطبيق الساكنة، وهذا التعديل ضروري ليتمكن Nginx من معرفة مكان هذه الملفات والتعامل مع الطلبات الواردة بخصوصها، انتقل الآن إلى نهاية الملف واكتب الأسطر التالية التي ستحدد مكان وجود هذه الملفات ويُشار إليه بـ <code>static</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_50" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
STATIC_URL </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/static/'</span><span class="pln">
</span><span class="kwd">import</span><span class="pln"> os
STATIC_ROOT </span><span class="pun">=</span><span class="pln"> os</span><span class="pun">.</span><span class="pln">path</span><span class="pun">.</span><span class="pln">join</span><span class="pun">(</span><span class="pln">BASE_DIR</span><span class="pun">,</span><span class="pln"> </span><span class="str">'static/'</span><span class="pun">)</span></pre>

<p>
	والآن احفظ الملف وأغلقه.
</p>

<h3>
	إكمال الإعداد الأولي للمشروع
</h3>

<p>
	بعد أن ضبطنا إعدادات قاعدة البيانات يمكننا الآن تهجير مخطط قاعدة البيانات الأولي إلى قاعدة بيانات PostgreSQL المعتمدة في مشروعنا وذلك باستخدام سكريبت الإدارة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_52" style="">
<span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py makemigrations
</span><span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py migrate</span></pre>

<p>
	ومن بعدها سننشئ حساب المستخدم المسؤول عن إدارة التطبيق بكتابة الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_54" style="">
<span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py createsuperuser</span></pre>

<p>
	وسيُطلب منك النظام إدخال اسم مستخدم لهذا الحساب وكلمة مرور وعنوان بريد إلكتروني.
</p>

<p>
	أما الآن سننفذ عملية جمع المكونات الساكنة للتطبيق static assets في المسار الذي حددناه في الفقرة السابقة وذلك وفق الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_56" style="">
<span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py collectstatic</span></pre>

<p>
	سيُطلب منك تأكيد العملية، وبعدها ستُجمع الملفات الساكنة ضمن مجلد مشروعك في المكان الذي أطلقنا عليه اسم <code>static</code>.
</p>

<p>
	كما ورد في متطلبات بيئة العمل في بداية المقال فإن الخادم أوبونتو يجب أن يتضمن جدار ناري فعال هو <a href="https://academy.hsoub.com/devops/security/firewalls/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-ufw-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D9%88%D8%A3%D9%88%D8%A7%D9%85%D8%B1-%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-%D9%84%D9%84%D8%AC%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D9%86%D8%A7%D8%B1%D9%8A-r121/" rel="">الجدار الناري الأساسي للينكس UFW</a> (يسمى الجدار الناري غير المعقد)، ولذا يتحتم علينا فتح البوابة التي سنستخدمها في هذا الجدار (وهي البوابة 8000) حتى نتمكن من الاتصال مع خادم تطوير جانغو واختباره.
</p>

<p>
	يتم فتح البوابة بالأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_60" style="">
<span class="pln">sudo ufw allow </span><span class="lit">8000</span></pre>

<p>
	والآن يمكنك اختبار تطبيقك بتشغيل خادم التطوير عبر الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_62" style="">
<span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">manage</span><span class="pun">.</span><span class="pln">py runserver </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span></pre>

<p>
	استعرض التطبيق بكتابة عنوان IP أو اسم النطاق مع البوابة <code>8000:</code> ضمن متصفحك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_64" style="">
<span class="pln">http</span><span class="pun">://</span><span class="pln">server_domain_or_IP</span><span class="pun">:</span><span class="lit">8000</span></pre>

<p>
	وستحصل على صفحة الدليل index الافتراضية لجانغو:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="112372" href="https://academy.hsoub.com/uploads/monthly_2022_11/img-01-new_django.index.png.1e56fcb8413cef079d76989f8191264e.png" rel=""><img alt="صفحة الدليل index الافتراضية لجانغو" class="ipsImage ipsImage_thumbnailed" data-fileid="112372" data-unique="darw4rmvm" src="https://academy.hsoub.com/uploads/monthly_2022_11/img-01-new_django.index.thumb.png.09700440cbe741dea71d047fc3bc43a9.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	ولو أضفت <code>admin/</code> في نهاية الرابط، سترى شاشة تسجيل الدخول إلى لوحة التحكم:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="112373" href="https://academy.hsoub.com/uploads/monthly_2022_11/img-02-admin_login.png.619b483273cbd6acbdf260d239d2ddc0.png" rel=""><img alt="شاشة تسجيل الدخول إلى لوحة التحكم" class="ipsImage ipsImage_thumbnailed" data-fileid="112373" data-unique="g65jpc0yy" src="https://academy.hsoub.com/uploads/monthly_2022_11/img-02-admin_login.png.619b483273cbd6acbdf260d239d2ddc0.png" style="width: 409px; height: auto;"></a>
</p>

<p>
	أدخل اسم المستخدم وكلمة المرور الذي أنشأته عبر استخدام الأمر <code>createsuperuser</code> فتدخل إلى لوحة التحكم:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="112374" href="https://academy.hsoub.com/uploads/monthly_2022_11/img-03-admin_interface.png.a42f0ac27dbd924126b90b75e4c5ceee.png" rel=""><img alt="استخدام الأمر createsuperuser  للدخول إلى لوحة التحكم" class="ipsImage ipsImage_thumbnailed" data-fileid="112374" data-unique="xcmqaydsk" src="https://academy.hsoub.com/uploads/monthly_2022_11/img-03-admin_interface.thumb.png.2125b21458938ede7a996f7c48ee7bab.png" style="width: 700px; height: auto;"></a>
</p>

<p>
	أوقف خادم التطوير بالضغط على ctrl-c داخل شاشة الطرفية حالما تنتهي من اختباراتك.
</p>

<h3>
	التحقق من قدرة Gunicorn على تخديم المشروع
</h3>

<p>
	التحقق من قدرة Gunicorn على تخديم المشروع هو الأمر الأخير الذي سنقوم به ضمن البيئة الافتراضية، وذلك عبر الدخول إلى مجلد المشروع ومن ثم استخدام الأمر <code>gunicorn</code> لتشغيل وحدة WSGI الخاصة بتطبيقنا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_71" style="">
<span class="pln">cd </span><span class="pun">~/</span><span class="pln">myprojectdir
gunicorn </span><span class="pun">--</span><span class="pln">bind </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> myproject</span><span class="pun">.</span><span class="pln">wsgi</span></pre>

<p>
	سيشغل الأمر السابق Gunicorn على نفس الواجهة التي استخدمها خادم تطوير جانغو ويمكنك اختبار ذلك بإعادة طلب صفحات التطبيق عبر المتصفح.
</p>

<p>
	<strong>تنويه</strong>: ستلاحظ أن لوحة التحكم غير مُنسقة لأن Gunicorn لا يملك حتى الآن مسار صفحات CSS المسؤولة عن ذلك.
</p>

<p>
	لاحظ أننا استدعينا Gunicorn ومررنا له الوحدة التي تتضمن ملف wsgi.py -الذي يعدّ نقطة الدخول إلى تطبيقنا- مع تحديد موقع هذا الملف (حددناه عبر تنفيذ الأمر في مكان وجود الملف أي في مجلد المشروع)، ويحتوي الملف wsgi.py على دالة تسمى التطبيق application هي المسؤولة عن التواصل مع التطبيق. يمكنك معرفة المزيد حول <a href="https://peps.python.org/pep-0333/" rel="external nofollow">مواصفات WSGI</a>.
</p>

<p>
	اضغط على الاختصار ctrl-c داخل شاشة الطرفية عندما تنتهي من الاختبار لإيقاف خادم Gunicorn.
</p>

<p>
	يمكنك الآن إيقاف البيئة الافتراضية بالأمر التالي بعد أن انتهت إعدادات جانغو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_73" style="">
<span class="pln">deactivate</span></pre>

<p>
	وستلاحظ اختفاء محث البيئة الافتراضية من نافذة سطر الأوامر.
</p>

<h2>
	إنشاء ملفات تمهيد Gunicorn
</h2>

<p>
	اختبرنا في الفقرة السابقة تفاعل Gunicorn مع جانغو وتأكدنا من عمله، والآن سننشئ ملفات التمهيد systemd الخاصة به (المِقبس socket والخدمة)، لاستخدامها في إيقاف وتشغيل خادم التطبيق بطريقة أفضل.
</p>

<p>
	سيُنشَأ عند الإقلاع مِقبس Gunicorn ويبقى جاهزًا يستمع بانتظار الاتصالات، وعند ورود أي اتصال، فإن systemd سيشغل تلقائيًا عملية Gunicorn لتتعامل مع هذا الاتصال.
</p>

<p>
	لنبدأ بإنشاء ملف التمهيد لمِقبس Gunicorn بكتابة الأمر التالي مع امتيازات sudo:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_76" style="">
<span class="pln">sudo nano </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	بنية هذا الملف تتضمن ثلاثة مقاطع سنكتبها ضمنه، وهي: المقطع <code>[Unit]</code> مخصص لوصف المِقبس، والمقطع <code>[Socket]</code> يبين موقع المِقبس، أما المقطع <code>[Install]</code> فيحرص على إنشاء المقبس في الوقت الصحيح، وبذلك يصبح الملف بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_79" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">gunicorn socket

</span><span class="pun">[</span><span class="typ">Socket</span><span class="pun">]</span><span class="pln">
</span><span class="typ">ListenStream</span><span class="pun">=/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock

</span><span class="pun">[</span><span class="typ">Install</span><span class="pun">]</span><span class="pln">
</span><span class="typ">WantedBy</span><span class="pun">=</span><span class="pln">sockets</span><span class="pun">.</span><span class="pln">target</span></pre>

<p>
	يمكنك الآن إغلاق الملف بعد حفظ التغييرات.
</p>

<p>
	ومن ثم أنشئ ملف التمهيد لخدمة Gunicorn وفق الأمر التالي وأيضًا مع امتيازات sudo، لاحظ أن الاسم والمسار متطابقان بين ملف المقبس وملف الخدمة باستثناء اللاحقة فهي التي تحدد نوع الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_81" style="">
<span class="pln">sudo nano </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">service</span></pre>

<p>
	تتضمن بنية ملف الخدمة ثلاثة مقاطع أيضًا هي: <code>[Unit]</code> و <code>[Service]</code> و <code>[Install]</code>.
</p>

<p>
	يُخصص المقطع <code>[Unit]</code> لبيانات التعريف والاعتماديات، ونكتب ضمنه وصف الخدمة ونخبر نظام التمهيد أن تشغيل هذه الخدمة مرتبط بالوصول إلى هدف الشبكة الذي يعتمد على المقبس (عند ورود اتصال على المقبس كما ذكرنا سابقًا) وهذه العلاقة بين المقبس والخدمة سنذكرها ضمن المحدد <code>Requires</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_84" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">gunicorn daemon
</span><span class="typ">Requires</span><span class="pun">=</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target</span></pre>

<p>
	نحدد ضمن المقطع <code>[Service]</code> من يتمتع بصلاحية تشغيل الخدمة من مستخدمين ومجموعات عمل، وهم في حالتنا مستخدم التطبيق <code>sammy</code> وهو مالك كافة الملفات المرتبطة، ومجموعة العمل <code>www-data</code> التي ستتيح لخادم Nginx الاتصال بسهولة مع Gunicorn.
</p>

<p>
	ونوضح بعده مجلد العمل والأمر الذي سيُستخدم لتشغيل الخدمة: بكتابة المسار الكامل لملف Gunicorn التنفيذي (الذي تم تثبيته في بيئتنا الافتراضية)، وربط العملية بمقبس يونيكس الذي أنشأناه ضمن المسار المحدد في <code>run/</code> (انظر المقطع <code>[Socket]</code> في ملف المقبس) حتى تتمكن العملية من التواصل مع Nginx.
</p>

<p>
	وأخيرًا سنكتب ضمن المقطع التعليمات اللازمة لتُسجل كافة الأحداث في ملفات خرج قياسية حتى تتمكن <code>journald</code> من التقاط تسجيلات الأحداث الصادرة عن Gunicorn، وننوه إلى إمكانية ضبط تعديلات أخرى خاصة بـ Gunicorn هنا مثل تحديد عدد العمال <code>workers</code> بثلاثة كما في حالتنا.
</p>

<p>
	انظر الآن إلى ملف الخدمة بعد إضافة المقطع <code>[Service]</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_87" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">gunicorn daemon
</span><span class="typ">Requires</span><span class="pun">=</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target

</span><span class="pun">[</span><span class="typ">Service</span><span class="pun">]</span><span class="pln">
</span><span class="typ">User</span><span class="pun">=</span><span class="pln">sammy
</span><span class="typ">Group</span><span class="pun">=</span><span class="pln">www</span><span class="pun">-</span><span class="pln">data
</span><span class="typ">WorkingDirectory</span><span class="pun">=/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir
</span><span class="typ">ExecStart</span><span class="pun">=/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn \
          </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> \
          </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> \
          </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock \
          myproject</span><span class="pun">.</span><span class="pln">wsgi</span><span class="pun">:</span><span class="pln">application</span></pre>

<p>
	أما المقطع <code>[Install]</code> وهو المقطع الأخير، نستخدمه لإخبار نظام التمهيد بمرحلة بدء التشغيل التي سيرتبط بها تشغيل الخدمة في حال فعلّناها لتعمل تلقائيًا عند الإقلاع، وفي حالتنا ضبطنا الموضوع لتعمل الخدمة عندما يصل نظام التشغيل إلى مرحلة تعدد المستخدمين multi-user.
</p>

<p>
	انظر الآن الشكل النهائي لملف الخدمة، ولاتنسَ حفظ التغييرات عليه قبل الإغلاق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_89" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">gunicorn daemon
</span><span class="typ">Requires</span><span class="pun">=</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target

</span><span class="pun">[</span><span class="typ">Service</span><span class="pun">]</span><span class="pln">
</span><span class="typ">User</span><span class="pun">=</span><span class="pln">sammy
</span><span class="typ">Group</span><span class="pun">=</span><span class="pln">www</span><span class="pun">-</span><span class="pln">data
</span><span class="typ">WorkingDirectory</span><span class="pun">=/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir
</span><span class="typ">ExecStart</span><span class="pun">=/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn \
          </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> \
          </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> \
          </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock \
          myproject</span><span class="pun">.</span><span class="pln">wsgi</span><span class="pun">:</span><span class="pln">application

</span><span class="pun">[</span><span class="typ">Install</span><span class="pun">]</span><span class="pln">
</span><span class="typ">WantedBy</span><span class="pun">=</span><span class="pln">multi</span><span class="pun">-</span><span class="pln">user</span><span class="pun">.</span><span class="pln">target</span></pre>

<p>
	يمكننا الآن تشغيل وتفعيل مقبس Gunicorn وفق الأمرين أدناه، وسيؤدي هذا إلى إنشاء ملف المقبس في المسار <code>run/gunicorn.sock/</code> الآن عند تشغيلنا اليدوي للمقبس وتلقائيًا عند إقلاع نظام التشغيل، وفور ورود اتصال إلى المقبس سيشغل systemd تلقائيًا الخدمة <code>gunicorn.service</code> لتتعامل مع الاتصال:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_91" style="">
<span class="pln">sudo systemctl start gunicorn</span><span class="pun">.</span><span class="pln">socket
sudo systemctl enable gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	يمكنك التأكد من نجاح العملية عبر التحقق من وجود ملف المقبس.
</p>

<p>
	اطلع على معلومات إضافية عن systemd من خلال المقالين:
</p>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/linux/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-systemd-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%AE%D8%AF%D9%85%D8%A7%D8%AA%D8%8C-%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-units%D8%8C-%D9%88%D8%A7%D9%84%D9%8A%D9%88%D9%85%D9%8A%D8%A7%D8%AA-journal-r130/" rel="">أساسيات Systemd: العمل مع الخدمات، الوحدات Units، واليوميات Journal</a> <a href="https://academy.hsoub.com/devops/linux/%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-%D8%B9%D9%84%D9%89-%D9%84%D9%8A%D9%86%D9%83%D8%B3-%D8%A8%D8%A3%D8%AF%D9%88%D8%A7%D8%AA-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D9%85%D9%87%D9%8A%D8%AF-systemd-r284/" rel="">إدارة الخدمات على لينكس بأدوات نظام التمهيد systemd</a>
	</li>
</ul>
<h2>
	التحقق من ملف المقبس
</h2>

<p>
	تأكد من حالة العملية لترى إن كانت فعالة وقابلة للتشغيل عن طريق الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_93" style="">
<span class="pln">sudo systemctl status gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	وستحصل على ما يشبه الخرج التالي في حال كانت فعالة دون أخطاء:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_95" style="">
<span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">socket </span><span class="pun">-</span><span class="pln"> gunicorn socket
     </span><span class="typ">Loaded</span><span class="pun">:</span><span class="pln"> loaded </span><span class="pun">(/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket</span><span class="pun">;</span><span class="pln"> enabled</span><span class="pun">;</span><span class="pln"> vendor prese</span><span class="pun">&gt;</span><span class="pln">
     </span><span class="typ">Active</span><span class="pun">:</span><span class="pln"> active </span><span class="pun">(</span><span class="pln">listening</span><span class="pun">)</span><span class="pln"> since </span><span class="typ">Fri</span><span class="pln"> </span><span class="lit">2020</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">26</span><span class="pln"> </span><span class="lit">17</span><span class="pun">:</span><span class="lit">53</span><span class="pun">:</span><span class="lit">10</span><span class="pln"> UTC</span><span class="pun">;</span><span class="pln"> </span><span class="lit">14s</span><span class="pln"> ago
   </span><span class="typ">Triggers</span><span class="pun">:</span><span class="pln"> </span><span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">service
     </span><span class="typ">Listen</span><span class="pun">:</span><span class="pln"> </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock </span><span class="pun">(</span><span class="typ">Stream</span><span class="pun">)</span><span class="pln">
      </span><span class="typ">Tasks</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">(</span><span class="pln">limit</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1137</span><span class="pun">)</span><span class="pln">
     </span><span class="typ">Memory</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0B</span><span class="pln">
     </span><span class="typ">CGroup</span><span class="pun">:</span><span class="pln"> </span><span class="pun">/</span><span class="pln">system</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	تحقق الآن من وجود ملف المقبس <code>gunicorn.sock</code> ضمن المسار <code>run/</code> بكتابة الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_97" style="">
<span class="pln">file </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock</span></pre>

<p>
	ستحصل على الخرج التالي الذي يبين وجود الملف:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_100" style="">
<span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock</span><span class="pun">:</span><span class="pln"> socket</span></pre>

<p>
	أما في حال عدم وجود الملف gunicorn.sock، أو ظهور أي خطأ في خرج التعليمة <code>systemctl status</code> السابقة، فهذا يعني حدوث خطأ ما منع اكتمال العملية وإنشاء ملف المقبس، يمكنك تبيانه عبر استعراض سجلات الأحداث الخاصة بمقبس Gunicorn من خلال الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_102" style="">
<span class="pln">sudo journalctl </span><span class="pun">-</span><span class="pln">u gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	ومن ثم فتح الملف gunicorn.socket ضمن المسار etc/systemd/system/ ثانيةً لتصحيح واستدراك أي مشاكل موجودة بينتها السجلات قبل استكمال العمل.
</p>

<h2>
	التحقق من تفعيل المقبس
</h2>

<p>
	صحيح أننا شغلنا المقبس gunicorn.socket وتحققنا منه في الخطوات السابقة، ولكن الخدمة gunicorn.service لم تُفعل بعد وهذا طبيعي فهي مرتبطة بالاتصالات الواردة ولن تعمل قبل ورود أول اتصال. ويمكنك التحقق من حالتها بالأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_104" style="">
<span class="pln">sudo systemctl status gunicorn</span></pre>

<p>
	وستحصل على الخرج التالي الذي يبين أنها غير فعالة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_106" style="">
<span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">service </span><span class="pun">-</span><span class="pln"> gunicorn daemon
   </span><span class="typ">Loaded</span><span class="pun">:</span><span class="pln"> loaded </span><span class="pun">(/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">service</span><span class="pun">;</span><span class="pln"> disabled</span><span class="pun">;</span><span class="pln"> vendor preset</span><span class="pun">:</span><span class="pln"> enabled</span><span class="pun">)</span><span class="pln">
   </span><span class="typ">Active</span><span class="pun">:</span><span class="pln"> inactive </span><span class="pun">(</span><span class="pln">dead</span><span class="pun">)</span></pre>

<p>
	أرسل الآن اتصال تجريبي عبر <code>curl</code> لتختبر آلية العمل، وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_108" style="">
<span class="pln">curl </span><span class="pun">--</span><span class="pln">unix</span><span class="pun">-</span><span class="pln">socket </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock localhost</span></pre>

<p>
	وستحصل بموجبه على صفحة HTML من تطبيقك داخل شاشة الطرفية، ما يعني أن Gunicorn بدأ بالعمل وأنه قادرٌ على تخديم التطبيق، نفذ مجددًا أمر التحقق من حالة الخدمة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_110" style="">
<span class="pln">sudo systemctl status gunicorn</span></pre>

<p>
	ولاحظ اختلاف الخرج التنفيذ السابق قبل وجود اتصالات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_112" style="">
<span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">service </span><span class="pun">-</span><span class="pln"> gunicorn daemon
     </span><span class="typ">Loaded</span><span class="pun">:</span><span class="pln"> loaded </span><span class="pun">(/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">service</span><span class="pun">;</span><span class="pln"> disabled</span><span class="pun">;</span><span class="pln"> vendor preset</span><span class="pun">:</span><span class="pln"> enabled</span><span class="pun">)</span><span class="pln">
     </span><span class="typ">Active</span><span class="pun">:</span><span class="pln"> active </span><span class="pun">(</span><span class="pln">running</span><span class="pun">)</span><span class="pln"> since </span><span class="typ">Fri</span><span class="pln"> </span><span class="lit">2020</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> UTC</span><span class="pun">;</span><span class="pln"> </span><span class="lit">2s</span><span class="pln"> ago
</span><span class="typ">TriggeredBy</span><span class="pun">:</span><span class="pln"> </span><span class="pun">●</span><span class="pln"> gunicorn</span><span class="pun">.</span><span class="pln">socket
   </span><span class="typ">Main</span><span class="pln"> <abbr title="Process IDentifier | معرّف العملية أو البرنامج">PID</abbr></span><span class="pun">:</span><span class="pln"> </span><span class="lit">22914</span><span class="pln"> </span><span class="pun">(</span><span class="pln">gunicorn</span><span class="pun">)</span><span class="pln">
      </span><span class="typ">Tasks</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">(</span><span class="pln">limit</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1137</span><span class="pun">)</span><span class="pln">
     </span><span class="typ">Memory</span><span class="pun">:</span><span class="pln"> </span><span class="lit">89.1M</span><span class="pln">
     </span><span class="typ">CGroup</span><span class="pun">:</span><span class="pln"> </span><span class="pun">/</span><span class="pln">system</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">service
             </span><span class="pun">├─</span><span class="lit">22914</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">python </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunico</span><span class="pun">&gt;</span><span class="pln">
             </span><span class="pun">├─</span><span class="lit">22927</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">python </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunico</span><span class="pun">&gt;</span><span class="pln">
             </span><span class="pun">├─</span><span class="lit">22928</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">python </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunico</span><span class="pun">&gt;</span><span class="pln">
             </span><span class="pun">└─</span><span class="lit">22929</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">python </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">access</span><span class="pun">-</span><span class="pln">logfile </span><span class="pun">-</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> </span><span class="pun">--</span><span class="pln">bind unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunico</span><span class="pun">&gt;</span><span class="pln">

</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> django</span><span class="pun">-</span><span class="pln">tutorial systemd</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]:</span><span class="pln"> </span><span class="typ">Started</span><span class="pln"> gunicorn daemon</span><span class="pun">.</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> django</span><span class="pun">-</span><span class="pln">tutorial gunicorn</span><span class="pun">[</span><span class="lit">22914</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2020</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">22914</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Starting</span><span class="pln"> gunicorn </span><span class="lit">20.0</span><span class="pun">.</span><span class="lit">4</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> django</span><span class="pun">-</span><span class="pln">tutorial gunicorn</span><span class="pun">[</span><span class="lit">22914</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2020</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">22914</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Listening</span><span class="pln"> at</span><span class="pun">:</span><span class="pln"> unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock </span><span class="pun">(</span><span class="lit">22914</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> django</span><span class="pun">-</span><span class="pln">tutorial gunicorn</span><span class="pun">[</span><span class="lit">22914</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2020</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">22914</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Using</span><span class="pln"> worker</span><span class="pun">:</span><span class="pln"> sync
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> django</span><span class="pun">-</span><span class="pln">tutorial gunicorn</span><span class="pun">[</span><span class="lit">22927</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2020</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">22927</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">22927</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> django</span><span class="pun">-</span><span class="pln">tutorial gunicorn</span><span class="pun">[</span><span class="lit">22928</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2020</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">22928</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">22928</span><span class="pln">
</span><span class="typ">Jun</span><span class="pln"> </span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> django</span><span class="pun">-</span><span class="pln">tutorial gunicorn</span><span class="pun">[</span><span class="lit">22929</span><span class="pun">]:</span><span class="pln"> </span><span class="pun">[</span><span class="lit">2020</span><span class="pun">-</span><span class="lit">06</span><span class="pun">-</span><span class="lit">26</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">52</span><span class="pun">:</span><span class="lit">21</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">22929</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">22929</span></pre>

<p>
	وفي حال حصلت على أي أخطاء في خرج <code>systemctl status</code> أو خرج <code>curl</code>، تفقد عندها سجلات الأحداث عبر الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_114" style="">
<span class="pln">sudo journalctl </span><span class="pun">-</span><span class="pln">u gunicorn</span></pre>

<p>
	وافتح الملف gunicorn.service ضمن المسار etc/systemd/system/ وصحح المشاكل التي بينتها السجلات قبل إكمال العمل، وانتبه إلى أن أي تعديلات تطبقها عليه لن تصبح سارية المفعول إلّا بعد إعادة تحميل الخدمة ومن ثم إعادة تشغيل Gunicorn وفق الأوامر التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_116" style="">
<span class="pln">sudo systemctl daemon</span><span class="pun">-</span><span class="pln">reload
sudo systemctl restart gunicorn</span></pre>

<h2>
	إعداد Nginx ليؤدي دور وكيل Gunicorn
</h2>

<p>
	لنبدأ بإنشاء كتلة خادم جديدة لخادم Nginx ضمن المجلد sites-available.
</p>

<p>
	اكتب أولًا الأمر التالي الذي سيفتح ملفًا نصيًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_118" style="">
<span class="pln">sudo nano </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">available</span><span class="pun">/</span><span class="pln">myproject</span></pre>

<p>
	أنشئ فيه كتلة خادم جديدة وحدد مواصفاتها بحيث تتلقى الطلبات الطبيعية التي ترد إلى البوابة 80 وتطلب اسم نطاقك أو عنوان IP الخادم، وذلك بكتابة التعليمات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_120" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name server_domain_or_IP</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وبعدها أضف ضمن الكتلة نفسها التعليمات الخاصة بـ location المبينة أدناه، ووظيفتها جعل Nginx يتجاهل الأخطاء التي تنجم عن عدم العثور على رمز الموقع favicon، وإرشاده إلى موقع ملفات التطبيق الساكنة أي المسار<code>myprojectdir/static/~</code> الذي اختصرناه إلى "static/"، إذًا أصبحت كتلة الخادم على الشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_123" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name server_domain_or_IP</span><span class="pun">;</span><span class="pln">

    location </span><span class="pun">=</span><span class="pln"> </span><span class="pun">/</span><span class="pln">favicon</span><span class="pun">.</span><span class="pln">ico </span><span class="pun">{</span><span class="pln"> access_log off</span><span class="pun">;</span><span class="pln"> log_not_found off</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
    location </span><span class="pun">/</span><span class="pln">static</span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        root </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	والخطوة الأخيرة ستكون إنشاء كتلة <code>{} / location</code> التي ستتعامل مع بقية الطلبات، والإشارة ضمنها للملف proxy_params القياسي الذي أنُشئ أثناء تثبيت Nginx، ومن ثم تمرير كامل حركة البيانات مباشرةً إلى مقبس Gunicorn:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_125" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name server_domain_or_IP</span><span class="pun">;</span><span class="pln">

    location </span><span class="pun">=</span><span class="pln"> </span><span class="pun">/</span><span class="pln">favicon</span><span class="pun">.</span><span class="pln">ico </span><span class="pun">{</span><span class="pln"> access_log off</span><span class="pun">;</span><span class="pln"> log_not_found off</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
    location </span><span class="pun">/</span><span class="pln">static</span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        root </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myprojectdir</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        include proxy_params</span><span class="pun">;</span><span class="pln">
        proxy_pass http</span><span class="pun">://</span><span class="pln">unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	احفظ التغييرات على الملف وأغلقه.
</p>

<p>
	ومن ثم فعل الملف عبر ربطه بالمجلد <code>sites-enabled</code> باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_127" style="">
<span class="pln">sudo ln </span><span class="pun">-</span><span class="pln">s </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">available</span><span class="pun">/</span><span class="pln">myproject </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">enabled</span></pre>

<p>
	اختبر صحة تعليمات Nginx من ناحية قواعد الكتابة من خلال الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_129" style="">
<span class="pln">sudo nginx </span><span class="pun">-</span><span class="pln">t</span></pre>

<p>
	أعد تشغيل Nginx إن لم تظهر لك أي أخطاء:
</p>

<pre class="ipsCode">
sudo systemctl restart nginx
</pre>

<p>
	لنضبط الآن إعدادات الجدار الناري UFW عبر السماح بحركة مرور البيانات عبر البوابة 80، وإغلاق البوابة 8000 التي سمحنا بها سابقًا للتخاطب مع خادم التطوير، فهي لم تعد لازمة.
</p>

<p>
	اكتب الأوامر التالية لضبط الجدار الناري:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_131" style="">
<span class="pln">sudo ufw delete allow </span><span class="lit">8000</span><span class="pln">
sudo ufw allow </span><span class="str">'Nginx Full'</span></pre>

<p>
	يمكنك الآن الوصول لتطبيقك باستخدام اسم النطاق أو عنوان IP.
</p>

<p>
	<strong>تنويه</strong>: الخطوة الأهم بعد إعداد الخادم الوكيل هي تأمين شهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> لتشفير حركة البيانات المتبادلة مع التطبيق وحماية البيانات الحساسة مثل كلمات المرور، لذا احرص على تنفيذ هذه الخطوة، ويمكنك الحصول على شهادات مجانية من خدمة Let’s Encrypt في كان لديك اسم نطاق محجوز، واتبع الخطوات الواردة في مقال <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81-%D8%AA%D8%A4%D9%85%D9%91%D9%86-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-1604-r365/" rel="">كيف تؤمّن خادم ويب NGINX على أوبنتو 16.04</a> لتثبيت الشهادة فالطريقة نفسها.
</p>

<h2>
	استكشاف الأخطاء وإصلاحها في هذه البيئة
</h2>

<p>
	إذا لم تُظهر هذه الخطوة الأخيرة تطبيقك، فستحتاج إلى استكشاف أخطاء التثبيت وإصلاحها.
</p>

<h3>
	يظهر Nginx الصفحة الافتراضية بدلاً من تطبيق جانغو
</h3>

<p>
	في حال أظهر Nginx صفحته الافتراضية بدلًا من تطبيقك، فهذا يعني عادةً حاجتك لضبط <code>server_name</code> في الملف التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_133" style="">
<span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">available</span><span class="pun">/</span><span class="pln">myproject</span></pre>

<p>
	ليشير إلى اسم نطاقك أو عنوان IP للخادم، إذ يستخدم Nginx القيمة الموجودة في <code>server_name</code> ليُحدد أي كتلة خادم ستلبي الطلبات الواردة إليه، وعندما يرجع لك صفحته الافتراضية فهذا يعني أنه لم يتمكن من مطابقة طلبك بدقة مع أي من الكتل الموجودة ضمن ملفاته فيُرجع تلقائيًا الصفحة الموجودة في كتلته الافتراضية في المسار التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_135" style="">
<span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">available</span><span class="pun">/</span><span class="pln">default</span></pre>

<p>
	لذا احرص على تحديد <code>server_name</code> في الكتلة الخاصة بمشروعك بدقة وعلى نطاق أضيق من المذكور في الكتلة الافتراضية بحيث لا تسمح لخادم Nginx بتجاهلها والعودة إلى كتلته الافتراضية.
</p>

<h3>
	ظهور Nginx الخطأ 502 بدلا من تطبيق جانغو
</h3>

<p>
	يوجد مجموعة واسعة من الاحتمالات التي قد تسبب الخطأ 502 Bad Gateway Error، لذا فهو يحتاج إلى معلومات وتقصي أكثر لبيان سببه الفعلي.
</p>

<p>
	سجلات أحداث Nginx هي أهم مصدر معلومات عن الأحداث والأخطاء التي تحصل خلال عمل الوكيل أو الوسيط، ويمكنك تتبعها باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_137" style="">
<span class="pln">sudo tail </span><span class="pun">-</span><span class="pln">F </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">error</span><span class="pun">.</span><span class="pln">log</span></pre>

<p>
	والآن حدث الصفحة في متصفح الإنترنت لتولد خطأ جديدًا في السجل واقرأه بدقة من شأنه تضييق نطاق البحث عن سبب الخطأ، وسنعطيك بعض الاحتمالات عن ما يمكن أن يظهر لك من رسائل خطأ.
</p>

<p>
	احتمال الخطأ الأول:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_139" style="">
<span class="pln">connect</span><span class="pun">()</span><span class="pln"> to unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock failed </span><span class="pun">(</span><span class="lit">2</span><span class="pun">:</span><span class="pln"> </span><span class="typ">No</span><span class="pln"> such file </span><span class="kwd">or</span><span class="pln"> directory</span><span class="pun">)</span></pre>

<p>
	وهو يعني أن Nginx لم يتمكن من العثور على ملف المقبس <code>gunicorn.sock</code> في الموقع المحدد، وعندها عليك مقارنة موقع <code>proxy_pass</code> المذكور في الملف التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_141" style="">
<span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">available</span><span class="pun">/</span><span class="pln">myproject</span></pre>

<p>
	مع الموقع الفعلي المذكور في ملف المقبس <code>gunicorn.sock</code> المنشئ بواسطة وحدة تمهيد النظام <code>gunicorn.socket</code>.
</p>

<p>
	أما في حال لم تجد ملف المقبس <code>gunicorn.sock</code> في المسار <code>run/</code> فارجع إلى فقرة التحقق من ملف المقبس واتبع إرشاداتها لمعالجة هذه الحالة.
</p>

<p>
	احتمال الخطأ الثاني:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_143" style="">
<span class="pln">connect</span><span class="pun">()</span><span class="pln"> to unix</span><span class="pun">:/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock failed </span><span class="pun">(</span><span class="lit">13</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Permission</span><span class="pln"> denied</span><span class="pun">)</span></pre>

<p>
	يدل هذا الخطأ على مشاكل في صلاحيات وصول Nginx إلى مقبس Gunicorn، وغالبًا ما ينجم هذا الخطأ عن تنفيذ إجرائية العمل بمستخدم الجذر بدلًا من مستخدم يملك امتيازات <code>sudo</code> وعندها فإن systemd سيُنشئ مقبس Gunicorn ولكنّ Nginx لن يتمكن من الوصول إليه.
</p>

<p>
	ومن الممكن أن تظهر نفس المشكلة في حال وجود أي تقييد للصلاحيات على طول المسار الواصل إلى ملف المقبس بدءًا من الجذر <code>/</code> وصولًا إلى الملف <code>gunicorn.sock</code>، ويمكنك استعراض صلاحيات الوصول واسم المالك على كافة المجلدات الموجودة على مسار ملف المقبس باستخدام التعليمة <code>namei</code> وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_145" style="">
<span class="pln">namei </span><span class="pun">-</span><span class="pln">l </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock</span></pre>

<p>
	وستحصل على المعلومات التفصيلية عن صلاحيات كافة عناصر المسار ضمن الخرج بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_147" style="">
<span class="pln">f</span><span class="pun">:</span><span class="pln"> </span><span class="pun">/</span><span class="pln">run</span><span class="pun">/</span><span class="pln">gunicorn</span><span class="pun">.</span><span class="pln">sock
drwxr</span><span class="pun">-</span><span class="pln">xr</span><span class="pun">-</span><span class="pln">x root root </span><span class="pun">/</span><span class="pln">
drwxr</span><span class="pun">-</span><span class="pln">xr</span><span class="pun">-</span><span class="pln">x root root run
srw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln">rw</span><span class="pun">-</span><span class="pln"> root root gunicorn</span><span class="pun">.</span><span class="pln">sock</span></pre>

<p>
	يبين لك العمود الأول الصلاحيات الممنوحة على العنصر، والعمود الثاني يشير إلى اسم المستخدم المالك، بينما العمود الثالث فهو خاص باسم مجموعة العمل التي ينتمي إليها المالك، وبذلك يمكنك معرفة نوع الوصول المسموح إلى ملف المقبس.
</p>

<p>
	فمثلًا في الخرج السابق، كافة عناصر المسار تحمل صلاحية القراءة والتنفيذ فهي تنتهي بالرمز <code>r-x</code> بدلًا من <code>---</code> وهذا يعني إمكانية الوصول إليها من قبل Nginx فلو كان أي منها لا يحمل هذه الصلاحيات لحصلت مشكلة في الوصول إلى المقبس، وتُحل المشكلة آنذاك بإعطاء صلاحية العمل الناقصة أو بضم مستخدم Nginx إلى مجموعة العمل التي تتضمن مالك العنصر.
</p>

<h3>
	ظهور جانغو (لا يمكن الاتصال بالخادم: تم رفض الاتصال)
</h3>

<p>
	هي إحدى الرسائل التي قد تتلقاها من جانغو عند محاولتك استعراض بعض أجزاء التطبيق في متصفح الإنترنت:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_149" style="">
<span class="typ">OperationalError</span><span class="pln"> at </span><span class="pun">/</span><span class="pln">admin</span><span class="pun">/</span><span class="pln">login</span><span class="pun">/</span><span class="pln">
could </span><span class="kwd">not</span><span class="pln"> connect to server</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Connection</span><span class="pln"> refused
    </span><span class="typ">Is</span><span class="pln"> the server running on host </span><span class="str">"localhost"</span><span class="pln"> </span><span class="pun">(</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> accepting
    TCP</span><span class="pun">/</span><span class="pln">IP connections on port </span><span class="lit">5432</span><span class="pun">?</span></pre>

<p>
	تعني هذه الرسالة أن جانغو لم يتمكن من الوصول إلى قاعدة البيانات، لذا فالخطوة الأولى هي التأكد من إقلاع نسخة قاعدة البيانات Postgres instance باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_151" style="">
<span class="pln">sudo systemctl status postgresql</span></pre>

<p>
	وفي حال تبين لك أنها لا تعمل فيمكنك تشغيلها ومن ثم تفعيلها لتعمل تلقائيًا عند الإقلاع (ما لم تكن مفعلة للتشغيل التلقائي) وفق الأمرين:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_154" style="">
<span class="pln">sudo systemctl start postgresql
sudo systemctl enable postgresql</span></pre>

<p>
	وإن استمرت المشكلة تأكد من إعدادات قاعدة البيانات الموجودة في الملف التالي وصححها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_156" style="">
<span class="pun">~/</span><span class="pln">myprojectdir</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">settings</span><span class="pun">.</span><span class="pln">py</span></pre>

<h3>
	المزيد من استكشاف الأخطاء وإصلاحها
</h3>

<p>
	استعن دومًا بسجلات الأحداث فهي مصدر مهم للمعلومات وحدد الأخطاء بدقة وابحث عنها، إليك أهم السجلات.
</p>

<ul>
<li>
		للتحقق من سجلات عملية Nginx اكتب:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_159" style="">
<span class="pln">sudo journalctl </span><span class="pun">-</span><span class="pln">u nginx</span></pre>

<ul>
<li>
		للتحقق من سجلات وصول Nginx اكتب:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_161" style="">
<span class="pln">sudo less </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">access</span><span class="pun">.</span><span class="pln">log</span></pre>

<ul>
<li>
		للتحقق من سجلات خطأ Nginx اكتب:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_163" style="">
<span class="pln">sudo less </span><span class="pun">/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">log</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">error</span><span class="pun">.</span><span class="pln">log</span></pre>

<ul>
<li>
		للتحقق من سجلات أحداث Gunicorn اكتب:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_165" style="">
<span class="pln">sudo journalctl </span><span class="pun">-</span><span class="pln">u gunicorn</span></pre>

<ul>
<li>
		للتحقق من سجلات أحداث مقبس Gunicorn اكتب:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_169" style="">
<span class="pln">sudo journalctl </span><span class="pun">-</span><span class="pln">u gunicorn</span><span class="pun">.</span><span class="pln">socket</span></pre>

<p>
	عند تعديل أو تحديث الإعدادات أو التطبيق يجب إعادة تشغيل العمليات لتصبح التعديلات سارية المفعول.
</p>

<p>
	ففي حال عدلت تطبيق جانغو يمكنك إعادة تشغيل عملية Gunicorn عبر الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_171" style="">
<span class="pln">sudo systemctl restart gunicorn</span></pre>

<p>
	وإن عدلت ملفات مقبس أو خدمة Gunicorn فعليك إعادة تحميل العفريت وإعادة تشغيل الخدمة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_173" style="">
<span class="pln">sudo systemctl daemon</span><span class="pun">-</span><span class="pln">reload
sudo systemctl restart gunicorn</span><span class="pun">.</span><span class="pln">socket gunicorn</span><span class="pun">.</span><span class="pln">service</span></pre>

<p>
	أما لو عدلت إعدادات كتلة الخادم لـ Nginx فعليك التحقق من صحة القواعد أولًا ومن ثم إعادة تشغيل Nginx وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_4398_175" style="">
<span class="pln">sudo nginx </span><span class="pun">-</span><span class="pln">t </span><span class="pun">&amp;&amp;</span><span class="pln"> sudo systemctl restart nginx</span></pre>

<p>
	ستفيدك هذه الأوامر في عكس تعديلاتك على عمل البيئة بصورة سليمة.
</p>

<h2>
	خاتمة
</h2>

<p>
	أعددنا في هذا المقال مشروع جانغو ضمن بيئة افتراضية معزولة ومجهز بخادم Gunicorn يوصل طلبات العملاء إلى جانغو حتى يعالجها، وبخادم Nginx الذي يلعب دور الوكيل العكسي لتحسين أداء التطبيق وأمانه، وقدمنا مجموعة من الأدوات المساعدة التي من شأنها خدمة تطبيقاتك العاملة على خادم واحد.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-20-04" rel="external nofollow">How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 20.04</a> لصاحبه Erin Glass.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/servers/databases/postgresql/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-postgresql-r481/" rel="">المرجع الشامل إلى تعلم PostgreSQL</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-r1625/" rel="">البدء مع إطار العمل جانغو لإنشاء تطبيق ويب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81-%D8%AA%D9%8F%D8%B1%D9%82%D9%91%D9%90%D9%8A-%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%85%D9%88%D8%AC%D9%88%D8%AF-%D8%A8%D8%AF%D9%88%D9%86-%D9%82%D8%B7%D8%B9-%D8%A7%D8%AA%D8%B5%D8%A7%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-r445/" rel="">كيف تُرقِّي خادم Nginx موجود بدون قطع اتصالات العميل</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">663</guid><pubDate>Wed, 16 Nov 2022 08:11:03 +0000</pubDate></item><item><title>&#x646;&#x634;&#x631; &#x62A;&#x637;&#x628;&#x64A;&#x642; &#x62C;&#x627;&#x646;&#x63A;&#x648; &#x622;&#x645;&#x646; &#x648;&#x642;&#x627;&#x628;&#x644; &#x644;&#x644;&#x62A;&#x648;&#x633;&#x64A;&#x639; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x643;&#x648;&#x628;&#x64A;&#x631;&#x646;&#x62A;&#x633; Kubernetes</title><link>https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A2%D9%85%D9%86-%D9%88%D9%82%D8%A7%D8%A8%D9%84-%D9%84%D9%84%D8%AA%D9%88%D8%B3%D9%8A%D8%B9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D8%AA%D8%B3-kubernetes-r662/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_11/6374844f62594_-------.jpg.cda43fb6efddc8ed2ac7985f1cc4bd3b.jpg" /></p>

<p>
	هذا المقال هو الثالث من سلسلة تعليمية تتضمن ثلاث مقالات عن الانتقال من الحاويات إلى كوبيرنتس Kubernetes باستخدام جانغو Django إطار العمل الخاص بتسريع تطوير تطبيقات الويب المبنية <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r211/" rel="">بلغة بايثون</a>، في <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A8%D8%AE%D8%A7%D8%AF%D9%85-gunicorn-%D9%88%D9%88%D8%B6%D8%B9%D9%87-%D8%B6%D9%85%D9%86-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-r658/" rel="">المقال الأول</a> تعلمنا تعديل عينة تجريبية من تطبيق جانغو (تسمى التطبيق Polls) وفق منهجية <a href="https://12factor.net/" rel="external nofollow">Twelve-Factor</a> الخاصة بتطوير تطبيقات ويب سحابية قابلة للتوسع والعمل ضمن الحاويات، وفي <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D9%88%D8%B3%D9%8A%D8%B9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%AA%D8%A3%D9%85%D9%8A%D9%86%D9%87-%D8%B9%D8%A8%D8%B1-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%88%D8%AE%D8%AF%D9%85%D8%A9-lets-encrypt-r659/" rel="">المقال الثاني</a> تعرفنا على طرق توسيع هذا التطبيق (المعدّل المغلف في حاوية دوكر) توسيعًا أفقيًا وتأمينه بتشغيل وكيل عكسي أمامه هو خادم Nginx مع استخدام شهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> مصدقة من Let's Encrypt، أما اليوم نشارككم كيفية نشر التطبيق المعدّل نفسه باستعمال عنقود كوبيرنتس Kubernetes Cluster.
</p>

<p>
	<a href="https://academy.hsoub.com/devops/cloud-computing/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-kubernetes-r467/" rel="">كوبيرنتس</a> kubernetes هو حل <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D8%A7%D9%84%D9%85%D9%82%D8%B5%D9%88%D8%AF-%D8%A8%D9%85%D8%B5%D8%B7%D9%84%D8%AD-%D9%85%D9%81%D8%AA%D9%88%D8%AD-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-open-source%D8%9F-r885/" rel="">مفتوح المصدر</a> لإدارة الحاويات، ميزته الأساسية أتمتة عمليات نشر وتوسيع وإدارة تطبيقات الويب المغلفة ضمن حاويات، وذلك من خلال مجموعة من الكائنات الخاصة مثل خرائط الإعدادات ConfigMaps والأسرار Secrets التي تسمح بتهيئة مركزية للبيئة من خارج الحاويات، ووحدات التحكم مثل النشر Deployments المسؤولة عن إعادة تشغيل الحاويات تلقائيًا في حال حدوث أي خلل وعن التوسعة التلقائية بتشغيل حاويات إضافية مماثلة لحاوية التطبيق عند ازدياد الطلب عليه، وأيضًا كائن الإدخال ingress أو المُدخل (تترجم أحيانًا إلى كائن الولوج واعتمدنا هنا كائن الإدخال والمُدخل حسب سياق الجملة) المسؤول عن توجيه حركة البيانات HTTP أو HTTPS المشفرة بشهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> من المصادر الخارجية إلى خدمات كوبيرنتس الداخلية عبر قواعد وصول محددة مع وحدة التحكم مفتوحة المصدر <a href="https://github.com/kubernetes/ingress-nginx" rel="external nofollow">ingress-nginx</a>، أما التجديد الدوري للشهادات وتصديقها من Let's Encrypt تنجزه الوظيفة الإضافية مدير الشهادات <a href="https://github.com/cert-manager/cert-manager" rel="external nofollow">cert-manager</a>.
</p>

<p>
	هذا المقال جزء من سلسلة قصيرة حول احتواء تطبيقات جانغو بحاويات وإدارتها وإليك فهرس السلسلة:
</p>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D8%A8%D8%AE%D8%A7%D8%AF%D9%85-gunicorn-%D9%88%D9%88%D8%B6%D8%B9%D9%87-%D8%B6%D9%85%D9%86-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-r658/" rel="">بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D8%AA%D9%88%D8%B3%D9%8A%D8%B9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%88%D8%AA%D8%A3%D9%85%D9%8A%D9%86%D9%87-%D8%B9%D8%A8%D8%B1-%D8%AD%D8%A7%D9%88%D9%8A%D8%A9-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%88%D8%AE%D8%AF%D9%85%D8%A9-lets-encrypt-r659/" rel="">توسيع تطبيق جانغو وتأمينه عبر حاوية دوكر وخادم Nginx وخدمة Let's Encrypt</a>
	</li>
	<li>
		نشر تطبيق جانغو آمن وقابل للتوسيع باستخدام كوبيرنتس
	</li>
</ul>
<h2>
	متطلبات بيئة العمل
</h2>

<p>
	ستحتاج المتطلبات التالية لتتمكن من التطبيق العملي لهذا المقال:
</p>

<ol>
<li>
		عنقود <a href="https://academy.hsoub.com/devops/cloud-computing/%D9%86%D8%B8%D8%A7%D9%85-%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D8%AA%D8%B3-kubernetes-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%B9%D9%85%D9%84%D9%87-r598/" rel="">كوبيرنتس</a> بإصدار 1.15 أو أعلى، مع تفعيل خاصية التحكم بالوصول المستند على الدور <a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/" rel="external nofollow">Role-based access control (RBAC)</a>، استخدمنا في المقال <a href="https://www.digitalocean.com/products/kubernetes" rel="external nofollow">عنقود خدمة DigitalOcean</a>، لكنك لست ملزمًا بذلك يمكنك <a href="https://www.digitalocean.com/community/tutorials/how-to-create-a-kubernetes-cluster-using-kubeadm-on-ubuntu-18-04" rel="external nofollow">إعداده بنفسك بالطريقة التي تناسبك</a>.
	</li>
	<li>
		تثبيت أداة سطر الأوامر kubectl على جهازك المحلي وإعدادها للاتصال بالعنقود، يمكنك معرفة المزيد عن تثبيت kubectl بالاطلاع على <a href="https://kubernetes.io/docs/tasks/tools/" rel="external nofollow">توثيقات كوبيرنتس الرسمية</a>. أما إن اعتمدت DigitalOcean فاستعن بالمقال <a href="https://docs.digitalocean.com/products/kubernetes/how-to/connect-to-cluster/" rel="external nofollow">إعداد الاتصال مع عنقود كوبيرنتس في DigitalOcean</a>.
	</li>
	<li>
		حجز اسم نطاق لتطبيقك، سنعتمد في مقالنا على الاسم your_domain.com، مع العلم بإمكانية حصولك على اسم نطاق مجاني من Freenom.
	</li>
	<li>
		تثبيت وحدة التحكم ingress-nginx ومدير الشهادات cert-manager وضبط الإعدادات اللازمة للتحقق من الشهادات، يمكنك الاطلاع على <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-nginx-ingress-with-cert-manager-on-digitalocean-kubernetes" rel="external nofollow">كيفية إعداد Nginx Ingress ومدير الشهادات على كوبيرنتس</a>.
	</li>
	<li>
		ربط اسم النطاق بعنوان IP العام لمُدخل موازن الحمل عبر إضافة سجل DNS من النوع A، يمكنك الحصول على تفاصيل إضافية حول DNS بالاطلاع على <a href="https://academy.hsoub.com/devops/servers/dns/" rel="">القسم الخاص بخدمة اسم النطاق DNS</a> على أكاديمية حسوب.
	</li>
	<li>
		خدمة تخزين كائني متوافقة مع S3 ومع إضافة <a href="https://django-storages.readthedocs.io/en/latest/" rel="external nofollow">django-storages</a> مثل DigitalOcean Space وظيفتها تأمين المساحة التخزينية اللازمة لملفات تطبيق جانغو الساكنة، مع مجموعة مفاتيح الوصول الخاصة بإدارة هذه المساحة، استرشد <a href="https://docs.digitalocean.com/products/spaces/how-to/create/" rel="external nofollow">بكيفية إعداد مساحة على DigitalOcean Space</a> وعدّل بما يلائم خدمة التخزين التي اخترتها.
	</li>
	<li>
		نظام إدارة قواعد بيانات <a href="https://docs.djangoproject.com/en/2.2/ref/databases/" rel="external nofollow">متوافق مع جانغو</a> مع إنشاء قاعدة بيانات ومستخدم خاص بالتطبيق، نحن اخترنا خادم PostgreSQL، اهتم بالتفاصيل التالية أثناء إنجازك لهذه النقطة لتتمكن من متابعة خطوات المقال:
	</li>
</ol>
<ul style="margin-right: 40px;">
<li>
		استخدم الاسم polls لقاعدة البيانات والاسم sammy لمستخدم قاعدة البيانات (هذه الأسماء ليست ملزمة ولكننا استخدمناها في المقال واستخدامك لها سيسهل عليك المتابعة)، لمزيد من التفاصيل حول الإنشاء استرشد بالخطوة /1/ من مقالنا السابق [بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر]()، مع العلم أن هذه المهمة يمكن إنجازها من أي خادم من الخوادم الثلاثة لبيئة العمل.
	</li>
	<li>
		اعتمدنا في المقال على نظام إدارة قواعد البيانات من نوع <a href="https://www.digitalocean.com/products/managed-databases/" rel="external nofollow">Managed PostgreSQL cluster</a> الذي توفره DigitalOcean.
	</li>
	<li>
		لكن يمكنك تثبيت PostgreSQL وتشغيله وإعداده بنفسك مع الاستعانة بالفيديو التعليمي <a href="https://academy.hsoub.com/devops/servers/databases/postgresql/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-postgresql-r400/" rel="">تثبيت وإعداد قاعدة بيانات PostgreSQL</a>.
	</li>
</ul>
<ol start="8">
<li>
		حساب على <a href="https://hub.docker.com/" rel="external nofollow">Docker Hub</a>، استرشد بتوثيقات <a href="https://docs.docker.com/docker-hub/repos/" rel="external nofollow">دوكر الخاصة بالمستودعات</a> لإنشائه.
	</li>
	<li>
		تثبيت دوكر على جهازك المحلي، استعن لذلك بمقال <a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%AF%D9%88%D9%83%D8%B1-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87-%D8%B9%D9%84%D9%89-%D8%AF%D8%A8%D9%8A%D8%A7%D9%86-r465/" rel="">كيفية تثبيت دوكر واستخدامه على دبيان</a>.
	</li>
</ol>
<h2>
	الخطوة 1: استنساخ التطبيق وضبط إعداداته
</h2>

<p>
	تتلخص هذه الخطوة باستنساخ كود التطبيق من مستودع GitHub بالإضافة إلى ضبط محددات الاتصال مع خادم قاعدة البيانات والمفاتيح الخاصة بخدمة التخزين الكائني المستخدمة.
</p>

<p>
	سنحصل على كود التطبيق والملف Dockerfile من القسم polls-docker من <a href="https://github.com/do-community/django-polls/tree/polls-docker" rel="external nofollow">مستودع GitHub</a> الذي يتضمن <a href="https://docs.djangoproject.com/en/3.0/intro/" rel="external nofollow">نسخًا تجريبية من التطبيق المسمى Polls</a> وهذه النسخ تتناسب مع أغراض جانغو التعليمية، بالإضافة لكونها مغلفة ومعدّلة بكل ما يلزم لتعمل بفعالية داخل الحاوية وتتكيف مع متغيرات بيئة التشغيل وفق ما تعلمنا في المقال الأول من هذه السلسلة.
</p>

<p>
	اكتب الأمر التالي للاستنساخ:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_16" style="">
<span class="pln">git clone </span><span class="pun">--</span><span class="pln">single</span><span class="pun">-</span><span class="pln">branch </span><span class="pun">--</span><span class="pln">branch polls</span><span class="pun">-</span><span class="pln">docker https</span><span class="pun">://</span><span class="pln">github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="kwd">do</span><span class="pun">-</span><span class="pln">community</span><span class="pun">/</span><span class="pln">django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">.</span><span class="pln">git</span></pre>

<p>
	توجه للمجلد django-polls عبر الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_14" style="">
<span class="pln">cd django</span><span class="pun">-</span><span class="pln">polls</span></pre>

<p>
	يتضمن هذا المجلد كود التطبيق والملف Dockerfile الذي سيستخدمه دوكر لبناء صورة الحاوية، بالإضافة إلى الملف env الذي يتضمن متغيرات البيئة التي ستمرر للحاوية من بيئة التشغيل المحيطة.
</p>

<p>
	استعرض الملف Dockerfile عبر تعليمة لينكس <code>cat</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_19" style="">
<span class="pln">cat </span><span class="typ">Dockerfile</span></pre>

<p>
	وستحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_21" style="">
<span class="pln">FROM python</span><span class="pun">:</span><span class="lit">3.7</span><span class="pun">.</span><span class="lit">4</span><span class="pun">-</span><span class="pln">alpine3</span><span class="pun">.</span><span class="lit">10</span><span class="pln">

ADD django</span><span class="pun">-</span><span class="pln">polls</span><span class="pun">/</span><span class="pln">requirements</span><span class="pun">.</span><span class="pln">txt </span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">requirements</span><span class="pun">.</span><span class="pln">txt

RUN set </span><span class="pun">-</span><span class="pln">ex \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> apk add </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">cache </span><span class="pun">--</span><span class="pln">virtual </span><span class="pun">.</span><span class="pln">build</span><span class="pun">-</span><span class="pln">deps postgresql</span><span class="pun">-</span><span class="pln">dev build</span><span class="pun">-</span><span class="pln">base \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> python </span><span class="pun">-</span><span class="pln">m venv </span><span class="pun">/</span><span class="pln">env \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">/</span><span class="pln">env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">pip install </span><span class="pun">--</span><span class="pln">upgrade pip \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> </span><span class="pun">/</span><span class="pln">env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">pip install </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">cache</span><span class="pun">-</span><span class="pln">dir </span><span class="pun">-</span><span class="pln">r </span><span class="pun">/</span><span class="pln">app</span><span class="pun">/</span><span class="pln">requirements</span><span class="pun">.</span><span class="pln">txt \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> runDeps</span><span class="pun">=</span><span class="str">"$(scanelf --needed --nobanner --recursive /env \
        | awk '{ gsub(/,/, "</span><span class="pln">\nso</span><span class="pun">:</span><span class="str">", $2); print "</span><span class="pln">so</span><span class="pun">:</span><span class="str">" $2 }' \
        | sort -u \
        | xargs -r apk info --installed \
        | sort -u)"</span><span class="pln"> \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> apk add </span><span class="pun">--</span><span class="pln">virtual rundeps $runDeps \
    </span><span class="pun">&amp;&amp;</span><span class="pln"> apk </span><span class="kwd">del</span><span class="pln"> </span><span class="pun">.</span><span class="pln">build</span><span class="pun">-</span><span class="pln">deps

ADD django</span><span class="pun">-</span><span class="pln">polls </span><span class="pun">/</span><span class="pln">app
WORKDIR </span><span class="pun">/</span><span class="pln">app

ENV VIRTUAL_ENV </span><span class="pun">/</span><span class="pln">env
ENV PATH </span><span class="pun">/</span><span class="pln">env</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">:</span><span class="pln">$PATH

EXPOSE </span><span class="lit">8000</span><span class="pln">

CMD </span><span class="pun">[</span><span class="str">"gunicorn"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--bind"</span><span class="pun">,</span><span class="pln"> </span><span class="str">":8000"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"--workers"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"3"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"mysite.wsgi"</span><span class="pun">]</span></pre>

<p>
	وفقًا للخرج السابق، فإن Dockerfile سيستخدم <a href="https://hub.docker.com/_/python" rel="external nofollow">صورة دوكر</a> بإصدار بايثون 3.7.4، ويثبت اعتماديات وحزم بايثون اللازمة لكل من جانغو وخادم gunicorn وفق ملف متطلبات التطبيق django-polls/requirements.txt ومن ثم يحذف الملفات غير اللازمة بعد انتهاء التثبيت، وينسخ بعد ذلك كود التطبيق إلى الصورة ويسند قيمة المتغير <code>PATH</code>، وأخيرًا سيحدد البوابة أو المنفذ 8000 لاستقبال حركة البيانات الواردة إلى الحاوية، ويشغل <code>gunicorn</code> مع عمال <code>workers</code> عدد /3/ عبر المنفذ 8000.
</p>

<p>
	لتعرف المزيد عن كل مرحلة ضمن Dockerfile راجع مقالنا الأول السابق من السلسلة.
</p>

<p>
	لنبني صورة الحاوية باستخدام <code>docker build</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_23" style="">
<span class="pln">docker build </span><span class="pun">-</span><span class="pln">t polls </span><span class="pun">.</span></pre>

<p>
	سميّنا الصورة باسم polls باستخدام الراية <code>t-</code> ومررنا المسار الحالي <code>.</code> كمسار context للتعليمة build فهو يتضمن كافة الملفات اللازمة لعملية البناء.
</p>

<p>
	بعد انتهاء عملية البناء استخدم الأمر <code>docker images</code> لاستعراض الصور المتاحة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_25" style="">
<span class="pln">docker images</span></pre>

<p>
	وستحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_27" style="">
<span class="pln">REPOSITORY          TAG                    IMAGE ID             CREATED              SIZE
polls                           latest                  </span><span class="lit">80ec4f33aae1</span><span class="pln">         </span><span class="lit">2</span><span class="pln"> weeks ago          </span><span class="lit">197MB</span><span class="pln">
python                   </span><span class="lit">3.7</span><span class="pun">.</span><span class="lit">4</span><span class="pun">-</span><span class="pln">alpine3</span><span class="pun">.</span><span class="lit">10</span><span class="pln">     f309434dea3a       </span><span class="lit">8</span><span class="pln"> months ago        </span><span class="lit">98.7MB</span></pre>

<p>
	لنضبط متغيرات البيئة تمهيدًا لعملية التشغيل <code>docker run</code>، وذلك بتعديل الملف env باستعمال محرر النصوص:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_29" style="">
<span class="pln">nano env</span></pre>

<p>
	سيظهر أمامك الملف env بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_31" style="">
<span class="pln">DJANGO_SECRET_KEY</span><span class="pun">=</span><span class="pln">
DEBUG</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
DJANGO_ALLOWED_HOSTS</span><span class="pun">=</span><span class="pln">
DATABASE_ENGINE</span><span class="pun">=</span><span class="pln">postgresql_psycopg2
DATABASE_NAME</span><span class="pun">=</span><span class="pln">polls
DATABASE_USERNAME</span><span class="pun">=</span><span class="pln">
DATABASE_PASSWORD</span><span class="pun">=</span><span class="pln">
DATABASE_HOST</span><span class="pun">=</span><span class="pln">
DATABASE_PORT</span><span class="pun">=</span><span class="pln">
STATIC_ACCESS_KEY_ID</span><span class="pun">=</span><span class="pln">
STATIC_SECRET_KEY</span><span class="pun">=</span><span class="pln">
STATIC_BUCKET_NAME</span><span class="pun">=</span><span class="pln">
STATIC_ENDPOINT_URL</span><span class="pun">=</span><span class="pln">
DJANGO_LOGLEVEL</span><span class="pun">=</span><span class="pln">info</span></pre>

<p>
	كما ترى فبعض محددات الملف قيمها موجودة والبعض الآخر يحتاج إلى ضبط بما يناسب بيئتك، سنذكر المحددات التي تحتاج إلى ضبط بالترتيب:
</p>

<ol>
<li>
		المفتاح السري <code>DJANGO_SECRET_KEY</code> اختر له قيمة فريدة صعبة التخمين كما توصي <a href="https://docs.djangoproject.com/en/2.2/ref/settings/#secret-key" rel="external nofollow">توثيقات Docker</a> نقدم لك في الخطوة /5/ من مقال <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces#step-5-adjusting-the-app-settings" rel="external nofollow">كيفية إعداد تطبيق جانغو عبر DigitalOcean Managed Databases and Spaces</a> إحدى طرق جانغو لتوليد المفاتيح.
	</li>
	<li>
		أما <code>DJANGO_ALLOWED_HOSTS</code> فأسند له اسم النطاق الخاص بك <code>your_domain.com</code>، أو يمكنك وضع الرمز <code>*</code> لأغراض الاختبار فقط وليس ضمن بيئة العمل الفعلية فهذا الرمز يسمح لأي عنوان IP بالوصول، يحميك هذا المحدد من الهجمات المعتمدة على حقن ترويسة HTTP وفي حال أردت المزيد من المعلومات اطلع على توثيقات جانغو الخاصة بهذا المحدد ضمن <a href="https://docs.djangoproject.com/en/3.0/ref/settings/#allowed-hosts" rel="external nofollow">Core Settings</a>
	</li>
	<li>
		<code>DATABASE_USERNAME</code> ضع اسم مستخدم قاعدة البيانات PostgreSQL الذي أنشأته أثناء إعدادك لمتطلبات العمل.
	</li>
	<li>
		<code>DATABASE_NAME</code> ضع القيمة <code>polls</code> أو الاسم الذي اخترته لقاعدة البيانات أثناء إعدادك لمتطلبات العمل.
	</li>
	<li>
		كذلك الأمر لكلمة المرور <code>DATABASE_PASSWORD</code>.
	</li>
	<li>
		ضع اسم مضيف قاعدة البيانات <code>DATABASE_HOST</code> ورقم بوابة الاتصال معها <code>DATABASE_PORT</code>.
	</li>
	<li>
		أما المحددات <code>STATIC_ACCESS_KEY_ID</code> و <code>STATIC_SECRET_KEY</code> و <code>STATIC_BUCKET_NAME</code> و <code>STATIC_ENDPOINT_URL</code> فهي تتعلق بخدمة التخزين الكائني الخارجية، اضبطها بما يتناسب مع الخدمة التي تستخدمها.
	</li>
</ol>
<p>
	احفظ التغيرات على الملف env بعد الانتهاء وأغلقه، ولننتقل للخطوة التالية.
</p>

<h2>
	الخطوة 2: إنشاء مخطط قاعدة البيانات ورفع ملفات التطبيق إلى وحدة التخزين الكائني
</h2>

<p>
	شغل صورة الحاوية <code>polls:latest</code> عبر الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_33" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env polls sh </span><span class="pun">-</span><span class="pln">c </span><span class="str">"python manage.py makemigrations &amp;&amp; python manage.py migrate"</span></pre>

<p>
	وبموجبه الأمر سيجري تجاوز الأمر الافتراضي عند بدء التشغيل المحدد بتعليمة <code>CMD</code> ضمن الملف Dockerfile وتنشئ أثناء التشغيل مخطط قاعدة بيانات التطبيق وهو الأمر الفرعي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_35" style="">
<span class="pln">sh </span><span class="pun">-</span><span class="pln">c </span><span class="str">"python manage.py makemigrations &amp;&amp; python manage.py migrate"</span></pre>

<p>
	إن كنت تنفذ الأمر <code>docker run</code> للمرة الأولى فستحصل على الخرج التالي الذي يبين نجاح إنشاء مخطط قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_37" style="">
<span class="typ">No</span><span class="pln"> changes detected
</span><span class="typ">Operations</span><span class="pln"> to perform</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Apply</span><span class="pln"> all migrations</span><span class="pun">:</span><span class="pln"> admin</span><span class="pun">,</span><span class="pln"> auth</span><span class="pun">,</span><span class="pln"> contenttypes</span><span class="pun">,</span><span class="pln"> polls</span><span class="pun">,</span><span class="pln"> sessions
</span><span class="typ">Running</span><span class="pln"> migrations</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Applying</span><span class="pln"> contenttypes</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> admin</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> admin</span><span class="pun">.</span><span class="lit">0002</span><span class="pln">_logentry_remove_auto_add</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> admin</span><span class="pun">.</span><span class="lit">0003</span><span class="pln">_logentry_add_action_flag_choices</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> contenttypes</span><span class="pun">.</span><span class="lit">0002</span><span class="pln">_remove_content_type_name</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0002</span><span class="pln">_alter_permission_name_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0003</span><span class="pln">_alter_user_email_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0004</span><span class="pln">_alter_user_username_opts</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0005</span><span class="pln">_alter_user_last_login_null</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0006</span><span class="pln">_require_contenttypes_0002</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0007</span><span class="pln">_alter_validators_add_error_messages</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0008</span><span class="pln">_alter_user_username_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0009</span><span class="pln">_alter_user_last_name_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0010</span><span class="pln">_alter_group_name_max_length</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> auth</span><span class="pun">.</span><span class="lit">0011</span><span class="pln">_update_proxy_permissions</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> polls</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK
  </span><span class="typ">Applying</span><span class="pln"> sessions</span><span class="pun">.</span><span class="lit">0001</span><span class="pln">_initial</span><span class="pun">...</span><span class="pln"> OK</span></pre>

<p>
	مع العلم أن جانغو لن يجري أي عملية عندما تنفذ <code>migrate</code> في المرات القادمة طالما لم يطرأ أي تغيير على مخطط قاعدة بيانات التطبيق.
</p>

<p>
	نفذ بعدها أمر تشغيل جديد للحاوية مع استدعاء سطر الأوامر الخاص بالصدفة Shell كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_39" style="">
<span class="pln">docker run </span><span class="pun">-</span><span class="pln">i </span><span class="pun">-</span><span class="pln">t </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env polls sh</span></pre>

<p>
	تظهر بنتيجة هذا الأمر نافذة الصدفة shell ضمن الحاوية اكتب فيها تعليمة إنشاء المستخدم الخاص بتطبيق جانغو:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_41" style="">
<span class="pln">python manage</span><span class="pun">.</span><span class="pln">py createsuperuser</span></pre>

<p>
	استكمل بياناته وهي اسم المستخدم وكلمة المرور والبريد الإلكتروني، واضغط بعدها على الاختصار Ctrl+D للخروج من الحاوية وإنهاء عملها.
</p>

<p>
	أما العملية الأخيرة فهي جمع الملفات الساكنة الخاصة بالتطبيق <code>collectstatic</code> ورفعها على خدمة التخزين الكائني وتتم وفق الأمر التالي علمًا أنها قد تستغرق بعض الوقت:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_43" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env polls sh </span><span class="pun">-</span><span class="pln">c </span><span class="str">"python manage.py collectstatic --noinput"</span></pre>

<p>
	يظهر بعدها الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_45" style="">
<span class="lit">121</span><span class="pln"> static files copied</span><span class="pun">.</span></pre>

<p>
	يمكنك الآن تشغيل التطبيق:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_47" style="">
<span class="pln">docker run </span><span class="pun">--</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file env </span><span class="pun">-</span><span class="pln">p </span><span class="lit">80</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> polls</span></pre>

<p>
	ستحصل على الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_49" style="">
<span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Starting</span><span class="pln"> gunicorn </span><span class="lit">19.9</span><span class="pun">.</span><span class="lit">0</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Listening</span><span class="pln"> at</span><span class="pun">:</span><span class="pln"> http</span><span class="pun">://</span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Using</span><span class="pln"> worker</span><span class="pun">:</span><span class="pln"> sync
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">7</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">7</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">8</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8</span><span class="pln">
</span><span class="pun">[</span><span class="lit">2019</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="lit">17</span><span class="pln"> </span><span class="lit">21</span><span class="pun">:</span><span class="lit">23</span><span class="pun">:</span><span class="lit">36</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0000</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="lit">9</span><span class="pun">]</span><span class="pln"> </span><span class="pun">[</span><span class="pln">INFO</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Booting</span><span class="pln"> worker </span><span class="kwd">with</span><span class="pln"> pid</span><span class="pun">:</span><span class="pln"> </span><span class="lit">9</span></pre>

<p>
	مع العلم أن التشغيل هذه المرة وفق الإعدادات الافتراضية المنصوص عليها في ملف Dockerfile وهي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_51" style="">
<span class="pln">gunicorn </span><span class="pun">--</span><span class="pln">bind </span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> </span><span class="pun">--</span><span class="pln">workers </span><span class="lit">3</span><span class="pln"> mysite</span><span class="pun">.</span><span class="pln">wsgi</span><span class="pun">:</span><span class="pln">application</span></pre>

<p>
	وأن البوابة 8000 للحاوية فُتحت لتستقبل حركة البيانات القادمة من البوابة 80 للخادم أوبونتو.
</p>

<p>
	يمكنك الآن كتابة <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> الخاص بالتطبيق polls بمتصفح الإنترنت واستعراضه، لاحظ أنك ستحصل على الخطأ 404 (لم يتم العثور على الصفحة) في حال اكتفيت بالعنوان:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_53" style="">
<span class="pln">http</span><span class="pun">://</span><span class="pln">APP_SERVER_1_IP</span><span class="pun">/</span></pre>

<p>
	بسبب عدم وجود بيانات للتطبيق تحت الجذر <code>/</code> لذا اكتب العنوان التالي لتحصل على واجهة polls:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_58" style="">
<span class="pln">http</span><span class="pun">://</span><span class="pln">APP_SERVER_1_IP</span><span class="pun">/</span><span class="pln">polls</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="112368" href="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.07e9c7a9dfda4299a9aae779e7b1f569.png" rel=""><img alt="الحصول على واجهة polls" class="ipsImage ipsImage_thumbnailed" data-fileid="112368" data-unique="5yco8bqlq" src="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.07e9c7a9dfda4299a9aae779e7b1f569.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	اكتب الرابط التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_62" style="">
<span class="pln"> http</span><span class="pun">://</span><span class="pln">APP_SERVER_1_ip</span><span class="pun">/</span><span class="pln">admin</span></pre>

<p>
	وستتمكن من الوصول إلى واجهة الدخول للوحة التحكم الخاصة بالتطبيق:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="112369" href="https://academy.hsoub.com/uploads/monthly_2022_11/02-img-polls_admin.png.309e094f8457d5c2dc3a1b4e71a4c0d9.png" rel=""><img alt="واجهة الدخول للوحة التحكم الخاصة بالتطبيق" class="ipsImage ipsImage_thumbnailed" data-fileid="112369" data-unique="22932to88" src="https://academy.hsoub.com/uploads/monthly_2022_11/02-img-polls_admin.png.309e094f8457d5c2dc3a1b4e71a4c0d9.png" style="width: 420px; height: auto;"></a>
</p>

<p>
	أدخل بيانات المستخدم مدير التطبيق المنشأ سابقًا باستخدام الأمر <code>createsuperuser</code> وستظهر أمامك واجهة الإدارة والتحكم الخاصة بالتطبيق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="112370" href="https://academy.hsoub.com/uploads/monthly_2022_11/03-img-polls_admin_main.png.f152d15fdda6f53fe53dc18c8314df7a.png" rel=""><img alt="واجهة الإدارة والتحكم الخاصة بالتطبيق" class="ipsImage ipsImage_thumbnailed" data-fileid="112370" data-unique="1tf195c38" src="https://academy.hsoub.com/uploads/monthly_2022_11/03-img-polls_admin_main.thumb.png.56c15e71c4aeb4cb9e819fef058070f1.png" style="width: 700px; height: auto;"></a>
</p>

<p>
	تذكر أن تسليم المكونات الساكنة للتطبيق admin و polls يتم من خدمة التخزين الكائني ويمكنك اتباع هذه الخطوات لاختبار جودته وأيضًا <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-scalable-django-app-with-digitalocean-managed-databases-and-spaces#testing-spaces-static-file-delivery" rel="external nofollow">التأكد من صحة إحضار الملفات</a>.
</p>

<p>
	بعد أن تنتهي من اختبار عمل التطبيق اضغط على الاختصار Ctrl+c في نافذة كتابة الأوامر السطرية التي تشغل حاوية دوكر لتنهي عمل الحاوية.
</p>

<p>
	والآن بعد إتمام بناء صورة دوكر للتطبيق، وبناء مخطط قاعدة البيانات وتحميل مكونات التطبيق الساكنة إلى وحدة التخزين الكائني، وأخيرًا اختبار عمل الحاوية والتطبيق، يمكننا رفع الصورة إلى سجل صور مثل Docker Hub وهو موضوع خطوتنا التالية.
</p>

<h2>
	الخطوة 3: رفع صورة جانغو إلى Docker Hub
</h2>

<p>
	لتستخدم تطبيقك ضمن عنقود كوبيرنتس، ستحتاج أولًا رفع صورة التطبيق إلى سجل صور عام أو خاص مثل Docker Hub حتى تتمكن خدمة كوبيرنتس من سحب الصورة ونشر التطبيق.
</p>

<p>
	علمًا أن لسجلات الصور نوعان عام وخاص، السجل العام مجاني إلاّ أنه يجعل صورك في متناول جميع المستخدمين ويمكّنهم من سحبها، أما الخاص فيعطي خصوصية أكبر إذا يسمح لك بمشاركة الصور مع مجموعة محددة من المستخدمين مثل زملاء عملك، وتقدم بعض الشركات -مثل سجل حاويات DigitalOcean على سبيل المثال لا الحصر- عروضًا مجانية لإنشاء سجلات خاصة محدودة، أما Docker Hub فيدعم النوعين العام والخاص.
</p>

<p>
	سنقدم في مثالنا طريقة سحب الصور من سجل Docker Hub عام ويمكنك الاطلاع على <a href="https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/" rel="external nofollow">توثيقات كوبيرنتس</a> لمعرفة طريقة العمل مع السجلات الخاصة.
</p>

<p>
	لنبدأ بتسجيل الدخول إلى Docker Hub وفق الحساب الذي ذكرناه في متطلبات العمل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_69" style="">
<span class="pln">docker login</span></pre>

<p>
	سيُطلب منك إدخال اسم المستخدم وكلمة المرور لحسابك وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_71" style="">
<span class="typ">Login</span><span class="pln"> </span><span class="kwd">with</span><span class="pln"> your </span><span class="typ">Docker</span><span class="pln"> ID to push </span><span class="kwd">and</span><span class="pln"> pull images </span><span class="kwd">from</span><span class="pln"> </span><span class="typ">Docker</span><span class="pln"> </span><span class="typ">Hub</span><span class="pun">.</span><span class="pln"> </span><span class="typ">If</span><span class="pln"> you don</span><span class="str">'t have a Docker ID, head over to https://hub.docker.com to create one.
Username:</span></pre>

<p>
	تحمل صورة التطبيق في بيئتنا الوسم <code>polls:latest</code> أعطها وسمًا جديدًا يتضمن اسم حسابك على Docker Hub واسم مستودعك باستخدام <code>tag</code> وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_73" style="">
<span class="pln">docker tag polls</span><span class="pun">:</span><span class="pln">latest your_dockerhub_username</span><span class="pun">/</span><span class="pln">your_dockerhub_repo_name</span><span class="pun">:</span><span class="pln">latest</span></pre>

<p>
	استعملنا في مثالنا الاسم sammy للحساب و sammy-django للمستودع وبذلك يصبح الأمر على الشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_75" style="">
<span class="pln">docker push sammy</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">-</span><span class="pln">django</span><span class="pun">:</span><span class="pln">latest</span></pre>

<p>
	ستحصل على خرج يتضمن تفاصيل عملية الرفع، وبعد إتمام العملية ستصبح الصورة متاحة أمام كوبيرنتس على Docker Hub.
</p>

<h2>
	الخطوة 4: إعداد خريطة الإعدادات ConfigMap
</h2>

<p>
	خريطة الإعدادات ConfigMap هي كائن كوبيرنتس المسؤول عن تمرير متغيرات بيئة التشغيل العامة إلى العنقود مثل إعدادات التطبيق، ويشاركه المهمة كائن آخر يدعى السرّ Secret إلاّ أنه يُعنى بالمتغيرات الحساسة مثل البيانات الخاصة بواجهات التخاطب <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> أو بقاعدة البيانات ولذا فهو يخضع لقواعد خاصة لحمايته والتحكم بوصول المستخدمين إليه مثل تفعيل خاصية <a href="https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/" rel="external nofollow">encryption at rest</a>، وبناءً على هذا الاختلاف في طبيعة بيانات الكائنين فإن بيانات ConfigMap تخزن بشكل نصي صريح ومقروء للمستخدم، بينما تخزن بيانات Secret معماة بأسلوب base64.
</p>

<p>
	تتشابه هذه الآلية في الواقع مع تمرير متغيرات البيئة لحاوية دوكر بالملف <code>env</code> الذي يلقم هذه المعلومات لأمر تشغيل الحاوية <code>docker-run</code>.
</p>

<p>
	لنبدأ بإعداد ConfigMap، والخطوة الأولى هي إنشاء مجلد يدعى <code>yaml</code> سنخزن ضمنه وثائق كوبرنيتس الأساسية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_82" style="">
<span class="pln">mkdir yaml
cd</span></pre>

<p>
	ومن ثم أنشئ ضمن المجلد ملف نصي باسم polls-configmap.yaml يتضمن خريطة الإعدادات وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_79" style="">
<span class="pln">nano polls</span><span class="pun">-</span><span class="pln">configmap</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	وهذا يعني أننا بصدد إنشاء كائن كوبرنتس من نوع ConfigMap يحمل الاسم polls-config.
</p>

<p>
	الآن لننتقي من الملف env (الذي عملنا عليه في الخطوة /1/) البيانات العامة للتطبيق التي لا تتمتع بحساسية خاصة ونكتبها في الملف polls-configmap.yaml وذلك وفق الصيغة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_84" style="">
<span class="pln">apiVersion</span><span class="pun">:</span><span class="pln"> v1
kind</span><span class="pun">:</span><span class="pln"> </span><span class="typ">ConfigMap</span><span class="pln">
metadata</span><span class="pun">:</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> polls</span><span class="pun">-</span><span class="pln">config
data</span><span class="pun">:</span><span class="pln">
  DJANGO_ALLOWED_HOSTS</span><span class="pun">:</span><span class="pln"> </span><span class="str">"*"</span><span class="pln">
  STATIC_ENDPOINT_URL</span><span class="pun">:</span><span class="pln"> </span><span class="str">"https://your_space_name.space_region.digitaloceanspaces.com"</span><span class="pln">
  STATIC_BUCKET_NAME</span><span class="pun">:</span><span class="pln"> </span><span class="str">"your_space_name"</span><span class="pln">
  DJANGO_LOGLEVEL</span><span class="pun">:</span><span class="pln"> </span><span class="str">"info"</span><span class="pln">
  DEBUG</span><span class="pun">:</span><span class="pln"> </span><span class="str">"True"</span><span class="pln">
  DATABASE_ENGINE</span><span class="pun">:</span><span class="pln"> </span><span class="str">"postgresql_psycopg2"</span></pre>

<p>
	بالطبع مع وضع نفس القيم الموجودة في env وبخصوص المحدد <code>DJANGO_ALLOWED_HOSTS</code> نعطيه القيمة <code>*</code> كوننا نعمل في بيئة تجريبية.
</p>

<p>
	نحفظ بعدها التغييرات ونغلق الملف تمهيدًا لإنشاء الكائن ConfigMap بالأمر الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_86" style="">
<span class="pln">kubectl apply </span><span class="pun">-</span><span class="pln">f polls</span><span class="pun">-</span><span class="pln">configmap</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	وسنحصل على الخرج التالي الذي يبين نجاح العملية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_88" style="">
<span class="pln">configmap</span><span class="pun">/</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">config created</span></pre>

<h2>
	الخطوة 5: إعداد السر Secret
</h2>

<p>
	بيانات الكائن من نوع سرّ secret هي بيانات حساسة ومهمة كما ذكرنا سابقًا لذا فهي تُعمَّى <a href="https://ar.wikipedia.org/wiki/%D8%A7%D9%84%D8%A3%D8%B3%D8%A7%D8%B3_64" rel="external nofollow">بالأساس64</a>، ولتحقيق ذلك لديك طريقتين إما ترميز البيانات خارجيًا من ثم كتابتها معماة ضمن الملف الذي سنعدّه للكائن، أو استخدام الأمر <code>kubectl create</code> مع الراية <code>from-env-file--</code> وهي الطريقة التي استعملناها هنا.
</p>

<p>
	نعود للملف env استخدمنا متغيراته العامة في إنشاء الكائن Configmap والآن سنستخدم بقية المتغيرات التي تعد بيانات حساسة لإعداد الكائن <code>secret</code>.
</p>

<p>
	لننتقل للتطبيق العملي، انسخ الملف env إلى المجلد <code>yaml</code> وسمي النسخة <code>polls-secrets</code> وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_91" style="">
<span class="pln">cp </span><span class="pun">../</span><span class="pln">env </span><span class="pun">./</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">secrets</span></pre>

<p>
	افتح الملف <code>polls-secrets</code> باستعمال محرر النصوص:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_93" style="">
<span class="pln">nano polls</span><span class="pun">-</span><span class="pln">secrets</span></pre>

<p>
	ستحصل على بيانات الملف env المنسوخ كاملةً:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_95" style="">
<span class="pln">DJANGO_SECRET_KEY</span><span class="pun">=</span><span class="pln">
DEBUG</span><span class="pun">=</span><span class="kwd">True</span><span class="pln">
DJANGO_ALLOWED_HOSTS</span><span class="pun">=</span><span class="pln">
DATABASE_ENGINE</span><span class="pun">=</span><span class="pln">postgresql_psycopg2
DATABASE_NAME</span><span class="pun">=</span><span class="pln">polls
DATABASE_USERNAME</span><span class="pun">=</span><span class="pln">
DATABASE_PASSWORD</span><span class="pun">=</span><span class="pln">
DATABASE_HOST</span><span class="pun">=</span><span class="pln">
DATABASE_PORT</span><span class="pun">=</span><span class="pln">
STATIC_ACCESS_KEY_ID</span><span class="pun">=</span><span class="pln">
STATIC_SECRET_KEY</span><span class="pun">=</span><span class="pln">
STATIC_BUCKET_NAME</span><span class="pun">=</span><span class="pln">
STATIC_ENDPOINT_URL</span><span class="pun">=</span><span class="pln">
DJANGO_LOGLEVEL</span><span class="pun">=</span><span class="pln">info</span></pre>

<p>
	الآن احذف منه المتغيرات التي كتبناها ضمن ConfigMap في الخطوة السابقة، وستبقى المتغيرات التالية ضمنه:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_97" style="">
<span class="pln">DJANGO_SECRET_KEY</span><span class="pun">=</span><span class="pln">your_secret_key
DATABASE_NAME</span><span class="pun">=</span><span class="pln">polls
DATABASE_USERNAME</span><span class="pun">=</span><span class="pln">your_django_db_user
DATABASE_PASSWORD</span><span class="pun">=</span><span class="pln">your_django_db_user_password
DATABASE_HOST</span><span class="pun">=</span><span class="pln">your_db_host
DATABASE_PORT</span><span class="pun">=</span><span class="pln">your_db_port
STATIC_ACCESS_KEY_ID</span><span class="pun">=</span><span class="pln">your_space_access_key
STATIC_SECRET_KEY</span><span class="pun">=</span><span class="pln">your_space_access_key_secret</span></pre>

<p>
	ضع القيم الخاصة بتطبيقك نفسها التي ضبطناها في الخطوة /1/ من المقال (مثل محددات قاعدة البيانات والمفتاح الخاص وغيرها)، واحفظ التغييرات على الملف <code>polls-secrets</code> وأغلقه.
</p>

<p>
	أنشئ الآن الكائن من نوع secret والذي يحمل الاسم <code>polls-secret</code> من خلال الأمر التالي مع تمرير المتغيرات اللازمة للعنقود عبر الملف polls-secrets الذي أغلقته توًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_99" style="">
<span class="pln">kubectl create secret generic polls</span><span class="pun">-</span><span class="pln">secret </span><span class="pun">--</span><span class="kwd">from</span><span class="pun">-</span><span class="pln">env</span><span class="pun">-</span><span class="pln">file</span><span class="pun">=</span><span class="pln">poll</span><span class="pun">-</span><span class="pln">secrets</span></pre>

<p>
	وستحصل على الخرج الذي يبين نجاح الإنشاء:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_102" style="">
<span class="pln">secret</span><span class="pun">/</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">secret created</span></pre>

<p>
	استعرض بعدها وصف الكائن باستخدام <code>kubectl describe</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_104" style="">
<span class="pln">kubectl describe secret polls</span><span class="pun">-</span><span class="pln">secret</span></pre>

<p>
	لاحظ القيم المرمزة في الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_106" style="">
<span class="typ">Name</span><span class="pun">:</span><span class="pln">         polls</span><span class="pun">-</span><span class="pln">secret
</span><span class="typ">Namespace</span><span class="pun">:</span><span class="pln">    default
</span><span class="typ">Labels</span><span class="pun">:</span><span class="pln">       </span><span class="pun">&lt;</span><span class="pln">none</span><span class="pun">&gt;</span><span class="pln">
</span><span class="typ">Annotations</span><span class="pun">:</span><span class="pln">  </span><span class="pun">&lt;</span><span class="pln">none</span><span class="pun">&gt;</span><span class="pln">

</span><span class="typ">Type</span><span class="pun">:</span><span class="pln">  </span><span class="typ">Opaque</span><span class="pln">

</span><span class="typ">Data</span><span class="pln">
</span><span class="pun">====</span><span class="pln">
DATABASE_PASSWORD</span><span class="pun">:</span><span class="pln">     </span><span class="lit">8</span><span class="pln"> bytes
DATABASE_PORT</span><span class="pun">:</span><span class="pln">         </span><span class="lit">5</span><span class="pln"> bytes
DATABASE_USERNAME</span><span class="pun">:</span><span class="pln">     </span><span class="lit">5</span><span class="pln"> bytes
DJANGO_SECRET_KEY</span><span class="pun">:</span><span class="pln">     </span><span class="lit">14</span><span class="pln"> bytes
STATIC_ACCESS_KEY_ID</span><span class="pun">:</span><span class="pln">  </span><span class="lit">20</span><span class="pln"> bytes
STATIC_SECRET_KEY</span><span class="pun">:</span><span class="pln">     </span><span class="lit">43</span><span class="pln"> bytes
DATABASE_HOST</span><span class="pun">:</span><span class="pln">         </span><span class="lit">47</span><span class="pln"> bytes
DATABASE_NAME</span><span class="pun">:</span><span class="pln">         </span><span class="lit">5</span><span class="pln"> bytes</span></pre>

<p>
	مررنا بذلك إعدادات التطبيق جانغو إلى داخل عنقود كوبيرنتس وتم تخزينها باستخدام كائنات من نوع Secret و ConfigMap، وأصبحت بيئتنا جاهزة لنشر التطبيق ضمن العنقود.
</p>

<h2>
	الخطوة 6: نشر التطبيق باستخدام وحدة تحكم النشر
</h2>

<p>
	وحدة النشر Deployment هي إحدى وحدات تحكم عنقود كوبيرنتس، تتولى إدارة التطبيقات عديمة الحالة stateless applications التي تعمل ضمن العنقود. إذ أنها تدير أعباء العمل صعودًا ونزولًا فتُدخل العناصر بالخدمة أو تخرجها تبعًا لضغط العمل، بالإضافة إلى إعطائها أوامر إعادة التشغيل للحاويات التي تفشل أثناء الإقلاع.
</p>

<p>
	تتحكم وحدة النشر Deployment بواحد أو أكثر من كائنات Pod، أما Pod فهي أصغر عناصر عنقود كوبيرنتس وكل واحدة منها يمكن أن تتضمن حاوية أو أكثر، لمعرفة المزيد يمكنك الاطلاع على <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%AA%D8%B9%D9%84%D9%85-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-kubernetes-r468/" rel="">تعلم أساسيات Kubernetes</a> على أكاديمية حسوب.
</p>

<p>
	افتح ملفًا جديدًا يحمل الاسم polls-deployment.yaml عبر أي محرر تفضله وليكن عبر nano مثلًا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_108" style="">
<span class="pln">nano polls</span><span class="pun">-</span><span class="pln">deployment</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	والصق ضمنه التعليمات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_110" style="">
<span class="pln">apiVersion</span><span class="pun">:</span><span class="pln"> apps</span><span class="pun">/</span><span class="pln">v1
kind</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Deployment</span><span class="pln">
metadata</span><span class="pun">:</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> polls</span><span class="pun">-</span><span class="pln">app
  labels</span><span class="pun">:</span><span class="pln">
    app</span><span class="pun">:</span><span class="pln"> polls
spec</span><span class="pun">:</span><span class="pln">
    replicas</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
  selector</span><span class="pun">:</span><span class="pln">
    matchLabels</span><span class="pun">:</span><span class="pln">
      app</span><span class="pun">:</span><span class="pln"> polls
  template</span><span class="pun">:</span><span class="pln">
    metadata</span><span class="pun">:</span><span class="pln">
      labels</span><span class="pun">:</span><span class="pln">
        app</span><span class="pun">:</span><span class="pln"> polls
    spec</span><span class="pun">:</span><span class="pln">
      containers</span><span class="pun">:</span><span class="pln">
        </span><span class="pun">-</span><span class="pln"> image</span><span class="pun">:</span><span class="pln"> your_dockerhub_username</span><span class="pun">/</span><span class="pln">app_repo_name</span><span class="pun">:</span><span class="pln">latest
          name</span><span class="pun">:</span><span class="pln"> polls
          envFrom</span><span class="pun">:</span><span class="pln">
          </span><span class="pun">-</span><span class="pln"> secretRef</span><span class="pun">:</span><span class="pln">
              name</span><span class="pun">:</span><span class="pln"> polls</span><span class="pun">-</span><span class="pln">secret
          </span><span class="pun">-</span><span class="pln"> configMapRef</span><span class="pun">:</span><span class="pln">
              name</span><span class="pun">:</span><span class="pln"> polls</span><span class="pun">-</span><span class="pln">config
          ports</span><span class="pun">:</span><span class="pln">
            </span><span class="pun">-</span><span class="pln"> containerPort</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8000</span><span class="pln">
              name</span><span class="pun">:</span><span class="pln"> gunicorn</span></pre>

<p>
	أكتب اسم الصورة التي بنيناها في الخطوة /2/ ولا تنسَ الإشارة للاسم الذي رفعنا به الصورة على Docker Hub.
</p>

<p>
	لنشرح التعليمات السابقة بالترتيب.
</p>

<p>
	أطلقنا على وحدة التحكم الاسم polls-app وميّزناها بعنوان label ذا القيمة المزدوجة <code>app: polls</code>، وضبطنا عدد نسخ Pods المطلوب تشغيلها بنسختين وفق <code>replicas: 2</code> أما إعدادات هذه Pods فتُحددها المواصفات المكتوبة تحت <code>template</code>.
</p>

<p>
	أما <code>configMapRef</code> و <code>secretRef</code> المستخدمين بعد <code>envFrom</code> فيحددان على الترتيب قيم المتغيرات التي عرفناها من خلال كائن خريطة الإعدادات واسمه <code>polls-config</code> والكائن سرّ واسمه <code>polls-secret</code>، وفي نهاية الملف عرفنا بوابة الحاوية <code>containerPort</code> وهي تحمل الرقم 8000 والاسم <code>gunicorn</code>.
</p>

<p>
	يمكنك تعلم المزيد عن وحدة تحكم النشر من خلال <a href="https://kubernetes.io/docs/tasks/run-application/run-stateless-application-deployment/" rel="external nofollow">توثيقات كوبيرنتس</a>.
</p>

<p>
	أغلق الملف بعد حفظ التغييرات، لنباشر بإنشاء وحدة تحكم النشر عبر الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_112" style="">
<span class="pln">kubectl apply </span><span class="pun">-</span><span class="pln">f polls</span><span class="pun">-</span><span class="pln">deployment</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	تحصل على الخرج الذي يؤكد نجاح العملية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_114" style="">
<span class="pln">deployment</span><span class="pun">.</span><span class="pln">apps</span><span class="pun">/</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">app created</span></pre>

<p>
	يمكنك أيضًا التأكد من الإنشاء عبر الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_117" style="">
<span class="pln">kubectl get deploy polls</span><span class="pun">-</span><span class="pln">app</span></pre>

<p>
	الذي يعطي النتيجة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_119" style="">
<span class="pln">NAME        READY   UP</span><span class="pun">-</span><span class="pln">TO</span><span class="pun">-</span><span class="pln">DATE   AVAILABLE   AGE
polls</span><span class="pun">-</span><span class="pln">app   </span><span class="lit">2</span><span class="pun">/</span><span class="lit">2</span><span class="pln">     </span><span class="lit">2</span><span class="pln">            </span><span class="lit">2</span><span class="pln">           </span><span class="lit">6m38s</span></pre>

<p>
	في حال واجهت أي خطأ في عمل وحدة التحكم وأردت التأكد من حالتها استخدم الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_121" style="">
<span class="pln">kubectl describe deploy</span></pre>

<p>
	أما لفحص الـ Pods وحالتها فاستخدم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_123" style="">
<span class="pln">kubectl get pod</span></pre>

<p>
	وستحصل على الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_126" style="">
<span class="pln">NAME                         READY   STATUS    RESTARTS   AGE
polls</span><span class="pun">-</span><span class="pln">app</span><span class="pun">-</span><span class="lit">847f8ccbf4</span><span class="pun">-</span><span class="lit">2stf7</span><span class="pln">   </span><span class="lit">1</span><span class="pun">/</span><span class="lit">1</span><span class="pln">     </span><span class="typ">Running</span><span class="pln">   </span><span class="lit">0</span><span class="pln">          </span><span class="lit">6m42s</span><span class="pln">
polls</span><span class="pun">-</span><span class="pln">app</span><span class="pun">-</span><span class="lit">847f8ccbf4</span><span class="pun">-</span><span class="pln">tqpwm   </span><span class="lit">1</span><span class="pun">/</span><span class="lit">1</span><span class="pln">     </span><span class="typ">Running</span><span class="pln">   </span><span class="lit">0</span><span class="pln">          </span><span class="lit">6m57s</span></pre>

<h2>
	الخطوة 7: السماح للمصادر الخارجية بالوصول إلى الخدمة
</h2>

<p>
	سننشئ في هذه الخطوة خدمة كوبيرنتس خاصة بتطبيقنا، والخدمات في كوبيرنتس عبارة عن مفهوم مجرد يعرّف مجموعة من كائنات Pod لتظهر أمام المصادر الخارجية كخدمة شبكة واحدة أي كطرف واحد endpoint بغض النظر عن توقف أي Pod أو تشغيلها، والمصادر الخارجية في مثالنا هي حركة البيانات باتجاه التطبيق، وبالتالي من يطلب التطبيق جانغو سيطلب خدمة كوبيرنتس واحدة مستقرة ولن يتأثر بتوقف أو إضافة أي Pod داخلية.
</p>

<p>
	تتعدد أنواع خدمات كوبيرنتس، مثل خدمة ClusterIP التي تعتمد على عنوان IP الداخلي للعنقود، وخدمة NodePort التي تعرض الخدمة على بوابة محددة من كل عقدة، وخدمة LoadBalancer التي توفر موازنة حمل سحابية لحركة مرور البيانات القادمة من المصادر الخارجية إلى Pods داخل العنقود وذلك عبر خدمات NodePorts الخاصة بكل عقدة، والتي يتم إنشاؤها تلقائيًا في هذه الحالة.
</p>

<p>
	يمكنك تعلم المزيد عن هذه الخدمات من خلال <a href="https://kubernetes.io/docs/concepts/services-networking/service/" rel="external nofollow">توثيقات كوبيرنتس</a>.
</p>

<p>
	لنبدأ بإنشاء الخدمة الخاصة بتطبيق جانغو وسنعتمد النوع NodePort.
</p>

<p>
	أنشئ ملفًا باسم polls-svc.yaml باستعمال محرر النصوص الذي تفضله وليكن باستعمال نانو nano:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_128" style="">
<span class="pln">nano polls</span><span class="pun">-</span><span class="pln">svc</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	والصق ضمنه التعليمات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_133" style="">
<span class="pln">apiVersion</span><span class="pun">:</span><span class="pln"> v1
kind</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Service</span><span class="pln">
metadata</span><span class="pun">:</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> polls
  labels</span><span class="pun">:</span><span class="pln">
    app</span><span class="pun">:</span><span class="pln"> polls
spec</span><span class="pun">:</span><span class="pln">
  type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">NodePort</span><span class="pln">
  selector</span><span class="pun">:</span><span class="pln">
    app</span><span class="pun">:</span><span class="pln"> polls
  ports</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">-</span><span class="pln"> port</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8000</span><span class="pln">
      targetPort</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8000</span><span class="pln">
    </span><span class="pun">```</span></pre>

<p>
	لاحظ التعليمات السابقة، نوع الخدمة هو NodePort، اسمها <code>polls</code> وهي مميزة بالعنوان <code>app: polls</code>، وحددنا من خلال مُحدِّد selector الربط مع Pods الواجهات الخلفية بالعنوان <code>app: polls</code> و البوابة 8000.
</p>

<p>
	أغلق الملف بعد حفظ التغييرات، ومن ثم أنشئ الخدمة بالأمر <code>kubectl apply</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_135" style="">
<span class="pln">kubectl apply </span><span class="pun">-</span><span class="pln">f polls</span><span class="pun">-</span><span class="pln">svc</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	ونحصل على ما يؤكد الإنشاء وفق الخرج التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_137" style="">
<span class="pln">service</span><span class="pun">/</span><span class="pln">polls created</span></pre>

<p>
	استعرض الخدمة وتأكد من إنشائها عبر <code>kubectl get svc</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_139" style="">
<span class="pln">kubectl get svc polls</span></pre>

<p>
	وسيظهر لك الخرج التالي، الذي يتضمن عنوان IP الداخلي للعنقود وبوابة العقدة NodePort وهي في حالتنا <code>32654</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_141" style="">
<span class="pln">NAME    TYPE       CLUSTER</span><span class="pun">-</span><span class="pln">IP       EXTERNAL</span><span class="pun">-</span><span class="pln">IP     PORT</span><span class="pun">(</span><span class="pln">S</span><span class="pun">)</span><span class="pln">           AGE
polls   </span><span class="typ">NodePort</span><span class="pln">   </span><span class="lit">10.245</span><span class="pun">.</span><span class="lit">197.189</span><span class="pln">        </span><span class="pun">&lt;</span><span class="pln">none</span><span class="pun">&gt;</span><span class="pln">           </span><span class="lit">8000</span><span class="pun">:</span><span class="lit">32654</span><span class="pun">/</span><span class="pln">TCP   </span><span class="lit">59s</span></pre>

<p>
	حصلنا على رقم البوابة، ونحتاج إلى عنوان IP الخارجي للخدمة للاتصال مع التطبيق، وسنحصل عليه بالأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_143" style="">
<span class="pln">kubectl get node </span><span class="pun">-</span><span class="pln">o wide</span></pre>

<p>
	الذي يعطينا الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_145" style="">
<span class="pln">NAME                   STATUS   ROLES    AGE   VERSION   INTERNAL</span><span class="pun">-</span><span class="pln">IP   EXTERNAL</span><span class="pun">-</span><span class="pln">IP      OS</span><span class="pun">-</span><span class="pln">IMAGE                       KERNEL</span><span class="pun">-</span><span class="pln">VERSION          CONTAINER</span><span class="pun">-</span><span class="pln">RUNTIME
pool</span><span class="pun">-</span><span class="lit">7no0qd9e0</span><span class="pun">-</span><span class="lit">364fd</span><span class="pln">   </span><span class="typ">Ready</span><span class="pln">    </span><span class="pun">&lt;</span><span class="pln">none</span><span class="pun">&gt;</span><span class="pln">   </span><span class="lit">27h</span><span class="pln">   v1</span><span class="pun">.</span><span class="lit">18.8</span><span class="pln">   </span><span class="lit">10.118</span><span class="pun">.</span><span class="lit">0.5</span><span class="pln">    </span><span class="lit">203.0</span><span class="pun">.</span><span class="lit">113.1</span><span class="pln">   </span><span class="typ">Debian</span><span class="pln"> GNU</span><span class="pun">/</span><span class="typ">Linux</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">(</span><span class="pln">buster</span><span class="pun">)</span><span class="pln">   </span><span class="lit">4.19</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="pln">cloud</span><span class="pun">-</span><span class="pln">amd64   docker</span><span class="pun">://</span><span class="lit">18.9</span><span class="pun">.</span><span class="lit">9</span><span class="pln">
pool</span><span class="pun">-</span><span class="lit">7no0qd9e0</span><span class="pun">-</span><span class="lit">364fi</span><span class="pln">   </span><span class="typ">Ready</span><span class="pln">    </span><span class="pun">&lt;</span><span class="pln">none</span><span class="pun">&gt;</span><span class="pln">   </span><span class="lit">27h</span><span class="pln">   v1</span><span class="pun">.</span><span class="lit">18.8</span><span class="pln">   </span><span class="lit">10.118</span><span class="pun">.</span><span class="lit">0.4</span><span class="pln">    </span><span class="lit">203.0</span><span class="pun">.</span><span class="lit">113.2</span><span class="pln">    </span><span class="typ">Debian</span><span class="pln"> GNU</span><span class="pun">/</span><span class="typ">Linux</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">(</span><span class="pln">buster</span><span class="pun">)</span><span class="pln">   </span><span class="lit">4.19</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="pln">cloud</span><span class="pun">-</span><span class="pln">amd64   docker</span><span class="pun">://</span><span class="lit">18.9</span><span class="pun">.</span><span class="lit">9</span><span class="pln">
pool</span><span class="pun">-</span><span class="lit">7no0qd9e0</span><span class="pun">-</span><span class="lit">364fv</span><span class="pln">   </span><span class="typ">Ready</span><span class="pln">    </span><span class="pun">&lt;</span><span class="pln">none</span><span class="pun">&gt;</span><span class="pln">   </span><span class="lit">27h</span><span class="pln">   v1</span><span class="pun">.</span><span class="lit">18.8</span><span class="pln">   </span><span class="lit">10.118</span><span class="pun">.</span><span class="lit">0.3</span><span class="pln">    </span><span class="lit">203.0</span><span class="pun">.</span><span class="lit">113.3</span><span class="pln">   </span><span class="typ">Debian</span><span class="pln"> GNU</span><span class="pun">/</span><span class="typ">Linux</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">(</span><span class="pln">buster</span><span class="pun">)</span><span class="pln">   </span><span class="lit">4.19</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">10</span><span class="pun">-</span><span class="pln">cloud</span><span class="pun">-</span><span class="pln">amd64   docker</span><span class="pun">://</span><span class="lit">18.9</span><span class="pun">.</span><span class="lit">9</span></pre>

<p>
	أصبحت معلوماتنا كاملة لنستعرض التطبيق بكتابة العنوان التالي في المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_148" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">203.0</span><span class="pun">.</span><span class="lit">113.1</span><span class="pun">:</span><span class="lit">32654</span><span class="pun">/</span><span class="pln">polls</span></pre>

<p>
	من المفترض حصولك على واجهة التطبيق نفسها التي استعرضتها محليًا في الخطوة /1/:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="112368" href="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.07e9c7a9dfda4299a9aae779e7b1f569.png" rel=""><img alt="نتيجة واجهة التطبيق" class="ipsImage ipsImage_thumbnailed" data-fileid="112368" data-unique="4hsz5ovwb" src="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.07e9c7a9dfda4299a9aae779e7b1f569.png" style="width: 473px; height: auto;"></a>
</p>

<p>
	تصفح أيضًا واجهة إدارة التطبيق <code>admin/</code> للتأكد أكثر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_151" style="">
<span class="pln">http</span><span class="pun">://</span><span class="lit">203.0</span><span class="pun">.</span><span class="lit">113.1</span><span class="pun">:</span><span class="lit">32654</span><span class="pun">/</span><span class="pln">admin</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="112369" href="https://academy.hsoub.com/uploads/monthly_2022_11/02-img-polls_admin.png.309e094f8457d5c2dc3a1b4e71a4c0d9.png" rel=""><img alt="واجهة التطبيق" class="ipsImage ipsImage_thumbnailed" data-fileid="112369" data-unique="vyha65k3r" src="https://academy.hsoub.com/uploads/monthly_2022_11/02-img-polls_admin.png.309e094f8457d5c2dc3a1b4e71a4c0d9.png" style="width: 500px; height: auto;"></a>
</p>

<p>
	إذًا تطبيق جانغو الآن يستخدم حاويتين متماثلتين ويمكن الوصول له من خارج العنقود عبر خدمة كوبيرنتس مستقرة من نوع NodePort، والخطوة الأخيرة المتبقية لنا هي تأمين هذا الوصول ليتم عبر HTTPS وذلك بالاستفادة من وحدة التحكم <code>ingress-nginx</code> (وهي من متطلبات بيئة العمل المفترض تحضيرها) وبإنشاء كائن إدخال يوجه الحركة الخارجية إلى الخدمة <code>polls</code>.
</p>

<h2>
	الخطوة 8: إعداد HTTPS باستخدام Nginx Ingress ومدير الشهادات
</h2>

<p>
	توجه موارد كوبيرنتس من نوع المدخلات <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/" rel="external nofollow">Ingresses</a> حركة البيانات HTTP و HTTPS بمرونة إلى داخل العنقود، وذلك من خلال كائنات الإدخال التي تحدد قواعد التوجيه من المصادر الخارجية إلى خدمات كوبيرنتس الموجودة داخل عنقودك، ووحدات تحكم الإدخال التي تنفذ هذه القواعد عبر موازنة الحمل وتوجيه الطلبات إلى خدمات الواجهات الخلفية الأنسب.
</p>

<p>
	لابد أنك جهزت المتطلبات الواردة في بداية المقال وأنت تحضر بيئة العمل، ومنها تثبيت وحدة التحكم Ingress-nginx والوظيفة الإضافية cert-manager الخاصة بأتمتة شهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr>، مع ما يلزم لتصديق النطاق من Let’s Encrypt، وفي أثناء العملية أنشأت كائن إدخال لاختبار صلاحية الشهادة والتشفير <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> مع اثنين من خدمات الواجهات الخلفية الوهمية المنشأة لهذا الغرض، لذا سنبدأ بحذف echo-ingress الذي أنشأته خلال التحضير وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_154" style="">
<span class="pln">kubectl delete ingress echo</span><span class="pun">-</span><span class="pln">ingress</span></pre>

<p>
	يمكنك أيضًا حذف الخدمات الوهمية المنشأة أثناء التحضير عبر التعليمتين
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_156" style="">
<span class="pln">kubectl delete svc 
kubectl delete deploy</span></pre>

<p>
	لكنه ليس أمرًا ملزمًا في حالتنا.
</p>

<p>
	لديك أيضًا (من متطلبات بيئة العمل) سجل DNS من النوع <code>A</code> يربط اسم لنطاقك بعنوان الـ IP العام لمُدخل موازن الحمل، فيمكنك إذًا إنشاء مُدخل لنطاقك <code>your_domain.com</code> والخدمة <code>polls</code>.
</p>

<p>
	أنشئ ملف polls-ingress.yaml باستخدام محرر النصوص:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_158" style="">
<span class="pln">nano polls</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	والصق ضمنه التعليمات التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_162" style="">
<span class="pun">[</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">.</span><span class="pln">yaml</span><span class="pun">]</span><span class="pln">
apiVersion</span><span class="pun">:</span><span class="pln"> networking</span><span class="pun">.</span><span class="pln">k8s</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">v1beta1
kind</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Ingress</span><span class="pln">
metadata</span><span class="pun">:</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> polls</span><span class="pun">-</span><span class="pln">ingress
  annotations</span><span class="pun">:</span><span class="pln">
    kubernetes</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">ingress</span><span class="pun">.</span><span class="kwd">class</span><span class="pun">:</span><span class="pln"> </span><span class="str">"nginx"</span><span class="pln">
    cert</span><span class="pun">-</span><span class="pln">manager</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">cluster</span><span class="pun">-</span><span class="pln">issuer</span><span class="pun">:</span><span class="pln"> </span><span class="str">"letsencrypt-staging"</span><span class="pln">
spec</span><span class="pun">:</span><span class="pln">
  <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr></span><span class="pun">:</span><span class="pln">
  </span><span class="pun">-</span><span class="pln"> hosts</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">-</span><span class="pln"> your_domain</span><span class="pun">.</span><span class="pln">com
    secretName</span><span class="pun">:</span><span class="pln"> polls</span><span class="pun">-</span><span class="pln"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr>
  rules</span><span class="pun">:</span><span class="pln">
  </span><span class="pun">-</span><span class="pln"> host</span><span class="pun">:</span><span class="pln"> your_domain</span><span class="pun">.</span><span class="pln">com
    http</span><span class="pun">:</span><span class="pln">
      paths</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> backend</span><span class="pun">:</span><span class="pln">
          serviceName</span><span class="pun">:</span><span class="pln"> polls
          servicePort</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8000</span></pre>

<p>
	بموجب التعليمات السابقة، وصّفنا كائن إدخال يدعى <code>polls-ingress</code> ليستخدم وحدة التحكم ingress-nginx ووظيفة مدير الشهادات cert-manager مع ضبط ClusterIssuer الخاص بها على خيار staging، كما فعّلنا الشهادات <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> لترتبط باسم النطاق الخاص بنا <code>your_domain.com</code> أما ملف الشهادة والمفتاح الخاص تم تخزينهم في كائن من نوع سرّ يدعى <code>polls-<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr></code>، وأخيرًا وجهّنا حركة البيانات التي تطلب النطاق <code>your_domain.com</code> لتمر عبر البوابة <code>8000</code> من الخدمة <code>polls</code>.
</p>

<p>
	احفظ الملف الآن وأغلقه، لننشئ الكائن بالاعتماد عليه، باستعمال الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_164" style="">
<span class="pln">kubectl apply </span><span class="pun">-</span><span class="pln">f polls</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	سنحصل بعدها على الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_166" style="">
<span class="pln">ingress</span><span class="pun">.</span><span class="pln">networking</span><span class="pun">.</span><span class="pln">k8s</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">ingress created</span></pre>

<p>
	يمكنك استعراض وصف الكائن وتتبع حالته باستخدام <code>kubectl describe</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_168" style="">
<span class="pln">kubectl describe ingress polls</span><span class="pun">-</span><span class="pln">ingress</span></pre>

<p>
	التي تعطي الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_170" style="">
<span class="typ">Name</span><span class="pun">:</span><span class="pln">             polls</span><span class="pun">-</span><span class="pln">ingress
</span><span class="typ">Namespace</span><span class="pun">:</span><span class="pln">        default
</span><span class="typ">Address</span><span class="pun">:</span><span class="pln">          workaround</span><span class="pun">.</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com
</span><span class="typ">Default</span><span class="pln"> backend</span><span class="pun">:</span><span class="pln">  default</span><span class="pun">-</span><span class="pln">http</span><span class="pun">-</span><span class="pln">backend</span><span class="pun">:</span><span class="lit">80</span><span class="pln"> </span><span class="pun">(&lt;</span><span class="pln">error</span><span class="pun">:</span><span class="pln"> endpoints </span><span class="str">"default-http-backend"</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> found</span><span class="pun">&gt;)</span><span class="pln">
<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr></span><span class="pun">:</span><span class="pln">
  polls</span><span class="pun">-</span><span class="pln"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr> terminates your_domain</span><span class="pun">.</span><span class="pln">com
</span><span class="typ">Rules</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Host</span><span class="pln">        </span><span class="typ">Path</span><span class="pln">  </span><span class="typ">Backends</span><span class="pln">
  </span><span class="pun">----</span><span class="pln">        </span><span class="pun">----</span><span class="pln">  </span><span class="pun">--------</span><span class="pln">
  your_domain</span><span class="pun">.</span><span class="pln">com
                 polls</span><span class="pun">:</span><span class="lit">8000</span><span class="pln"> </span><span class="pun">(</span><span class="lit">10.244</span><span class="pun">.</span><span class="lit">0.207</span><span class="pun">:</span><span class="lit">8000</span><span class="pun">,</span><span class="lit">10.244</span><span class="pun">.</span><span class="lit">0.53</span><span class="pun">:</span><span class="lit">8000</span><span class="pun">)</span><span class="pln">
</span><span class="typ">Annotations</span><span class="pun">:</span><span class="pln">  cert</span><span class="pun">-</span><span class="pln">manager</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">cluster</span><span class="pun">-</span><span class="pln">issuer</span><span class="pun">:</span><span class="pln"> letsencrypt</span><span class="pun">-</span><span class="pln">staging
              kubernetes</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">ingress</span><span class="pun">.</span><span class="kwd">class</span><span class="pun">:</span><span class="pln"> nginx
</span><span class="typ">Events</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Type</span><span class="pln">    </span><span class="typ">Reason</span><span class="pln">             </span><span class="typ">Age</span><span class="pln">   </span><span class="typ">From</span><span class="pln">                      </span><span class="typ">Message</span><span class="pln">
  </span><span class="pun">----</span><span class="pln">    </span><span class="pun">------</span><span class="pln">             </span><span class="pun">----</span><span class="pln">  </span><span class="pun">----</span><span class="pln">                      </span><span class="pun">-------</span><span class="pln">
  </span><span class="typ">Normal</span><span class="pln">  CREATE             </span><span class="lit">51s</span><span class="pln">   nginx</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">-</span><span class="pln">controller  </span><span class="typ">Ingress</span><span class="pln"> default</span><span class="pun">/</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">ingress
  </span><span class="typ">Normal</span><span class="pln">  </span><span class="typ">CreateCertificate</span><span class="pln">  </span><span class="lit">51s</span><span class="pln">   cert</span><span class="pun">-</span><span class="pln">manager              </span><span class="typ">Successfully</span><span class="pln"> created </span><span class="typ">Certificate</span><span class="pln"> </span><span class="str">"polls-<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr>"</span><span class="pln">
  </span><span class="typ">Normal</span><span class="pln">  UPDATE             </span><span class="lit">25s</span><span class="pln">   nginx</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">-</span><span class="pln">controller  </span><span class="typ">Ingress</span><span class="pln"> default</span><span class="pun">/</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">ingress</span></pre>

<p>
	وبنفس الطريقة تستطيع استعراض وصف <code>polls-<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr></code> للتأكد من صحته:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_172" style="">
<span class="pln">kubectl describe certificate polls</span><span class="pun">-</span><span class="pln"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr></span></pre>

<p>
	وستظهر لك حالة الشهادة في الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_174" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="typ">Events</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Type</span><span class="pln">    </span><span class="typ">Reason</span><span class="pln">     </span><span class="typ">Age</span><span class="pln">    </span><span class="typ">From</span><span class="pln">          </span><span class="typ">Message</span><span class="pln">
  </span><span class="pun">----</span><span class="pln">    </span><span class="pun">------</span><span class="pln">     </span><span class="pun">----</span><span class="pln">   </span><span class="pun">----</span><span class="pln">          </span><span class="pun">-------</span><span class="pln">
  </span><span class="typ">Normal</span><span class="pln">  </span><span class="typ">Issuing</span><span class="pln">    </span><span class="lit">3m33s</span><span class="pln">  cert</span><span class="pun">-</span><span class="pln">manager  </span><span class="typ">Issuing</span><span class="pln"> certificate </span><span class="kwd">as</span><span class="pln"> </span><span class="typ">Secret</span><span class="pln"> does </span><span class="kwd">not</span><span class="pln"> exist
  </span><span class="typ">Normal</span><span class="pln">  </span><span class="typ">Generated</span><span class="pln">  </span><span class="lit">3m32s</span><span class="pln">  cert</span><span class="pun">-</span><span class="pln">manager  </span><span class="typ">Stored</span><span class="pln"> new private key </span><span class="kwd">in</span><span class="pln"> temporary </span><span class="typ">Secret</span><span class="pln"> resource </span><span class="str">"polls-<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr>-v9lv9"</span><span class="pln">
  </span><span class="typ">Normal</span><span class="pln">  </span><span class="typ">Requested</span><span class="pln">  </span><span class="lit">3m32s</span><span class="pln">  cert</span><span class="pun">-</span><span class="pln">manager  </span><span class="typ">Created</span><span class="pln"> new </span><span class="typ">CertificateRequest</span><span class="pln"> resource </span><span class="str">"polls-<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr>-drx9c"</span><span class="pln">
  </span><span class="typ">Normal</span><span class="pln">  </span><span class="typ">Issuing</span><span class="pln">    </span><span class="lit">2m58s</span><span class="pln">  cert</span><span class="pun">-</span><span class="pln">manager  </span><span class="typ">The</span><span class="pln"> certificate has been successfully issued</span></pre>

<p>
	بذلك نكون قد وجّهنا الطلبات الواردة إلى التطبيق لتصبح HTTPS مشفرة، ولكن Clusterissuer في وضع staging ما يعني أن شهادتنا التجريبية المزيفة من Let's Encrypt لن تحظى بثقة معظم متصفحات الويب، أكتب الطلب التالي لنطاقك في نافذة سطر الأوامر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_176" style="">
<span class="pln">wget </span><span class="pun">-</span><span class="pln">O </span><span class="pun">-</span><span class="pln"> http</span><span class="pun">://</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">polls</span></pre>

<p>
	ولاحظ التحذير الظاهر في الخرج بسبب هذه الشهادة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_178" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
ERROR</span><span class="pun">:</span><span class="pln"> cannot verify your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="str">'s certificate, issued by ‘CN=Fake LE Intermediate X1’:
  Unable to locally verify the issuer'</span><span class="pln">s authority</span><span class="pun">.</span><span class="pln">
</span><span class="typ">To</span><span class="pln"> connect to your_domain</span><span class="pun">.</span><span class="pln">com insecurely</span><span class="pun">,</span><span class="pln"> use </span><span class="pun">`--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">check</span><span class="pun">-</span><span class="pln">certificate</span><span class="str">'.</span></pre>

<p>
	سنتبع المقترح الوارد في الخرج أعلاه ونستخدم الراية <code>no-check-certificate--</code> مع الطلب <code>wget</code> لتجاوز التحذير الخاص بالشهادة وفق التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_180" style="">
<span class="pln">wget </span><span class="pun">--</span><span class="pln">no</span><span class="pun">-</span><span class="pln">check</span><span class="pun">-</span><span class="pln">certificate </span><span class="pun">-</span><span class="pln">q </span><span class="pun">-</span><span class="pln">O </span><span class="pun">-</span><span class="pln"> http</span><span class="pun">://</span><span class="pln">your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">polls</span></pre>

<p>
	ستحصل عندها على الخرج المبين أدناه والذي يظهر صفحة HTML المقابلة للواجهة <code>polls/</code> وأيضًا مسار التخزين الكائني الذي أُحضرت منه الصفحة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_182" style="">
<span class="pun">&lt;</span><span class="pln">link rel</span><span class="pun">=</span><span class="str">"stylesheet"</span><span class="pln"> type</span><span class="pun">=</span><span class="str">"text/css"</span><span class="pln"> href</span><span class="pun">=</span><span class="str">"https://your_space.nyc3.digitaloceanspaces.com/django-polls/static/polls/style.css"</span><span class="pun">&gt;</span><span class="pln">


    </span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">No</span><span class="pln"> polls are available</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span></pre>

<p>
	وهذا دليل على نجاح طلب النطاق، ما يعني أن بإمكاننا الآن تبديل Clusterissuer إلى وضع المنتج production عوضًا عن staging عبر فتح الملف <code>polls-ingress.yaml</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_185" style="">
<span class="pln">nano polls</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	وتعديل قيمة <code>cluster-issuer</code> كما ذكرنا:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_187" style="">
<span class="pun">[</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">.</span><span class="pln">yaml</span><span class="pun">]</span><span class="pln">
apiVersion</span><span class="pun">:</span><span class="pln"> networking</span><span class="pun">.</span><span class="pln">k8s</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">v1beta1
kind</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Ingress</span><span class="pln">
metadata</span><span class="pun">:</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> polls</span><span class="pun">-</span><span class="pln">ingress
  annotations</span><span class="pun">:</span><span class="pln">
    kubernetes</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">ingress</span><span class="pun">.</span><span class="kwd">class</span><span class="pun">:</span><span class="pln"> </span><span class="str">"nginx"</span><span class="pln">
    cert</span><span class="pun">-</span><span class="pln">manager</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">cluster</span><span class="pun">-</span><span class="pln">issuer</span><span class="pun">:</span><span class="pln"> </span><span class="str">"letsencrypt-prod"</span><span class="pln">
spec</span><span class="pun">:</span><span class="pln">
  <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr></span><span class="pun">:</span><span class="pln">
  </span><span class="pun">-</span><span class="pln"> hosts</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">-</span><span class="pln"> your_domain</span><span class="pun">.</span><span class="pln">com
    secretName</span><span class="pun">:</span><span class="pln"> polls</span><span class="pun">-</span><span class="pln"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr>
  rules</span><span class="pun">:</span><span class="pln">
  </span><span class="pun">-</span><span class="pln"> host</span><span class="pun">:</span><span class="pln"> your_domain</span><span class="pun">.</span><span class="pln">com
    http</span><span class="pun">:</span><span class="pln">
      paths</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> backend</span><span class="pun">:</span><span class="pln">
          serviceName</span><span class="pun">:</span><span class="pln"> polls
          servicePort</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8000</span></pre>

<p>
	احفظ التغييرات على الملف وأغلقه، ولكن انتبه فهي لن تصبح نافذة حتى تُحدّث الكائن <code>polls-ingress</code> باستخدام التعليمة <code>kubectl apply</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_189" style="">
<span class="pln">kubectl apply </span><span class="pun">-</span><span class="pln">f polls</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	سيشير الخرج إلى نجاح العملية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_193" style="">
<span class="pln">ingress</span><span class="pun">.</span><span class="pln">networking</span><span class="pun">.</span><span class="pln">k8s</span><span class="pun">.</span><span class="pln">io</span><span class="pun">/</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">ingress configured</span></pre>

<p>
	شاهد التغير الذي طرأ على حالة الشهادات باستخدام:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_191" style="">
<span class="pln">kubectl describe certificate polls</span><span class="pun">-</span><span class="pln"><abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr></span></pre>

<p>
	أو
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_197" style="">
<span class="pln">kubectl describe ingress polls</span><span class="pun">-</span><span class="pln">ingress</span></pre>

<p>
	وستحصل على الخرج التالي الذي يبين نجاح التحقق من الشهادات الجديدة وكونها مخزنة في السرّ <code>polls-<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr></code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_199" style="">
<span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> </span><span class="pun">.</span><span class="pln">
</span><span class="typ">Events</span><span class="pun">:</span><span class="pln">
  </span><span class="typ">Type</span><span class="pln">    </span><span class="typ">Reason</span><span class="pln">             </span><span class="typ">Age</span><span class="pln">                </span><span class="typ">From</span><span class="pln">                      </span><span class="typ">Message</span><span class="pln">
  </span><span class="pun">----</span><span class="pln">    </span><span class="pun">------</span><span class="pln">             </span><span class="pun">----</span><span class="pln">               </span><span class="pun">----</span><span class="pln">                      </span><span class="pun">-------</span><span class="pln">
  </span><span class="typ">Normal</span><span class="pln">  CREATE             </span><span class="lit">23m</span><span class="pln">                nginx</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">-</span><span class="pln">controller  </span><span class="typ">Ingress</span><span class="pln"> default</span><span class="pun">/</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">ingress
  </span><span class="typ">Normal</span><span class="pln">  </span><span class="typ">CreateCertificate</span><span class="pln">  </span><span class="lit">23m</span><span class="pln">                cert</span><span class="pun">-</span><span class="pln">manager              </span><span class="typ">Successfully</span><span class="pln"> created </span><span class="typ">Certificate</span><span class="pln"> </span><span class="str">"polls-<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr>"</span><span class="pln">
  </span><span class="typ">Normal</span><span class="pln">  UPDATE             </span><span class="lit">76s</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x2 over </span><span class="lit">22m</span><span class="pun">)</span><span class="pln">  nginx</span><span class="pun">-</span><span class="pln">ingress</span><span class="pun">-</span><span class="pln">controller  </span><span class="typ">Ingress</span><span class="pln"> default</span><span class="pun">/</span><span class="pln">polls</span><span class="pun">-</span><span class="pln">ingress
  </span><span class="typ">Normal</span><span class="pln">  </span><span class="typ">UpdateCertificate</span><span class="pln">  </span><span class="lit">76s</span><span class="pln">                cert</span><span class="pun">-</span><span class="pln">manager              </span><span class="typ">Successfully</span><span class="pln"> updated </span><span class="typ">Certificate</span><span class="pln"> </span><span class="str">"polls-<abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">tls</abbr>"</span></pre>

<p>
	استعرض الآن تطبيقك
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_202" style="">
<span class="pln"> your_domain</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="pln">polls </span></pre>

<p>
	ولاحظ تفعيل HTTPS وعدم وجود أي تحذير بخصوص صلاحية الشهادة ويمكنك استعراض تفاصيل الشهادة وسلطة التصديق بالضغط على رمز القفل قرب الشريط المخصص لكتابة عنوان URL في متصفحك:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="112368" href="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.07e9c7a9dfda4299a9aae779e7b1f569.png" rel=""><img alt="نتيجة تفعيل HTTPS" class="ipsImage ipsImage_thumbnailed" data-fileid="112368" data-unique="4hsz5ovwb" src="https://academy.hsoub.com/uploads/monthly_2022_11/01-img-polls_app.png.07e9c7a9dfda4299a9aae779e7b1f569.png" style="width: 473px; height: auto;"></a>
</p>

<p>
	ننصحك بإجراء اختياري أخير هو تعديل نوع الخدمة polls من NodePort إلى ClusterIP إذ إن هذا النوع يقبل فقط الحركة القادمة من عنوان IP الداخلي للعنقود ما يمنح تطبيقك مزيدًا من الأمان:
</p>

<p>
	افتح الملف polls-svc.yaml باستخدام المحرر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_205" style="">
<span class="pln">nano polls</span><span class="pun">-</span><span class="pln">svc</span><span class="pun">.</span><span class="pln">yaml</span></pre>

<p>
	بدل قيمة المحدد <code>type</code> من <code>NodePort</code> إلى <code>ClusterIP</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_207" style="">
<span class="pln">apiVersion</span><span class="pun">:</span><span class="pln"> v1
kind</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Service</span><span class="pln">
metadata</span><span class="pun">:</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> polls
  labels</span><span class="pun">:</span><span class="pln">
    app</span><span class="pun">:</span><span class="pln"> polls
spec</span><span class="pun">:</span><span class="pln">
  type</span><span class="pun">:</span><span class="pln"> </span><span class="typ">ClusterIP</span><span class="pln">
  selector</span><span class="pun">:</span><span class="pln">
    app</span><span class="pun">:</span><span class="pln"> polls
  ports</span><span class="pun">:</span><span class="pln">
    </span><span class="pun">-</span><span class="pln"> port</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8000</span><span class="pln">
      targetPort</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8000</span></pre>

<p>
	احفظ التغييرات على الملف وأغلقه، وحدّث بعدها الخدمة عبر الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_209" style="">
<span class="pln">kubectl apply </span><span class="pun">-</span><span class="pln">f polls</span><span class="pun">-</span><span class="pln">svc</span><span class="pun">.</span><span class="pln">yaml </span><span class="pun">--</span><span class="pln">force</span></pre>

<p>
	سيؤكد لك الخرج التالي نجاح تعديل الخدمة:
</p>

<pre class="ipsCode">
service/polls configured
</pre>

<p>
	تحقق من حالة الخدمة المحدثة باستخدام:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_211" style="">
<span class="pln">kubectl get svc polls</span></pre>

<p>
	ولاحظ معلومات الخرج:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_9925_215" style="">
<span class="pln">NAME    TYPE        CLUSTER</span><span class="pun">-</span><span class="pln">IP       EXTERNAL</span><span class="pun">-</span><span class="pln">IP   PORT</span><span class="pun">(</span><span class="pln">S</span><span class="pun">)</span><span class="pln">    AGE
polls   </span><span class="typ">ClusterIP</span><span class="pln">   </span><span class="lit">10.245</span><span class="pun">.</span><span class="lit">203.186</span><span class="pln">   </span><span class="pun">&lt;</span><span class="pln">none</span><span class="pun">&gt;</span><span class="pln">        </span><span class="lit">8000</span><span class="pun">/</span><span class="pln">TCP   </span><span class="lit">22s</span></pre>

<p>
	وبذلك قيدّت الوصول لتطبيقك ليكون حصرًا من خلال اسم النطاق والمُدخل المنشأ في هذه الخطوة.
</p>

<h2>
	خاتمة
</h2>

<p>
	تعلمنا في هذا المقال طريقة نشر تطبيق جانغو قابل للتوسيع ومؤمن ببروتوكول HTTPS باستخدام عنقود كوبيرنتس، مع تسليم ملفات التطبيق الساكنة من وحدة تخزين كائني خارجية، وتوفير المرونة التامة لزيادة أو إنقاص عدد Pods التي تُخدم التطبيق بسرعة وبسهولة عبر المحدد <code>replicas</code> من ملف الإعدادات الخاص بوحدة تحكم النشر Deployment والتي أسميناها <code>polls-app</code>، ونذكرك بإمكانية تسريع التسليم باستخدام <a href="https://academy.hsoub.com/devops/cloud-computing/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%89-cdn-%D9%84%D8%AA%D8%B3%D8%B1%D9%8A%D8%B9-%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%89-%D8%A7%D9%84%D8%AB%D8%A7%D8%A8%D8%AA-r393/" rel="">شبكة توصيل المحتوى CDN</a> كإجراء اختياري.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-deploy-a-scalable-and-secure-django-application-with-kubernetes" rel="external nofollow">How To Deploy a Scalable and Secure Django Application with Kubernetes</a> لصاحبه Hanif Jetha.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/docker/%D9%85%D8%A7-%D8%A7%D9%84%D9%81%D8%B1%D9%82-%D8%A8%D9%8A%D9%86-%D8%AF%D9%88%D9%83%D8%B1-docker-%D9%88%D9%83%D9%88%D8%A8%D9%8A%D8%B1%D9%86%D9%8A%D8%AA%D9%8A%D8%B3-kubernetes%D8%9F-r612/" rel="">ما الفرق بين دوكر Docker وكوبيرنيتيس Kubernetes؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/cloud-computing/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-helm-%D9%85%D8%AF%D9%8A%D8%B1-%D8%AD%D8%B2%D9%85-kubernetes-r470/" rel="">مدخل إلى Helm: مدير حزم Kubernetes</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/django/%D8%A7%D9%84%D8%A8%D8%AF%D8%A1-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D8%AC%D8%A7%D9%86%D8%BA%D9%88-%D9%84%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%88%D9%8A%D8%A8-r1625/" rel="">البدء مع إطار العمل جانغو لإنشاء تطبيق ويب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">662</guid><pubDate>Wed, 16 Nov 2022 07:31:04 +0000</pubDate></item><item><title>&#x645;&#x627; &#x647;&#x648; &#x627;&#x644;&#x62A;&#x643;&#x627;&#x645;&#x644; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631; &#x648;&#x627;&#x644;&#x646;&#x634;&#x631; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631; CI/CD&#x61F;</title><link>https://academy.hsoub.com/devops/deployment/%D9%85%D8%A7-%D9%87%D9%88-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd%D8%9F-r792/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2023_08/aca--CD-CI(1).png.ac4b6ed0d112ad1436d1c19a23650ba8.png" /></p>
<p>
	سنتحدث في هذا الفيديو عن CI/CD إذ سنتعرف على التكامل المستمر CI أو Continuous Integration وعن النشر المستمر CD أو Continuous Deployment أو Continuous Delivery كما سنتطرق للحديث عن الفرق بين CI/CD وأهمية وجودها في دورة حياة تطوير التطبيقات، إضافة إلى أننا سنذكر لكم أشهر الأدوات المستخدمة.
</p>

<p>
	<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="603" src="https://academy.hsoub.com/applications/core/interface/index.html" title="ما هو التكامل المستمر والنشر المستمر CI/CD" width="1072" data-embed-src="https://www.youtube.com/embed/hFzSG9qNWWs"></iframe>
</p>

<p>
	تعلم البرمجة وعلوم الحاسوب والتعامل مع لينكس في أكاديمية حسوب عبر <a href="https://academy.hsoub.com/learn/computer-science/" rel="">دورة علوم الحاسوب</a>، ولا تنسَ الاستعانة خلال رحلة تعلمك وعملك بتوثيقات <a href="https://wiki.hsoub.com/%D8%A7%D9%84%D8%B5%D9%81%D8%AD%D8%A9_%D8%A7%D9%84%D8%B1%D8%A6%D9%8A%D8%B3%D9%8A%D8%A9" rel="external">موسوعة حسوب</a> المجانية. وإذا أردت متابعة المعلومات البرمجية العلمية مكتوبة فيمكنك الاطلاع على <a href="https://academy.hsoub.com/programming/" rel="">قسم البرمجة في أكاديمية حسوب</a>، كما يمكنك متابعة جديد الفيديوهات التقنية المتاحة على <a href="https://www.youtube.com/@HsoubAcademy" rel="external nofollow">يوتيوب أكاديمية حسوب</a> مجانًا.
</p>
]]></description><guid isPermaLink="false">792</guid><pubDate>Sat, 03 Dec 2022 15:00:00 +0000</pubDate></item><item><title>&#x62A;&#x62E;&#x62F;&#x64A;&#x645; &#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x641;&#x644;&#x627;&#x633;&#x643; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x62E;&#x627;&#x62F;&#x645;&#x64A; &#x627;&#x644;&#x648;&#x64A;&#x628; uWSGI &#x648; Nginx</title><link>https://academy.hsoub.com/devops/deployment/%D8%AA%D8%AE%D8%AF%D9%8A%D9%85-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AE%D8%A7%D8%AF%D9%85%D9%8A-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-uwsgi-%D9%88-nginx-r649/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_09/63244a8fef5c7_-------uWSGI--Nginx---.jpg.4ea7e324ee0a8a111c548ae099770414.jpg" /></p>

<p>
	سننشئ في هذا المقال تطبيق <a href="https://academy.hsoub.com/programming/python/%D8%A7%D9%84%D9%85%D8%B1%D8%AC%D8%B9-%D8%A7%D9%84%D8%B4%D8%A7%D9%85%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B9%D9%84%D9%85-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r735/" rel="">بايثون</a> باستخدام إطار العمل المُصغّر <a href="https://academy.hsoub.com/programming/python/flask/" rel="">فلاسك Flask</a> في الإصدار 20.04 من توزيعة أبونتو، سيتضمّن القسم الأكبر من هذا المقال معلومات حول كيفية إعداد خادم تطبيق uWSGI وكيفية الوصول إلى هذا التطبيق وآلية <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-nginx-%D9%83%D9%88%D8%B3%D9%8A%D8%B7-%D8%B9%D9%83%D8%B3%D9%8A-reverse-proxy-%D9%84%D9%80apache-r19/" rel="">إعداد خادم Nginx ليعمل مثل خادم وكيل معكوس reverse proxy</a> لدى طرف المستخدم.
</p>

<h2>
	مستلزمات العمل
</h2>

<p>
	قبل المتابعة في هذا المقال لا بدّ من:
</p>

<ul>
<li>
		توفّر <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم</a> مثبّت عليه نظام تشغيل <a href="https://academy.hsoub.com/programming/python/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-3-%D9%88%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A8%D9%8A%D8%A6%D8%AA%D9%87-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-%D8%B9%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-1804-r709/" rel="">أبونتو 20.04</a> ذو مستخدم عادي ليس مستخدم جذر root وبصلاحيات sudo، مع تفعيل جدار الحماية.
	</li>
	<li>
		تثبيت خادم <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%88%D9%86%D8%AA%D9%88-1804-r434/" rel="">Nginx</a>.
	</li>
	<li>
		خادم نطاق أسماء للإشارة إلى خادم التطبيق، إذ من الممكن شراء نطاق من Namecheap أو الحصول على نطاق مجاني من Freenom، وفي مقالنا سنفترض أنّ خادم نطاق الأسماء DNS يحوي السجلات التالية:
	</li>
	<li>
		سجل من النوع A يحمل القيمة "your_domain" ويشير إلى عنوان IP العام المُخصّص لخادم التطبيق.
	</li>
	<li>
		سجل من النوع A يحمل القيمة "www.your_domain" يشير إلى عنوان IP العام المُخصّص لخادم التطبيق.
	</li>
</ul>
<p>
	إضافةً لما سبق يُفضّل وجود معرفة جيدة بالتعامل مع خادم uWSGI، ومع الخادم الذي سننشئ عليه التطبيق، والفهم الجيد لمواصفات وخصائص بروتوكول WSGI.
</p>

<h2>
	الخطوة الأولى - تثبيت المكونات اللازمة من مستودعات أبونتو
</h2>

<p>
	سنثبّت في هذه الخطوة كافّة المكونات التي سنحتاجها من مستودع أبونتو، إضافةً إلى مدير تثبيت حزمة بايثون "pip" لإدارة مكونات بايثون، كما سننزّل ملفات تطوير بايثون الضرورية لبناء خادم "uWSGI".
</p>

<p>
	بدايةً سنحدّث موقع دليل الحزمة المحلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_7" style="">
<span class="pln">$ sudo apt update</span></pre>

<p>
	ثمّ سنثبّت الحزم اللازمة لبناء بيئة بايثون والتي تشمل الحزمة "python3-pip" وغيرها من الحزم وأدوات التطوير الضرورية للحصول على بيئة برمجية مستقرّة ومتينة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_9" style="">
<span class="pln">$ sudo apt install python3</span><span class="pun">-</span><span class="pln">pip python3</span><span class="pun">-</span><span class="pln">dev build</span><span class="pun">-</span><span class="pln">essential libssl</span><span class="pun">-</span><span class="pln">dev libffi</span><span class="pun">-</span><span class="pln">dev python3</span><span class="pun">-</span><span class="pln">setuptools</span></pre>

<p>
	الآن وبعد الانتهاء من تثبيت الحزم ننتقل إلى إنشاء البيئة الافتراضية اللازمة لمشروعنا.
</p>

<h2>
	الخطوة الثانية – إنشاء بيئة بايثون افتراضية
</h2>

<p>
	سنُعِد في هذه الخطوة البيئة الافتراضية اللازمة لفصل تطبيق فلاسك عن ملفات بايثون الأُخرى في النظام.
</p>

<p>
	سنبدأ بتثبيت الحزمة "python-venv" المسؤولة عن تحميل حزم بايثون ضمن بيئة معزولة خاصّة بكل مشروع، والتي ستتثبّت بدورها الوحدة "venv":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_11" style="">
<span class="pln">$ sudo apt install python3</span><span class="pun">-</span><span class="pln">venv</span></pre>

<p>
	الآن سننشئ المجلد الرئيسي لمشروع فلاسك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_13" style="">
<span class="pln">$ mkdir </span><span class="pun">~/</span><span class="pln">myproject</span></pre>

<p>
	ثمّ سننتقل إلى هذا المجلد كما يلي:
</p>

<pre class="ipsCode">
$ cd ~/myproject
</pre>

<p>
	وفيه سننشئ البيئة الافتراضية اللازمة لتخزين متطلبات بايثون التي سيحتاجها مشروع فلاسك، وذلك بتنفيذ الأمر التالي:
</p>

<pre class="ipsCode">
$ python3.6 -m venv myprojectenv
</pre>

<p>
	سيثبّت الأمر السابق نسخةً محليةً من بيئة بايثون ومن الحزمة "pip" في مجلد باسم "myprojectenv" ضمن المجلد الرئيسي للتطبيق.
</p>

<p>
	والآن، لا بدّ من تفعيل هذه البيئة الافتراضية قبل تثبيت أي تطبيقات ضمنها على النحو التالي:
</p>

<pre class="ipsCode">
$ source myprojectenv/bin/activate
</pre>

<p>
	وهنا نلاحظ تغيّر مؤشّر واجهة أسطر الأوامر ليشير إلى عملنا ضمن البيئة الافتراضية ليصبح على الشّكل:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_16" style="">
<span class="pun">(</span><span class="pln">myprojectenv</span><span class="pun">)</span><span class="pln">user@host</span><span class="pun">:~/</span><span class="pln">myproject$</span></pre>

<h2>
	الخطوة الثالثة - إعداد تطبيق فلاسك
</h2>

<p>
	الآن، وبعد تفعيل البيئة الافتراضية والعمل ضمنها، أصبح بالإمكان تثبيت فلاسك وخادم "uWSGI" لنباشر بتصميم التطبيق.
</p>

<p>
	سنثبّت بدايةً المكوّن "wheel" مع استخدام مُثبّت الحزم "pip"، وبذلك نتأكّد من تثبيت وعمل الحزم بصورةٍ سليمة حتّى في حال عدم وجود "wheel".
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_18" style="">
<span class="pln">$ pip install wheel</span></pre>

<p>
	<strong>ملاحظة:</strong> بغض النظر عن إصدار بايثون المُستخدم، يجب استخدام الأمر <code>pip</code> وليس <code>pip3</code> عندما تكون البيئة الافتراضية قيد التشغيل.
</p>

<p>
	سنثبّت الآن فلاسك وخادم uWSGI:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_20" style="">
<span class="pun">(</span><span class="pln">muprojectenv</span><span class="pun">)</span><span class="pln"> $ pip install uwsgi flask</span></pre>

<p>
	وبعد انتهاء التثبيت، يمكننا البدء باستخدام فلاسك.
</p>

<h3>
	إنشاء تطبيق بمثابة عينة للاختبار
</h3>

<p>
	الآن، وبعدما أصبح فلاسك جاهزًا، يمكننا الشروع بإنشاء تطبيق بسيط، فكما نعلم فإن فلاسك إطار عمل مًصغّر، لا يحتوي على كثيرٍ من الأدوات مثل تلك التي يمتلكها إطار العمل كامل الميزات، إذ أن فلاسك بالأصل هو مُجرّد وحدة نستوردها إلى مشاريعنا للمساعدة في تهيئة تطبيق الويب.
</p>

<p>
	وطالما أنّ التطبيق ككل قد يصبح أكثر تعقيدًا لاحقًا بإضافة مكونات أُخرى عليه، سننشئ تطبيق فلاسك ضمن ملف واحد باستخدام أي محرّر نصوص تفضّله، وفي مقالنا سنستخدم محرّر نانو nano وسنسمّي الملف "myproject.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_22" style="">
<span class="pun">(</span><span class="pln">muprojectenv</span><span class="pun">)</span><span class="pln"> $ nano </span><span class="pun">~/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	أي ستُخزّن الشيفرة الخاصّة بالتطبيق في هذا الملف، وفيها سنستورد فلاسك وننشئ كائن فلاسك الذي سنستخدمه لتعريف الدوال التي ستعمل لدى طلب اتجاهٍ معينة من قبل المُستخدم:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_24" style="">
<span class="kwd">from</span><span class="pln"> flask </span><span class="kwd">import</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln">
app </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Flask</span><span class="pun">(</span><span class="pln">__name__</span><span class="pun">)</span><span class="pln">

</span><span class="lit">@app</span><span class="pun">.</span><span class="pln">route</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">)</span><span class="pln">
</span><span class="kwd">def</span><span class="pln"> hello</span><span class="pun">():</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"&lt;h1 style='color:blue'&gt;Hello There!&lt;/h1&gt;"</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    app</span><span class="pun">.</span><span class="pln">run</span><span class="pun">(</span><span class="pln">host</span><span class="pun">=</span><span class="str">'0.0.0.0'</span><span class="pun">)</span></pre>

<p>
	حدّدنا في الشيفرة السابقة المحتوى الذي سيُعرض عند الدخول إلى النطاق الأساسي للتطبيق.
</p>

<p>
	نحفظ الملف ونغلقه بالضغط على مفتاحي "CTRL+X"، ثمّ "Y"، ثمّ "ENTER" في حال كنت تستخدم محرّر النصوص نانو.
</p>

<p>
	وباتباع خطوات إعداد الخادم الابتدائي سيكون جدار حماية UFW قيد التشغيل، وبالتالي لن نتمكّن من الوصول إلى لتطبيق لاختباره ما لم نسمح بالوصول إلى المنفذ <code>5000</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_26" style="">
<span class="pun">(</span><span class="pln">muprojectenv</span><span class="pun">)</span><span class="pln"> $ sudo ufw allow </span><span class="lit">5000</span></pre>

<p>
	سنختبر الآن تطبيق فلاسك:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_28" style="">
<span class="pun">(</span><span class="pln">muprojectenv</span><span class="pun">)</span><span class="pln"> $ python myproject</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	فيكون الخرج مُشابهًا لما يلي، والذي يتضمّن تحذيرات مهمّة ومفيدة تشير لعدم استخدام إعداد الخادم هذا (خادم التطوير) في مرحلة الاستخدام الفعلي للتطبيق (نشر المُنتج).
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_30" style="">
<span class="pun">*</span><span class="pln"> </span><span class="typ">Serving</span><span class="pln"> </span><span class="typ">Flask</span><span class="pln"> app </span><span class="str">"myproject"</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lazy loading</span><span class="pun">)</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Environment</span><span class="pun">:</span><span class="pln"> production
   WARNING</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Do</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> use the development server </span><span class="kwd">in</span><span class="pln"> a production environment</span><span class="pun">.</span><span class="pln">
   </span><span class="typ">Use</span><span class="pln"> a production WSGI server instead</span><span class="pun">.</span><span class="pln">
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Debug</span><span class="pln"> mode</span><span class="pun">:</span><span class="pln"> off
 </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Running</span><span class="pln"> on http</span><span class="pun">://</span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">5000</span><span class="pun">/</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Press</span><span class="pln"> CTRL</span><span class="pun">+</span><span class="pln">C to quit</span><span class="pun">)</span></pre>

<p>
	الآن وباستخدام المتصفح ننتقل إلى عنوان IP الخاص بالخادم متبوعًا برقم المنفذ الخاص بالتطبيق "5000:" على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_32" style="">
<span class="pln">http</span><span class="pun">://</span><span class="pln">your_server_ip</span><span class="pun">:</span><span class="lit">5000</span></pre>

<p>
	فتكون النتيجة مُشابهةً لما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="108086" href="https://academy.hsoub.com/uploads/monthly_2022_09/1st_test_app.png.90d4c23e8d90992b798ca19d8f62dc4d.png" rel=""><img alt="1st_test_app.png" class="ipsImage ipsImage_thumbnailed" data-fileid="108086" data-unique="3qais3dow" src="https://academy.hsoub.com/uploads/monthly_2022_09/1st_test_app.png.90d4c23e8d90992b798ca19d8f62dc4d.png"></a>
</p>

<p>
	وعند الانتهاء نضغط على "CTRL + C" في نافذة مشغّل الأوامر لإيقاف خادم تطوير فلاسك.
</p>

<h3>
	إنشاء نقطة دخول لتطبيق WSGI
</h3>

<p>
	في هذه الخطوة سننشئ ملفًا ليعمل مثل نقطة دخول إلى التطبيق، والتي سترشد خادم uWSGI لآلية التفاعل مع التطبيق، لذا سننشئ ملفاً باسم "wsgi.py":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_34" style="">
<span class="pun">(</span><span class="pln">muprojectenv</span><span class="pun">)</span><span class="pln"> $ nano </span><span class="pun">~/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">wsgi</span><span class="pun">.</span><span class="pln">py</span></pre>

<p>
	سنستورد في هذا الملف نسخة فلاسك من التطبيق ونشغلها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_36" style="">
<span class="kwd">from</span><span class="pln"> myproject </span><span class="kwd">import</span><span class="pln"> app

</span><span class="kwd">if</span><span class="pln"> __name__ </span><span class="pun">==</span><span class="pln"> </span><span class="str">"__main__"</span><span class="pun">:</span><span class="pln">
    app</span><span class="pun">.</span><span class="pln">run</span><span class="pun">()</span></pre>

<p>
	وبعد الانتهاء نحفظ الملف ونغلقه.
</p>

<h2>
	الخطوة 4 – إعداد خادم uWSGI
</h2>

<p>
	حتى الآن كتبنا شيفرة البرنامج مع إنشاء نقطة دخول، وفي هذه الخطوة سنبدأ بإعداد خادم uWSGI.
</p>

<h3>
	اختبار خادم uWSGI
</h3>

<p>
	بدايةً وقبل إجراء مزيدٍ من التغييرات، من المفيد اختبار ما إذا كان uWSGI قادرًا على تخديم تطبيقنا هذا، وذلك بتمرير اسم نقطة الدخول المكوّن من اسم الوحدة المُستخدمة بدون اللاحقة "py." مع اسم الاستدعاء داخل التطبيق، وبالتالي في حالتنا سيكون اسم نقطة الدخول هو "wsgi:app".
</p>

<p>
	كما أنّنا سنحدّد مقبس ويب للخادم ليعمل على واجهة عامّة، مُستخدمًا بروتوكول HTTP عوضًا عن بروتوكول uwsgi الثنائي، باستخدام نفس رقم المنفذ "5000" المفتوح أصلًا:
</p>

<pre class="ipsCode">
(muprojectenv) $ uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app
</pre>

<p>
	وبالانتقال إلى رابط الخادم (عنوان IP الخاص به) متبوعًا برقم المنفذ "5000:" باستخدام المتصفح:
</p>

<pre class="ipsCode">
http://your_server_ip:5000
</pre>

<p>
	يجب أن نحصل على نفس خرج التطبيق، أي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="108086" href="https://academy.hsoub.com/uploads/monthly_2022_09/1st_test_app.png.90d4c23e8d90992b798ca19d8f62dc4d.png" rel=""><img alt="1st_test_app.png" class="ipsImage ipsImage_thumbnailed" data-fileid="108086" data-unique="3qais3dow" src="https://academy.hsoub.com/uploads/monthly_2022_09/1st_test_app.png.90d4c23e8d90992b798ca19d8f62dc4d.png"></a>
</p>

<p>
	وبعد التأكد من كون الخادم يعمل كما يجب، نضغط "CTRL +C" في نافذة الطرفية لإيقاف تشغيله.
</p>

<p>
	وفي هذه المرحلة ومع الانتهاء من إعداد البيئة الافتراضية، أصبح من الممكن إلغاء تفعيلها:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_38" style="">
<span class="pun">(</span><span class="pln">muprojectenv</span><span class="pun">)</span><span class="pln"> $ deactivate</span></pre>

<p>
	وبذلك فإنّ أي أوامر بلغة بايثون ستستخدم بيئة بايثون الموجودة فعليًا على النظام.
</p>

<h3>
	إنشاء ملف ضبط uWSGI
</h3>

<p>
	الآن وبعد ما اختبرنا وتأكدنا من قدرة uWSGI على تخديم تطبيقنا (تمكنّا من الوصول إلى خرج التطبيق من خلاله)، ولضمان عمل فعّال وموثوق للتطبيق على المدى الطويل سننشئ ملفًا ضبط uWSGI مُتضمّنًا كافّة الخيارات اللازمة لذلك.
</p>

<p>
	سننشئ هذا الملف ضمن مجلد التطبيق الرئيسي باسم "myproject.ini":
</p>

<pre class="ipsCode">
$ nano ~/myproject/myproject.ini
</pre>

<p>
	سنبدأ بداخله بالترويسة <code>[uwsgi]</code>، التي تتضمّن الإعدادات الواجب على خادم uWSGI تطبيقها، وفيها سنحدّد الوحدة المُستخدمة وهي في حالتنا "wsgi.py"، إذ نكتب اسمها دون اللاحقة، مع اسم الاستدعاء إلى uWSGI داخل التطبيق وهو "app" على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_41" style="">
<span class="pun">[</span><span class="pln">uwsgi</span><span class="pun">]</span><span class="pln">
module </span><span class="pun">=</span><span class="pln"> wsgi</span><span class="pun">:</span><span class="pln">app</span></pre>

<p>
	ومن ثمّ كتبنا الأمر اللازم لتشغيل خادم uWSGI في الوضع الرئيسي Master mode مولدًّا خمس عمليات تابعة لتلك الرئيسية لتخديم الطلبات الفعليّة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_43" style="">
<span class="pun">[</span><span class="pln">uwsgi</span><span class="pun">]</span><span class="pln">
module </span><span class="pun">=</span><span class="pln"> wsgi</span><span class="pun">:</span><span class="pln">app

master </span><span class="pun">=</span><span class="pln"> true
processes </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span></pre>

<p>
	ومن الجدير بالملاحظة أنّنا وفي مرحلة الاختبار فتحنا منفذًا مُباشرًا لخادم uWSGI، ولكن بما أنّنا سنستخدم خادم Nginx تاليًا للتعامل مع اتصالات العملاء (زوّار التطبيق) ليمرّر بدوره الطلبات إلى خادم uWSGI، وبما أن هذين المكونين يعملان على حاسوبٍ واحد، فمن المفضّل استخدام مقبس ويب يونكس Unix كونه أسرع وأكثر أمنًا، لذا سنستدعي المقبس "myproject.sock" لنضمّنه في مجلدنا هذا.
</p>

<p>
	كما سنغيّر أذونات مقبس الويب هذا بما يمنح خادم Ngnix ملكية عمليات خادم uWSGI، لذا يجب التأكّد من كون مالك المقبس لديه أذونات القراءة منه والكتابة عليه، كما سنفعّل خيار تنظيف المقبس لدى توقّف العمليات بإضافة الخيار <code>vacuum</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_45" style="">
<span class="pun">[</span><span class="pln">uwsgi</span><span class="pun">]</span><span class="pln">
module </span><span class="pun">=</span><span class="pln"> wsgi</span><span class="pun">:</span><span class="pln">app

master </span><span class="pun">=</span><span class="pln"> true
processes </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pln">

socket </span><span class="pun">=</span><span class="pln"> myproject</span><span class="pun">.</span><span class="pln">sock
chmod</span><span class="pun">-</span><span class="pln">socket </span><span class="pun">=</span><span class="pln"> </span><span class="lit">660</span><span class="pln">
vacuum </span><span class="pun">=</span><span class="pln"> true</span></pre>

<p>
	ونهايةً سنضيف خيار <code>die-on-term</code>، وبذلك نضمن أنّه لدى كل من النظام المُهيئ وخادم uWSGI نفس الافتراضات حول معنى كل عملية، وبذلك تعمل مكونات النظامين على التوازي وصولًا إلى النتيجة المطلوب تنفيذها.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_47" style="">
<span class="pun">[</span><span class="pln">uwsgi</span><span class="pun">]</span><span class="pln">
module </span><span class="pun">=</span><span class="pln"> wsgi</span><span class="pun">:</span><span class="pln">app

master </span><span class="pun">=</span><span class="pln"> true
processes </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pln">

socket </span><span class="pun">=</span><span class="pln"> myproject</span><span class="pun">.</span><span class="pln">sock
chmod</span><span class="pun">-</span><span class="pln">socket </span><span class="pun">=</span><span class="pln"> </span><span class="lit">660</span><span class="pln">
vacuum </span><span class="pun">=</span><span class="pln"> true

die</span><span class="pun">-</span><span class="pln">on</span><span class="pun">-</span><span class="pln">term </span><span class="pun">=</span><span class="pln"> true</span></pre>

<p>
	نلاحظ أنّنا في هذه الخطوة لم نُحدّد بروتوكولًا كما فعلنا في نافذة أسطر الأوامر سابقًا، ذلك لأنّ خادم uWSGI يستخدم بروتوكول uwsgi افتراضيًا، وهو بروتوكول سريع يتعامل مع <a href="https://academy.hsoub.com/programming/os-embedded-systems/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%B9%D8%AF-%D8%A7%D9%84%D8%AB%D9%86%D8%A7%D8%A6%D9%8A-binary-%D8%A3%D8%B3%D8%A7%D8%B3-%D8%A7%D9%84%D8%AD%D9%88%D8%B3%D8%A8%D8%A9-r1658/" rel="">النظام الثنائي للعد</a> (يستخدم كامل قيم البايت الواحد)، مُصمّم للتواصل مع الخوادم الأخرى، وخادم Nginx قادرٌ أصلًا على التخاطب معه، وبالتالي يُعد استخدام هذا البروتوكول أفضل من فرض الاتصال باستخدام بروتوكول HTTP.
</p>

<p>
	وعند الانتهاء نحفظ الملف ونغلقه.
</p>

<h2>
	الخطوة الخامسة - إنشاء ملف وحدة الاتصال مع النظام
</h2>

<p>
	سننشئ في هذه الخطوة ملف وحدة الاتصال التي ستسمح لنظام أوبنتو المُهيّأ أصلًا بتشغيل خادم uWSGI تلقائيًا ليخدّم التطبيق في كل مرة يُشغّل فيها الخادم الفعلي.
</p>

<p>
	سننشئ هذا الملف ضمن المسار "etc/system/system/" مع ملاحظة أنّ لاحقته هي "service." كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_49" style="">
<span class="pln">$ sudo nano </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">.</span><span class="pln">service</span></pre>

<p>
	وضمن الملف نكتب الشيفرة بدءًا من القسم <code>[Unit]</code> الذي نحدّد من خلاله البيانات الوصفية metadata ومتطلبات العمل، وفيه نصف طبيعة الخدمة ونجعل النظام الفعلي المُهيّأ يبدأ بالعمل فقط بعد إنتهاء عمليات الربط الشبكي المطلوبة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_54" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">uWSGI instance to serve myproject
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target</span></pre>

<p>
	بعدها نكتب القسم <code>[Service]</code>، وفيه نحدّد مكان تنفيذ العمليات، أي المُستخدم والمجموعة الهدف، إذ سنعطي حساب المُستخدم العادي صلاحيات المالك كونه يملك بالفعل كافّة الملفات ذات الصلة بالعمليات الجارية، كما سنعطي المجموعة <code>www-data</code> صلاحيات مالك المجموعة بما يضمن تخاطب سهل ما بين خادم Nginx وعمليات uWSGI الجارية، ومن الضروري في هذا المقطع استبدال اسم المُستخدم بالاسم الخاص بك، كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_57" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">uWSGI instance to serve myproject
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target

</span><span class="pun">[</span><span class="typ">Service</span><span class="pun">]</span><span class="pln">
</span><span class="typ">User</span><span class="pun">=</span><span class="pln">username
</span><span class="typ">Group</span><span class="pun">=</span><span class="pln">www</span><span class="pun">-</span><span class="pln">data</span></pre>

<p>
	سنحدّد الآن مجلد العمل، كما سنضبط قيمة متغير البيئة <code>PATH</code> بما يُعلِم النظام الفعلي المُهيّأ بأنّ ملفات تشغيل العمليات موجودةٌ ضمن البيئة الافتراضية، كما سنحدّد الأمر البرمجي المسؤول عن بدء تشغيل الخدمة، والذي يتطلّب تمرير المسار الكامل إلى ملف تشغيل uWSGI المُثبّت في البيئة الافتراضية، كما سنمرر اسم ملف الضبط ذو اللاحقة "ini." الذي أنشأناه أصلًا في مجلد المشروع.
</p>

<p>
	ومن المهم استبدال اسم المستخدم username ومسار المشروع بما يتوافق مع معلوماتها لديك.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_60" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">uWSGI instance to serve myproject
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target

</span><span class="pun">[</span><span class="typ">Service</span><span class="pun">]</span><span class="pln">
</span><span class="typ">User</span><span class="pun">=</span><span class="pln">username
</span><span class="typ">Group</span><span class="pun">=</span><span class="pln">www</span><span class="pun">-</span><span class="pln">data
</span><span class="typ">WorkingDirectory</span><span class="pun">=/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">username</span><span class="pun">/</span><span class="pln">myproject
</span><span class="typ">Environment</span><span class="pun">=</span><span class="str">"PATH=/home/username/myproject/myprojectenv/bin"</span><span class="pln">
</span><span class="typ">ExecStart</span><span class="pun">=/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">username</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">uwsgi </span><span class="pun">--</span><span class="pln">ini myproject</span><span class="pun">.</span><span class="pln">ini</span></pre>

<p>
	أمّا الآن فسنضيف القسم <code>[Install]</code>، الذي يُعلم النظام المُهيّأ بمكان ربط الخدمة التي ستبدأ فور إقلاع النظام، إذ أنّ المطلوب في حالتنا جعل الخدمة تبدأ فور إعداد وتشغيل نظام مُستخدمي التطبيق العاديين:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_62" style="">
<span class="pun">[</span><span class="typ">Unit</span><span class="pun">]</span><span class="pln">
</span><span class="typ">Description</span><span class="pun">=</span><span class="pln">uWSGI instance to serve myproject
</span><span class="typ">After</span><span class="pun">=</span><span class="pln">network</span><span class="pun">.</span><span class="pln">target

</span><span class="pun">[</span><span class="typ">Service</span><span class="pun">]</span><span class="pln">
</span><span class="typ">User</span><span class="pun">=</span><span class="pln">username
</span><span class="typ">Group</span><span class="pun">=</span><span class="pln">www</span><span class="pun">-</span><span class="pln">data
</span><span class="typ">WorkingDirectory</span><span class="pun">=/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">username</span><span class="pun">/</span><span class="pln">myproject
</span><span class="typ">Environment</span><span class="pun">=</span><span class="str">"PATH=/home/username/myproject/myprojectenv/bin"</span><span class="pln">
</span><span class="typ">ExecStart</span><span class="pun">=/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">username</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">uwsgi </span><span class="pun">--</span><span class="pln">ini myproject</span><span class="pun">.</span><span class="pln">ini

</span><span class="pun">[</span><span class="typ">Install</span><span class="pun">]</span><span class="pln">
</span><span class="typ">WantedBy</span><span class="pun">=</span><span class="pln">multi</span><span class="pun">-</span><span class="pln">user</span><span class="pun">.</span><span class="pln">target</span></pre>

<p>
	وبذلك يكون ملف الخدمة الخاصّ بالنظام الفعلي المُهيّأ جاهزًا. احفظ هذا الملف واغلقه.
</p>

<p>
	الآن، سنشغّل خدمة uWSGI التي أنشأناها وجعلناها تبدأ فور الإقلاع:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_64" style="">
<span class="pln">$ sudo systemctl start myproject
$ sudo systemctl enable myproject</span></pre>

<p>
	ونتحقّق من حالة عملها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_66" style="">
<span class="pln">$ sudo systemctl status myproject</span></pre>

<p>
	إذا كانت تعمل بصورةٍ صحيحة، فيجب أن نحصل على خرج شبيه بما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_68" style="">
<span class="pun">●</span><span class="pln"> myproject</span><span class="pun">.</span><span class="pln">service </span><span class="pun">-</span><span class="pln"> uWSGI instance to serve myproject
     </span><span class="typ">Loaded</span><span class="pun">:</span><span class="pln"> loaded </span><span class="pun">(/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">systemd</span><span class="pun">/</span><span class="pln">system</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">.</span><span class="pln">service</span><span class="pun">;</span><span class="pln"> enabled</span><span class="pun">;</span><span class="pln"> vendor preset</span><span class="pun">:</span><span class="pln"> enabled</span><span class="pun">)</span><span class="pln">
     </span><span class="typ">Active</span><span class="pun">:</span><span class="pln"> active </span><span class="pun">(</span><span class="pln">running</span><span class="pun">)</span><span class="pln"> since </span><span class="typ">Wed</span><span class="pln"> </span><span class="lit">2020</span><span class="pun">-</span><span class="lit">05</span><span class="pun">-</span><span class="lit">20</span><span class="pln"> </span><span class="lit">13</span><span class="pun">:</span><span class="lit">21</span><span class="pun">:</span><span class="lit">39</span><span class="pln"> UTC</span><span class="pun">;</span><span class="pln"> </span><span class="lit">8h</span><span class="pln"> ago
   </span><span class="typ">Main</span><span class="pln"> <abbr title="Process IDentifier | معرّف العملية أو البرنامج">PID</abbr></span><span class="pun">:</span><span class="pln"> </span><span class="lit">22146</span><span class="pln"> </span><span class="pun">(</span><span class="pln">uwsgi</span><span class="pun">)</span><span class="pln">
      </span><span class="typ">Tasks</span><span class="pun">:</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> </span><span class="pun">(</span><span class="pln">limit</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2345</span><span class="pun">)</span><span class="pln">
     </span><span class="typ">Memory</span><span class="pun">:</span><span class="pln"> </span><span class="lit">25.5M</span><span class="pln">
     </span><span class="typ">CGroup</span><span class="pun">:</span><span class="pln"> </span><span class="pun">/</span><span class="pln">system</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">.</span><span class="pln">service
             </span><span class="pun">├─</span><span class="lit">22146</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">uwsgi </span><span class="pun">--</span><span class="pln">ini myproject</span><span class="pun">.</span><span class="pln">ini
             </span><span class="pun">├─</span><span class="lit">22161</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">uwsgi </span><span class="pun">--</span><span class="pln">ini myproject</span><span class="pun">.</span><span class="pln">ini
             </span><span class="pun">├─</span><span class="lit">22162</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">uwsgi </span><span class="pun">--</span><span class="pln">ini myproject</span><span class="pun">.</span><span class="pln">ini
             </span><span class="pun">├─</span><span class="lit">22163</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">uwsgi </span><span class="pun">--</span><span class="pln">ini myproject</span><span class="pun">.</span><span class="pln">ini
             </span><span class="pun">├─</span><span class="lit">22164</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">uwsgi </span><span class="pun">--</span><span class="pln">ini myproject</span><span class="pun">.</span><span class="pln">ini
             </span><span class="pun">└─</span><span class="lit">22165</span><span class="pln"> </span><span class="pun">/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myprojectenv</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">uwsgi </span><span class="pun">--</span><span class="pln">ini myproject</span><span class="pun">.</span><span class="pln">ini</span></pre>

<p>
	وفي حال وجود أي رسائل أخطاء، يجب إيجاد حلول لها قبل المتابعة في الخطوات التالية، أو يمكنك الانتقال لضبط تثبيت Nginx لتمرير الطلبات إلى مقبس "myproject.sock".
</p>

<h2>
	الخطوة السادسة - ضبط Nginx ليعمل مثل خادم وكيل
</h2>

<p>
	مع بداية هذه الخطوة يجب أن يكون خادم uWSGI الخاص بالتطبيق جاهزًا وقيد التشغيل، بانتظار الطلبات الواردة إلى ملف مقبس الويب المُنشأ في مجلد المشروع، وفي هذه الخطوة سنضبط خادم Nginx ليمرر طلبات الويب إلى المقبس آنف الذكر باستخدام بروتوكول "uwsgi".
</p>

<p>
	لذا، سننشئ ملف ضبط جديد للخادم باسم "myproject" ضمن المجلد "sites-available" الخاص بخادم Nginx:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_70" style="">
<span class="pln">$ sudo nano </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">available</span><span class="pun">/</span><span class="pln">myproject</span></pre>

<p>
	وسنكتب ضمن هذا الملف الشيفرة اللازمة لجعل خادم Nginx يصغي للمنفذ الافتراضي ذو الرقم "80" بانتظار أي طلبات واردة، كما سيستخدم هذه الشيفرة أيضًا للتعامل مع طلبات الوصول عن طريق اسم مجال خادم التطبيق.
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_72" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name your_domain www</span><span class="pun">.</span><span class="pln">your_domain</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذ سنضيف مجموعة شيفرات متوافقة مع كل موقع مطلوب، وفيها سنضمّن الملف "uwsgi_params" المسؤول عن تحديد معاملات uWSGI الواجب ضبطها، ثمّ سنمرر الطلبات إلى ملف مقبس الويب المُعرّف أصلًا باستخدام الموجّه <code>uwsgi_pass</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_74" style="">
<span class="pln">server </span><span class="pun">{</span><span class="pln">
    listen </span><span class="lit">80</span><span class="pun">;</span><span class="pln">
    server_name your_domain www</span><span class="pun">.</span><span class="pln">your_domain</span><span class="pun">;</span><span class="pln">

    location </span><span class="pun">/</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        include uwsgi_params</span><span class="pun">;</span><span class="pln">
        uwsgi_pass unix</span><span class="pun">:/</span><span class="pln">home</span><span class="pun">/</span><span class="pln">sammy</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">/</span><span class="pln">myproject</span><span class="pun">.</span><span class="pln">sock</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نحفظ الملف ونغلقه بعد الانتهاء.
</p>

<p>
	ولتمكين ملف كتلة إعدادات خادم Nginx هذا، سنربطه مع المجلد "sites-enabled" كما يلي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_76" style="">
<span class="pln">$ sudo ln </span><span class="pun">-</span><span class="pln">s </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">available</span><span class="pun">/</span><span class="pln">myproject </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">enabled</span></pre>

<p>
	عند تثبيت مخدم Nginx، يجري إعداد ملف ضبط للمخدم اسمه "default" ضمن المجلد "sites-available"، ثم يُنشأ ارتباط رمزي بين هذا الملف والمجلد "sites-available"؛ فإذا تركت هذا الارتباط الرمزي مكانه، فسيمنع الضبط الافتراضي "default" موقعك من التحميل.
</p>

<p>
	يمكنك إزالة هذا الرابط باستخدام الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_78" style="">
<span class="pln">$ sudo unlink </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">nginx</span><span class="pun">/</span><span class="pln">sites</span><span class="pun">-</span><span class="pln">enabled</span><span class="pun">/</span><span class="pln">default</span></pre>

<p>
	وسنتمكّن مع وجود الملف ضمن المجلد من فحص أي أخطاء في الصياغة البرمجية باستخدام الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_80" style="">
<span class="pln">$ sudo nginx </span><span class="pun">-</span><span class="pln">t</span></pre>

<p>
	وفي حال عدم ظهور أي أخطاء (أي قيمة معادة لدى تنفيذ الشيفرة السابقة)، نعيد تشغيل عملية Nginx لتتعرّف على الضبط الجديد:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_82" style="">
<span class="pln">$ sudo systemctl restart nginx</span></pre>

<p>
	والآن أصبح من الممكن تعديل جدار الحماية مُجدّدًا، إذ أنّنا لم نعد بحاجة إلى الوصول عبر المنفذ "5000"، لذا سنحذف هذا الاستثناء:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_84" style="">
<span class="pln">$ sudo ufw delete allow </span><span class="lit">5000</span></pre>

<p>
	ثمّ سنسمح بالوصول إلى خادم Nginx على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_86" style="">
<span class="pln">$ sudo ufw allow </span><span class="str">'Nginx Full'</span></pre>

<p>
	وبذلك يصبح من الممكن الوصول إلى التطبيق باستخدام اسم المجال الخاص بالخادم عبر المتصفح:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_89" style="">
<span class="pln">http</span><span class="pun">://</span><span class="pln">your_domain</span></pre>

<p>
	فيظهر لنا خرج التطبيق بالشّكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="108086" href="https://academy.hsoub.com/uploads/monthly_2022_09/1st_test_app.png.90d4c23e8d90992b798ca19d8f62dc4d.png" rel=""><img alt="1st_test_app.png" class="ipsImage ipsImage_thumbnailed" data-fileid="108086" data-unique="3qais3dow" src="https://academy.hsoub.com/uploads/monthly_2022_09/1st_test_app.png.90d4c23e8d90992b798ca19d8f62dc4d.png"></a>
</p>

<p>
	أمّا إذا واجهت أي أخطاء، فعليك التحقّق منها مُستخدمًا الأوامر التالية:
</p>

<ul>
<li>
		<code>sudo less /var/log/nginx/error.log</code>: للتحقّق من سجل أخطاء خادم Nginx.
	</li>
	<li>
		<code>sudo less /var/log/nginx/access.log</code>: للتحقّق من سجل الوصول إلى خادم Nginx.
	</li>
	<li>
		<code>sudo journalctl -u nginx</code>: للتحقّق من سجل عمليات خادم Nginx.
	</li>
	<li>
		<code>sudo journalctl -u myproject</code>: للتحقّق من سجل خادم uWSGI الخاص بتطبيق فلاسك.
	</li>
</ul>
<h2>
	الخطوة السابعة - تأمين التطبيق
</h2>

<p>
	لضمان اتصال ونقل بيانات آمن ومُشفّر من وإلى خادم التطبيق، لا بُدّ من الحصول على شهادة طبقة مقابس الويب الآمنة Secure Socket Layer -أو اختصارًا <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr>-؛ وهي شهادةٌ رقميةٌ تصادق على هوية موقع الويب وتتيح اتصالاً مشفرًا، ومن الممكن الحصول على هذه الشهادة مجانًا من <a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81-%D8%AA%D8%A4%D9%85%D9%91%D9%86-%D8%AE%D8%A7%D8%AF%D9%85-%D9%88%D9%8A%D8%A8-nginx-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-1604-r365/" rel="">Let's Encypt</a> (وهي هيئة شهادات مجانية تابعة لمجموعة أبحاث أمن الإنترنت)، أو إنشاء شهادة خاصّة بك، ناهيك عن وجود طرق عديدة للحصول على الشهادة.
</p>

<p>
	لذا بدايةً سنثبّت حزمة Certbot وإضافات Nginx الخاصة به باستخدام <code>apt</code>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_91" style="">
<span class="pln">$ sudo apt install certbot python3</span><span class="pun">-</span><span class="pln">certbot</span><span class="pun">-</span><span class="pln">nginx</span></pre>

<p>
	يؤمن Certbot عدة طرق مختلفة <a href="https://academy.hsoub.com/devops/servers/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-%D8%B4%D9%87%D8%A7%D8%AF%D8%A9-ssl-%D9%85%D9%86-%D8%B3%D9%84%D8%B7%D8%A9-%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%AA%D8%AC%D8%A7%D8%B1%D9%8A%D8%A9-%D8%A7%D9%84%D8%AD%D8%B5%D9%88%D9%84-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A9-%D9%88%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA%D9%87%D8%A7-r148/" rel="">للحصول على شهادة <abbr title="Secure Socket Layer | طبقة المنافذ الآمنة">SSL</abbr></a> من خلال هذه الإضافات plugins، وبعد تثبيت إحداها ستعمل إضافة Nginx من تلقاء نفسها على إعادة ضبط إعدادات الخادم وإعادة تحميلها كلما دعت الحاجة لذلك، ولاستخدام هذه الإضافة سنشغّل الأمر:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_94" style="">
<span class="pln">$ sudo certbot </span><span class="pun">--</span><span class="pln">nginx </span><span class="pun">-</span><span class="pln">d your_domain </span><span class="pun">-</span><span class="pln">d www</span><span class="pun">.</span><span class="pln">your_domain</span></pre>

<p>
	تشغّل الشيفرة السابقة <code>certbot</code> مع إضافة <code>nginx–</code>، وقد استخدمنا <code>d-</code> لتحديد الأسماء المتوافقة مع الشهادة.
</p>

<p>
	إذا كانت هذه المرة الأولى التي تشغّل فيها <code>certbot</code> على الخادم، سيتطلّب الأمر إدخال بريد إلكتروني والموافقة على شروط الخدمة، بعدها ستتواصل <code>certbot</code> مع خادم Let’s Encrypt، ثم تشغّل نظام تحقّق للتأكد من أنّك تتحكم فعلًا بالنطاق الذي تطلب الشهادة من أجله.
</p>

<p>
	فإذا انتهت العملية السابقة بنجاح، سيطلب منا <code>certbot</code> ضبط الإعدادات التي نريد لبروتوكول HTTPS:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_96" style="">
<span class="typ">Please</span><span class="pln"> choose whether </span><span class="kwd">or</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> to redirect HTTP traffic to HTTPS</span><span class="pun">,</span><span class="pln"> removing HTTP access</span><span class="pun">.</span><span class="pln">
</span><span class="pun">-------------------------------------------------------------------------------</span><span class="pln">
</span><span class="lit">1</span><span class="pun">:</span><span class="pln"> </span><span class="typ">No</span><span class="pln"> redirect </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Make</span><span class="pln"> no further changes to the webserver configuration</span><span class="pun">.</span><span class="pln">
</span><span class="lit">2</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Redirect</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Make</span><span class="pln"> all requests redirect to secure HTTPS access</span><span class="pun">.</span><span class="pln"> </span><span class="typ">Choose</span><span class="pln"> this </span><span class="kwd">for</span><span class="pln">
new sites</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">or</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> you</span><span class="str">'re confident your site works on HTTPS. You can undo this
change by editing your web server'</span><span class="pln">s configuration</span><span class="pun">.</span><span class="pln">
</span><span class="pun">-------------------------------------------------------------------------------</span><span class="pln">
</span><span class="typ">Select</span><span class="pln"> the appropriate number </span><span class="pun">[</span><span class="lit">1</span><span class="pun">-</span><span class="lit">2</span><span class="pun">]</span><span class="pln"> then </span><span class="pun">[</span><span class="pln">enter</span><span class="pun">]</span><span class="pln"> </span><span class="pun">(</span><span class="pln">press </span><span class="str">'c'</span><span class="pln"> to cancel</span><span class="pun">):</span></pre>

<p>
	في هذه الخطوة نختار ما نريده من إعدادات ثمّ نضغط على مفتاح "ENTER" لتحديث الضبط، كما يُعاد تحميل Nginx لتطبيق الإعدادات الجديدة هذه، ونهايةً يعرض <code>certbot</code> رسالةً مفادها نجاح العملية وموقع تخزين الشهادة:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_99" style="">
<span class="pln">IMPORTANT NOTES</span><span class="pun">:</span><span class="pln">
 </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Congratulations</span><span class="pun">!</span><span class="pln"> </span><span class="typ">Your</span><span class="pln"> certificate </span><span class="kwd">and</span><span class="pln"> chain have been saved at</span><span class="pun">:</span><span class="pln">
   </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">/</span><span class="pln">fullchain</span><span class="pun">.</span><span class="pln">pem
   </span><span class="typ">Your</span><span class="pln"> key file has been saved at</span><span class="pun">:</span><span class="pln">
   </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">/</span><span class="pln">live</span><span class="pun">/</span><span class="pln">your_domain</span><span class="pun">/</span><span class="pln">privkey</span><span class="pun">.</span><span class="pln">pem
   </span><span class="typ">Your</span><span class="pln"> cert will expire on </span><span class="lit">2020</span><span class="pun">-</span><span class="lit">08</span><span class="pun">-</span><span class="lit">18.</span><span class="pln"> </span><span class="typ">To</span><span class="pln"> obtain a new </span><span class="kwd">or</span><span class="pln"> tweaked
   version of this certificate </span><span class="kwd">in</span><span class="pln"> the future</span><span class="pun">,</span><span class="pln"> simply run certbot again
   </span><span class="kwd">with</span><span class="pln"> the </span><span class="str">"certonly"</span><span class="pln"> option</span><span class="pun">.</span><span class="pln"> </span><span class="typ">To</span><span class="pln"> non</span><span class="pun">-</span><span class="pln">interactively renew </span><span class="pun">*</span><span class="pln">all</span><span class="pun">*</span><span class="pln"> of
   your certificates</span><span class="pun">,</span><span class="pln"> run </span><span class="str">"certbot renew"</span><span class="pln">
 </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Your</span><span class="pln"> account credentials have been saved </span><span class="kwd">in</span><span class="pln"> your </span><span class="typ">Certbot</span><span class="pln">
   configuration directory at </span><span class="pun">/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">letsencrypt</span><span class="pun">.</span><span class="pln"> </span><span class="typ">You</span><span class="pln"> should make a
   secure backup of this folder now</span><span class="pun">.</span><span class="pln"> </span><span class="typ">This</span><span class="pln"> configuration directory will
   also contain certificates </span><span class="kwd">and</span><span class="pln"> private keys obtained by </span><span class="typ">Certbot</span><span class="pln"> so
   making regular backups of this folder </span><span class="kwd">is</span><span class="pln"> ideal</span><span class="pun">.</span><span class="pln">
 </span><span class="pun">-</span><span class="pln"> </span><span class="typ">If</span><span class="pln"> you like </span><span class="typ">Certbot</span><span class="pun">,</span><span class="pln"> please consider supporting our work by</span><span class="pun">:</span><span class="pln">

   </span><span class="typ">Donating</span><span class="pln"> to ISRG </span><span class="pun">/</span><span class="pln"> </span><span class="typ">Let</span><span class="str">'s Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le</span></pre>

<p>
	وإذا اتبعنا تعليمات تثبيت Nginx كما هو موضّح في فقرة مستلزمات العمل من هذا المقال، فلن نعود بحاجة لسماحية ملف تعريف <a href="https://academy.hsoub.com/programming/general/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-http-r73/" rel="">HTTP</a>:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_101" style="">
<span class="pln">$ sudo ufw delete allow </span><span class="str">'Nginx HTTP'</span></pre>

<p>
	الآن وللتحقّق من الإعدادات سننتقل مجدّدًا إلى النطاق الخاص بالتطبيق باستخدام "//:https":
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_8594_104" style="">
<span class="pln">https</span><span class="pun">://</span><span class="pln">your_domain</span></pre>

<p>
	وهنا يجب أن يظهر خرج التطبيق مجدّدًا ولكن مع مؤشر الأمان الخاص بالمتصفح والذي يشير إلى أنّ الموقع آمن.
</p>

<h1>
	الخاتمة
</h1>

<p>
	في هذا المقال أنشأنا تطبيق فلاسك صغير آمن باستخدام بيئة بايثون افتراضية، وأنشأنا نقطة دخول إلى بروتوكول WSGI بحيث يتمكّن أي خادم تطبيق متوافق مع WSGI من التعامل معه، إذ أعددنا خادم uWSGI ليؤدي هذه المهمة، بعدها أنشأنا ملف تخديم مسؤول عن الوصول آليًا خادم التطبيق لدى إقلاع الخادم الفعلي، كما أنشأنا كتلة خادم Nginx التي تمرّر حركة مرور مستخدم الويب إلى خادم التطبيق، وبالتالي تُرحل طلبات مستخدمي التطبيق الخارجية وحركة المرور الآمنة إلى الخادم مع خادم Let's Encrypt.
</p>

<p>
	وبذلك نجد أنّ فلاسك إطار عمل مرن جدًا، إذ يمنح التطبيقات القدرة على العمل الفعلي دون التقيد بالهيكل والتصميم، ويمكنك استخدام ما ذُكر في هذا المقال لتخديم أي تطبيق فلاسك ترغب بتصميمه.
</p>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-uwsgi-and-nginx-on-ubuntu-20-04" rel="external nofollow">How To Serve Flask Applications with uWSGI and Nginx on Ubuntu 20.04</a> لصاحبه Mark Drake و Kathleen Juell.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال التالي: <a href="https://academy.hsoub.com/programming/python/flask/%D8%AA%D8%B9%D9%84%D9%85-%D8%A8%D9%86%D8%A7%D8%A1-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D8%A7%D9%84%D8%A5%D9%84%D9%83%D8%AA%D8%B1%D9%88%D9%86%D9%8A-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-flask-%D8%A8%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-r1715/" rel="">تعلم بناء موقعك الإلكتروني الأول باستخدام إطار عمل فلاسك Flask بلغة بايثون</a>
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/python/flask/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D9%85%D8%A7%D8%B1%D9%83%D8%AF%D8%A7%D9%88%D9%86-%D9%85%D8%B9-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%B9%D9%85%D9%84-%D9%81%D9%84%D8%A7%D8%B3%D9%83-%D9%88%D9%85%D8%AD%D8%B1%D9%83-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-sqlite-r1714/" rel="">استخدام مكتبة بايثون-ماركداون مع إطار عمل فلاسك ومحرك قواعد البيانات SQLite</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/python/flask/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A5%D8%B7%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-flask-r333/" rel="">مدخل إلى تطوير تطبيقات الويب باستخدام إطار العمل Flask</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%83%D9%8A%D9%81-%D8%AA%D9%8F%D8%B1%D9%82%D9%91%D9%90%D9%8A-%D8%AE%D8%A7%D8%AF%D9%85-nginx-%D9%85%D9%88%D8%AC%D9%88%D8%AF-%D8%A8%D8%AF%D9%88%D9%86-%D9%82%D8%B7%D8%B9-%D8%A7%D8%AA%D8%B5%D8%A7%D9%84%D8%A7%D8%AA-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-r445/" rel="">كيف تُرقِّي خادم Nginx موجود بدون قطع اتصالات العميل</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/web/nginx/%D9%81%D9%87%D9%85-%D8%A8%D9%86%D9%8A%D8%A9-%D9%85%D9%84%D9%81-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-nginx-%D9%88%D8%B3%D9%8A%D8%A7%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D8%A7%D8%AA-r112/" rel="">فهم بنية ملف إعدادات nginx وسياقات الإعدادات</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/web/nginx/%D8%AA%D9%86%D8%B5%D9%8A%D8%A8%D8%8C-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-nginx-%D9%83%D8%AE%D8%A7%D8%AF%D9%88%D9%85-%D9%88%D9%8A%D8%A8-r1/" rel="">تنصيب، إعداد واستخدام nginx كخادوم ويب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">649</guid><pubDate>Sat, 24 Sep 2022 17:00:00 +0000</pubDate></item><item><title>&#x646;&#x634;&#x631; &#x625;&#x635;&#x62F;&#x627;&#x631;&#x627;&#x62A; &#x627;&#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x628;&#x623;&#x645;&#x627;&#x646; &#x641;&#x64A; &#x645;&#x646;&#x638;&#x648;&#x645;&#x629; &#x627;&#x644;&#x62A;&#x643;&#x627;&#x645;&#x644; &#x648;&#x627;&#x644;&#x62A;&#x633;&#x644;&#x64A;&#x645; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631;</title><link>https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A8%D8%A3%D9%85%D8%A7%D9%86-%D9%81%D9%8A-%D9%85%D9%86%D8%B8%D9%88%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r647/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_08/Keeping-Green.png.f5859df87e5e26d68a007469618f98a0.png" /></p>

<p>
	ينبغي أن يبقى الفرع الرئيسي دائمًا في المنطقة الخضراء، ويعني ذلك تنفيذ كل خطوات بناء خط الإنتاج بنجاح، إذ يجب أن يُبنى المشروع بنجاح، وأن تُنفَّذ الاختبارات دون أخطاء، ولا يجب أن يعترض المدقق على أي شيء.
</p>

<p>
	ما أهمية هذا الأمر؟ من المرجح أنك ستنشر شيفرتك إلى العالم الخارجي من الفرع الرئيسي تحديدًا، وأي إخفاق في هذا الفرع سيعني أنّ الميزات الجديدة لن تُنشر حتى تُحل المشكلة؛ فقد تكتشف أحيانًا ثغرات في نسخة الإنتاج لم يلتقطها خط الإنتاج لمنظومة التكامل المستمر CI/CD، وفي حالات كهذه، سترغب في التراجع إلى النسخة السابقة بطريقة آمنة.
</p>

<p>
	كيف ستُبقي فرعك الرئيسي في المنطقة الخضراء إذًا؟ تحاشى دفع أية تغييرات إلى الفرع الرئيسي مباشرةً، بل ادفع شيفرتك إلى فرع آخر يمثّل أحدث نسخة ممكنة للفرع الرئيسي، وعندما ترى أن هذا الفرع جاهزٌ للدمج مع الرئيسي، أنشئ <a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B7%D9%84%D8%A8-%D8%B3%D8%AD%D8%A8-%D8%B9%D9%84%D9%89-github-r1581/" rel="">طلب سحب Pull Request</a> -أو اختصارًا PR- على غيت هب GitHub.
</p>

<h2>
	العمل مع طلبات السحب
</h2>

<p>
	تمثل طلبات السحب جزءًا جوهريًا من عملية التعاون في تطوير أي مشروع برمجي يُنفَّذه مساهمَين على الأقل، فعندما تُجري أية تعديلات على مشروعك، ستختبرها على جهازك أولًا، ثم تعتمد هذه التغييرات وتدفعها إلى مستودع على الإنترنت -وهو غيت هب GitHub في حالتنا-، ثم تقدم طلب سحب لكي يراجع أحدٌ ما هذه التغييرات قبل أن تُدمج مع الفرع الرئيسي.
</p>

<p>
	هناك عدة أسباب تدفعنا لاستخدام طلبات السحب، كما أنّ مراجعة الشيفرة من قبل شخص آخر على الأقل فكرةٌ جيدةٌ دائمًا.
</p>

<ul>
<li>
		قد تتواجد بعض الثغرات حتى في شيفرة المطورين الخبراء، وهذا مشابهٌ لمشكلة الرؤية النفقية tunnel، إذ لا يستطيع المصاب بها سوى رؤية ما أمامه مباشرة وكأنه ينظر من خلال نفق.
	</li>
	<li>
		يمتلك المراجع منظورًا مختلفًا، فقد يقدم وجهة نظر مختلفة.
	</li>
	<li>
		سيكون هناك مطورٌ واحد على الأقل على دراية بالتغيرات التي أجريتها بعد قراءتها.
	</li>
	<li>
		ستسمح لك طلبات السحب أن تُنفِّذ تلقائيًا كل المهام في خط الإنتاج لمنظومة CI قبل أن تصل الشيفرة إلى الفرع الرئيسي، وستزودك GitHub Actions بآلية لتنفيذ طلبات السحب.
	</li>
</ul>
<p>
	يمكنك تهيئة مستودع GitHub بطريقة تمنع دمج الشيفرة المرتبطة بطلب سحب إلى أن يُوافق عليها.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105728" href="https://academy.hsoub.com/uploads/monthly_2022_08/pull_request_for_repository_01.png.5faf99764e2b83602b8872fbde28ae14.png" rel=""><img alt="pull_request_for_repository_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105728" data-unique="ucfnlvbia" src="https://academy.hsoub.com/uploads/monthly_2022_08/pull_request_for_repository_01.thumb.png.8fa9c5e6ffea1e5813335f2aaa3182fb.png" style="width: 720px; height: auto;"></a>
</p>

<p>
	لتقديم طلب سحب جديد، افتح فرعك في GitHub بالضغط على الزر الأخضر "Compare &amp; pull request" في الأعلى، وسيظهر لك نموذج لتعبئته بوصفٍ لطلب السحب الذي تقدمه.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105727" href="https://academy.hsoub.com/uploads/monthly_2022_08/pull_request_form_02.png.240ba500cf45136e68d6778ebda070bf.png" rel=""><img alt="pull_request_form_02.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105727" data-unique="qbzypkxbq" src="https://academy.hsoub.com/uploads/monthly_2022_08/pull_request_form_02.png.240ba500cf45136e68d6778ebda070bf.png" style="width: 720px; height: auto;"></a>
</p>

<p>
	تقدم لك واجهة طلبات السحب في GitHub إمكانية وصف الطلب ومناقشته، حيث تَظهر لك في الأسفل جميع علامات التحقق من منظومة CI (وهي في حالتنا كل فعل من أفعال <a href="https://academy.hsoub.com/devops/deployment/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-github-actions-%D9%84%D8%AA%D8%AD%D9%82%D9%8A%D9%82-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r645/" rel="">GitHub Actions</a> التي نستخدمها) التي هُيِّئت للعمل عند كل طلب سحب وحالة هذه العلامات. وما نهدف إليه فعلًا هو لوحة خضراء. يمكنك النقر على تفاصيل "Details" كل علامة تحقق للاطلاع عليها وتشغيل السجلات.
</p>

<p>
	تُنفَّذ جميع مخططات العمل التي واجهناها حتى الآن عند دفع الشيفرة إلى الفرع الرئيسي، ولجعلها تعمل عند كل طلب سحب، لا بدّ من تحديث الجزء المسؤول عن مسببات triggers إقلاع المخطط. سنستخدم المسبب "pull_request" للفرع الرئيسي ونربط هذا المسبب بالحدثين "opened"و "synchronize"؛ ويعني هذا مبدئيًا أن المخطط سيُنفَّذ عندما يُقدَّم طلب سحب إلى الفرع الرئيسي، أو عندما يُحدّث.
</p>

<p>
	لنغيّر إذًا الأحداث التي <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows" rel="external nofollow">تسبب</a> تشغيل مخطط العمل على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_5039_14" style="">
<span class="pln">on</span><span class="pun">:</span><span class="pln">
  push</span><span class="pun">:</span><span class="pln">
    branches</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> master
  pull_request</span><span class="pun">:</span><span class="pln">    
    branches</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">master</span><span class="pun">]</span><span class="pln">    
    types</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">opened</span><span class="pun">,</span><span class="pln"> synchronize</span><span class="pun">]</span></pre>

<p>
	سنمنع قريبًا دفع الشيفرة مباشرةً إلى الفرع الرئيسي، لكننا سننفذ حاليًا مخطط العمل لكل عمليات دفع الشيفرة المباشر إلى الفرع الرئيسي.
</p>

<h2>
	التمرينان 11.13 - 11.14
</h2>

<p>
	يعمل مخطط العمل على النحو المطلوب للحفاظ على نوعية جيدة لشيفرتنا، لكن طالما أنه يُقلع عند دفع الشيفرة إلى الفرع الرئيسي، فسيلتقط الأخطاء متأخرًا.
</p>

<h3>
	11.13: طلب سحب
</h3>

<p>
	حَدِّث مسببات إقلاع مخطط العمل كما اقترحنا سابقًا ليعمل عند تقديم طلب سحب جديد إلى الفرع الرئيسي.
</p>

<p>
	أنشئ فرعًا جديدًا وادفع بشيفرتك إليه، ثم افتح طلب سحب إلى الفرع الرئيسي.
</p>

<p>
	إذا لم تكن قد تعاملت مع الفروع سابقًا اطلع على المقالات التالية:
</p>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D8%A7%D9%84%D8%AA%D9%81%D8%B1%D9%8A%D8%B9-branching-%D9%81%D9%8A-git-r271/" rel="">مقدمة عن التفريع Branching في Git</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AA%D9%81%D8%B1%D9%8A%D8%B9%D8%A7%D8%AA-branches-%D9%81%D9%8A-git-%D9%88%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D9%84%D8%AA%D8%AE%D8%B7%D9%8A%D8%B7-%D8%B3%D9%8A%D8%B1-%D8%A7%D9%84%D8%B9%D9%85%D9%84-r287/" rel="">إدارة التفريعات (branches) في Git واستخدامها لتخطيط سير العمل</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D8%B9%D8%A7%D8%AF%D8%A9-%D8%AA%D8%A3%D8%B3%D9%8A%D8%B3-%D8%AA%D9%81%D8%B1%D9%8A%D8%B9%D8%A7%D8%AA-%D8%B7%D9%84%D8%A8-%D8%A7%D9%84%D8%B3%D8%AD%D8%A8-%D9%88%D8%AA%D8%AD%D8%AF%D9%8A%D8%AB%D9%87-%D9%81%D9%8A-git-r1584/" rel="">إعادة تأسيس تفريعات طلب السحب وتحديثه في git</a>
	</li>
</ul>
<p>
	<strong>تنبيه</strong>: تأكد عندما تفتح طلب سحب جديد أنك اخترت مستودعك الخاص واجهةً أساسية، إذ سيكون الاختيار افتراضيًا للمستودع الأصلي ولا يجب أن تفعل هذا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105726" href="https://academy.hsoub.com/uploads/monthly_2022_08/destination_base_repository_03.png.c9b63b1da181f3aa761f8c27f84230ff.png" rel=""><img alt="destination_base_repository_03.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105726" data-unique="quu3mld2z" src="https://academy.hsoub.com/uploads/monthly_2022_08/destination_base_repository_03.png.c9b63b1da181f3aa761f8c27f84230ff.png" style="width: 720px; height: auto;"></a>
</p>

<p>
	سترى في النافذة "Conversation" ضمن واجهة فتح طلب سحب آخر عملية (أو عمليات) دفع للشيفرة، كما سترى الحالة الصفراء لعلامات التحقق الذي يجري تنفيذه:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105729" href="https://academy.hsoub.com/uploads/monthly_2022_08/pull_request_interface_04.png.90fabe72a1d92bfcb5fdd1217b1ec54a.png" rel=""><img alt="pull_request_interface_04.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105729" data-unique="uizuz4jic" src="https://academy.hsoub.com/uploads/monthly_2022_08/pull_request_interface_04.thumb.png.239f3a40530d9e292ac39277779a9f38.png" style="width: 720px; height: auto;"></a>
</p>

<p>
	ينبغي أن تكون العلامات باللون الأخضر بعد أن تنتهي عمليات التحقق. تأكد من نجاح كل اختبارات التحقق، ولا تدمج فرعك الآن، فهناك نقاط ينبغي تحسينها في خط الإنتاج.
</p>

<h3>
	11.14: تشغيل خطوة النشر للفرع الرئيسي فقط
</h3>

<p>
	كل شيء يبدو على ما يرام، لكن هناك مشكلةٌ حقيقية في مخطط العمل الحالي، إذ ستُنفَّذ جميع الخطوات بما فيها خطوة النشر عندما نفتح طلب سحب، وبالتأكيد لا نريد حدوث هذا الأمر.
</p>

<p>
	ولحسن الحظ، هناك حلٌ بسيط لهذه المشكلة، إذ يمكننا استخدام العبارة الشرطية <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif" rel="external nofollow">if</a> في خطوة النشر لكي تضمن عدم تنفيذ هذه الخطوة إن لم تُدمج الشيفرة أو تُدفع إلى الفرع الرئيسي.
</p>

<p>
	يتضمن سياق <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#contexts" rel="external nofollow">context</a> شتى أنواع المعلومات حول الشيفرة التي ينفذها مخطط العمل. ستجد المعلومات المتعلقة بالموضوع في <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#github-context" rel="external nofollow">GitHub context</a>، إذ يمثل الحقل <code>event_name</code> اسم الحدث الذي يسبب تنفيذ المخطط.
</p>

<p>
	عندما يُدمج طلب سحب، سيظهر اسم الحدث على أنه "push"، وهو نفسه الحدث الذي يقع عندما تُدفع الشيفرة إلى المستودع، لذلك سنحصل على السلوك المطلوب بإضافة العبارة الشرطية التالية إلى خطوة النشر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5039_18" style="">
<span class="kwd">if</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{{</span><span class="pln"> github</span><span class="pun">.</span><span class="pln">event_name </span><span class="pun">==</span><span class="pln"> </span><span class="str">'push'</span><span class="pln"> </span><span class="pun">}}</span></pre>

<p>
	ادفع بشيفرة إضافية إلى فرعك، وتأكد من عدم تنفيذ خطوة النشر. ادمج بعد ذلك فرعك مع الفرع الرئيسي وتأكد من حدوث خطوة النشر.
</p>

<h2>
	إدارة إصدار نسخ البرمجيات
</h2>

<p>
	الغاية الرئيسية من تحديد الإصدار هي التعريف الفريد للبرمجية التي تعمل والشيفرة المتعلقة بها.
</p>

<p>
	يشكّل ترتيب الإصدارات أيضًا جزءًا مهمًا من المعلومات، فلو احتوى الإصدار الحالي لبرنامج ما، على سبيل المثال وظيفةً حيويةً معطلة، سيلزمنا تحديد النسخة السابقة المستقرة من البرنامج لكي نتمكن من التراجع إليها.
</p>

<h3>
	إدارة الإصدار بطريقة Semantic وبطريقة Hash
</h3>

<p>
	تُدعى الطريقة التي يُدار بها إصدار برنامج في بعض الأحيان باستراتيجية إدارة الإصدار Versioning strategy. سنطلّع ونوازن استراتيجيتين في هذا المجال، تدعى الأولى <a href="https://semver.org/lang/ar/" rel="external nofollow">الإدارة الدلالية لنُسخ البرمجيات Semantic</a>، إذ يُكتب الإصدار بالشكل التالي: <code>{major}.{minor}.{patch}</code>.
</p>

<p>
	فلو كان إصدار نسخة من البرنامج <code>1.2.3</code>، سيكون <code>1</code> هو رقم الإصدار الرئيسي الجذري major و <code>2</code> رقم الإصدار الثانوي (البسيط) minor و <code>3</code> هو رقم الإصدار الترميمي (الترقيع) patch.
</p>

<p>
	وعمومًا، فأية تغييرات تهدف لمعالجة الوظيفة المخفقة في إصدار دون المساس بالطريقة التي يعمل بها التطبيق من الخارج ستكون تغييرات ترميمية؛ أما التغييرات التي تؤدي إلى تغييرات بسيطة في الوظيفة (كما تبدو من الخارج) فستكون تغييرات ثانوية، وندعو تلك التغيرات التي ستؤثر كليًا على التطبيق (أو تغيير وظائفه بصورةٍ كبيرة) بالتغييرات الرئيسية، وقد يختلف تعريف هذه المصطلحات من مشروع لآخر.
</p>

<p>
	تتبع على سبيل المثال مكتبات npm ترميز semantic، ففي وقت كتابة هذه السطر (الثالث من آذار 2022)، يُعد الإصدار <a href="https://reactjs.org/versions/" rel="external nofollow">17.0.2</a> هو الأحدث للمكتبة <a href="https://academy.hsoub.com/programming/javascript/react/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-react-r1072/" rel="">React</a> إذ أن رقم الإصدار الرئيسي هو 17 والثانوي 0 والترميمي 1.
</p>

<p>
	أما الإصدار بترميز Hash (أو الترميز SHA) فهو مختلفٌ تمامًا، فرقم الإصدار هو سلسلة نصية عشوائية مشتقة من محتويات المستودع والتغيرات التي اعتمدت فيه. يُنفّذ ذلك في git تلقائيًا مثل رمز Hash معتمد وفريد لأي مجموعة من التغييرات.
</p>

<p>
	يستخدم الإصدار برمز غالبًا في الحالات المؤتمتة، فعملية نقل رمز Hash مكون من 32 محرف للتأكد من نشر كل شيء على النحو الصحيح عمليةٌ مضنية وتولّد الكثير من الأخطاء.
</p>

<h3>
	إلى ماذا يشير الإصدار؟
</h3>

<p>
	تحديد الشيفرة الموجودة في كل إصدار أمرٌ مهمٌ جدًا، ويختلف أسلوبي الترميز السابقين في طريقة فعل ذلك؛ فالعملية في Hash (على الأقل في غيت هب GitHub) بسيطة ببساطة البحث عن تغييرات معتمدة للشيفرة commit لها رمز Hash معرّف ومحدد، إذ سيمنحنا ذلك إمكانية معرفة الشيفرة التي نُشرت في كل إصدار.
</p>

<p>
	سيتعقد الوضع قليلُا في semantic، فهناك ثلاث طرق لمقاربة الموضوع: الأولى تتعلق بالشيفرة نفسها، والثانية تتعلق بالمستودع أو البيانات الوصفية له، والثالثة تتعلق بأشياء خارج المستودع كليًا.
</p>

<p>
	طالما أننا لن نتطرق إلى المقاربة الثالثة (لأنها متشعبة مثل حجر الأرنب)، فستلاحظ أن العملية بسيطة أيضًا وكأنها جدول بيانات يربط بين الإصدار والشيفرة المعتمدة التي يدل عليها.
</p>

<p>
	تتلخص المقاربة المتعلقة بالشيفرة برقم إصدار النسخة في ملف، بينما تعتمد مقاربة المستودع وبياناته الوصفية على الوسوم tags أو في حالة GitHub على الإصدارات، إذ يشير الوسم أو الإصدار في هذه المقاربة إلى شيفرة مُعتمدة هي نفسها الموجودة في الإصدار.
</p>

<h3>
	ترتيب إصدارات النسخ
</h3>

<p>
	من السهل في semantic ترتيب الإصدارات حتى لو كانت مختلفة (رئيسي، ثانوي، ترميمي)، فسيأتي الإصدار 1.3.7 قبل 2.0.0 والذي سيأتي قبل 2.1.5 وهذا الأخير سيكون قبل 2.2.0. لكننا سنحتاج إلى قائمة بالإصدارت أو الوسوم ( يؤمنها مدير حزم GitHub بصورةٍ ملائمة) لمعرفة الإصدار الأخير، فمن الأسهل الاطلاع على هذه القائمة ومناقشتها، أي من الأسهل أن نقول أننا سنتراجع إلى الإصدار 3.2.4 بدلًا من التعامل بأنفسنا مع رموز hash التي تشير إلى رقم الإصدار.
</p>

<p>
	وطبعًا لا نعني إطلاقًا أنّ رموز hash غير ملائمة؛ فلو عرفت أي شيفرة معتمدة سببت مشكلة بعينها، فمن السهل البحث من خلال تاريخ عمليات GitHub والحصول على رموز hash للإصدار السابق؛ لكن لو كانت لديك سلسلتين من رموز Hash على النحو التالي مثلًا:
</p>

<ul>
<li>
		<code>d052aa41edfb4a7671c974c5901f4abe1c2db071</code>
	</li>
	<li>
		<code>12c6f6738a18154cb1cef7cf0607a681f72eaff3</code>
	</li>
</ul>
<p>
	فلن تستطيع التمييز أيهما قبل الأخرى، وستحتاج إلى مساعدة إضافية مثل سجل عمل GitHub لكشف الترتيب الصحيح.
</p>

<h3>
	الموازنة بين أسلوبي إدارة الإصدار
</h3>

<p>
	لقد لمسنا بالمناقشة السابقة بعض إيجابيات وسلبيات كل أسلوب، لكن من الأفضل التطرق إلى مجال استعمال كل منهما.
</p>

<p>
	يعمل أسلوب semantic جيدًا عندما يكون لرقم الإصدار أهميةً خاصة أو عندما تكون هناك حاجة للبحث عنه. فكر مثلًا بمكتبات <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-javascript-r550/" rel="">جافا سكربت JavaScript</a> التي تستخدمها؛ فلو كنت تستخدم الإصدار 3.4.6 من مكتبة محددة وظهر تحديث لها يحمل الرقم 3.4.8، فيمكنك أن تأمل بأن انتقالك إلى الإصدار الأحدث سيكون آمنًا؛ لكن لو حمل الإصدار الجديد الرقم 4.0.1 فلن تضمن انتقالًا آمنًا إليه.
</p>

<p>
	يُناسب hash الحالات التي تُبنى فيها الشيفرة داخل أدوات برمجية artifact (مثل الملفات الثنائية القابلة للتنفيذ أو قوالب منصة Docker) قابلة بحد ذاتها للرفع أو التخزين، فإذا تطلبت اختباراتك على سبيل المثال بناء حزمتك داخل أداة برمجية، ثم رفعها إلى الخادم واختبارها، فمن الملائم عندها استخدام أسلوب Hash في تحديد الإصدار لأنه سيمنع وقوع الحوادث.
</p>

<p>
	تخيل على سبيل المثال أنك تعمل على الإصدار 3.2.2 وفشل أحد الاختبارات، أصلحت بعد ذلك الخلل ودفعت بشيفرتك إلى مستودعك، لكن طالما أنك تعمل ضمن مستودعك فلن تستطيع تحديث رقم الإصدار. لن يتغير اسم الأداة البرمجية دون تحديد إصدار hash. وإن كان هناك خطأ في رفع الأداة البرمجية، فقد يُنفَّذ الاختبار على الأداة الأقدم (طالما أنها لا تزال موجودة وبنفس الاسم). بينما لو أُعطيت الأداة رقم إصدار بأسلوب hash، ينبغي أن يتغير رقم الإصدار عند كل محاولة لدفع شيفرة معتمدة، ويعني هذا أنه في حال أخفق رفع الأداة، سيظهر خطأ لأن الأداة التي ستُنفَّذ عليها الاختبارات غير موجودة.
</p>

<p>
	يُعد ظهور الخطأ عندما يحدث شيء ما أفضل غالبًا من مشكلة تهملها منظومة CI بصمت.
</p>

<h3>
	أفضل ما في الأسلوبين
</h3>

<p>
	يمكن أن نستشف من المقارنة السابقة أن استخدام أسلوب semantic مناسب لإصدار برمجيات، بينما استخدام أسلوب hash أكثر منطقية أثناء نشر البرمجيات ولن يسبب هذا الأمر تعارضًا بالضرورة.
</p>

<p>
	فكر في ذلك على النحو التالي: تتلخص إدارة الإصدارات بأنها تقنية تشير إلى كمية معتمدة من الشيفرة وتعطيها اسمًا فليكن 3.5.5 لن يمنع ذلك طبعًا من إعطاء الشيفرة نفسها رمز hash.
</p>

<p>
	هناك أمر ما، فقد ذكرنا في بداية هذا القسم أنه علينا معرفة ما يحدث تمامًا في شيفرتنا، فعلينا أن نكون واثقين مثلًا بأننا اختبرنا الشيفرة التي نريد نشرها. سيعقد وجود أسلوبين متوازيين لإدارة الإصدار (أو تسميته) الأمور قليلًا.
</p>

<p>
	فعندما يكون هناك مشروع يستخدم نسخًا من أداة برمجية بحيث يُدار إصدار هذه النسخ بأسلوب hash أثناء اختبارها، يمكننا دائمًا تعقب نتيجة خطوات البناء والتدقيق والاختبار، وبالتالي سيعلم المطورون حالة هذه النسخ. ويحدث ذلك آليًا وبصورةٍ واضحة بالنسبة للمطور، فليس على المطور الاهتمام بما تفعله منظومة CI في الخفاء من حيث استخدام ترميز hash لتسمية النسخة واختبارها.
</p>

<p>
	عندما يدمج المطور شيفرته ضمن الفرع الرئيسي، تتولى منظومة CI الأمر من جديد، إذ ستبني هذه المرة وتختبر كل الشيفرة ومن ثم تمنحها رقم إصدار بأسلوب semantic، وتربط هذا الرقم بآخر شيفرة معتمدة ومختبرة وتحمل وسم git.
</p>

<p>
	سيكون البرنامج الذي سنصدره في الحالة السابقة مُختبرًا لأن منظومة CI ستتأكد أنّ الاختبارات قد نجحت على الشيفرة الموسومة بوسم git المحدد، وليس خطأً أن نقول أن المشروع سيستخدم أسلوب semantic في إدارة الإصدار ويتجاهل ببساطة فكرة أن منظومة CI ستختبر الفروع الخاصة بكل مطور أو طلبات السحب الخاصة بكل منهم بأسماء مبنية على أسلوب hash في إدارة الإصدار. نقول ذلك لأن النسخة التي تعنينا (التي سنصدرها) ستُمنح رقم إصدار semantic.
</p>

<h2>
	التمرينان 11.15 - 11.16
</h2>

<p>
	لنوسّع مخطط العمل الذي نبنيه لكي يزيد تلقائيًا رقم الإصدار عندما تجري الموافقة على طلب سحب وتنفيذ عملية الدمج ضمن الفرع الرئيسي وأن <a href="https://www.atlassian.com/git/tutorials/inspecting-a-repository/git-tag" rel="external nofollow">يوسم</a> النسخة المصّدرة برقم الإصدار. سنستخدم <a href="https://github.com/anothrNick/github-tag-action" rel="external nofollow">anothrNick/github-tag-action</a> وهو فعل GitHub Actions مفتوح المصدر طوره أحدهم.
</p>

<h3>
	11.15: إضافة رقم إصدار
</h3>

<p>
	سنوّسع مخطط العمل بإضافة خطوة واحدة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5039_24" style="">
<span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Bump</span><span class="pln"> version and push tag
  uses</span><span class="pun">:</span><span class="pln"> anothrNick</span><span class="pun">/</span><span class="pln">github</span><span class="pun">-</span><span class="pln">tag</span><span class="pun">-</span><span class="pln">action@1</span><span class="pun">.</span><span class="lit">36.0</span><span class="pln">
  env</span><span class="pun">:</span><span class="pln">
    GITHUB_TOKEN</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{{</span><span class="pln"> secrets</span><span class="pun">.</span><span class="pln">GITHUB_TOKEN </span><span class="pun">}}</span></pre>

<p>
	سنمرر متغير البيئة environment variable التالي <code>secrets.GITHUB_TOKEN</code> إلى الفعل، وطالما أنّ مصدر الفعل طرف ثالث، سنحتاج إلى شهادة استيثاق token في مستودعك. يمكن الاطلاع أكثر عن الاستيثاق في <a href="https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token" rel="external nofollow">توثيق GitHub</a>.
</p>

<p>
	يمكن أن نمرر عدة متحولات بيئة إلى الفعل <a href="https://github.com/anothrNick/github-tag-action" rel="external nofollow">anothrNick/github-tag-action</a>. تعدّل هذه المتغيرات الطريقة التي يُعرِّف بها الفعل الإصدارات. يمكنك الاطلاع على تفاصيل أكثر ضمن ملف <a href="https://github.com/anothrNick/github-tag-action" rel="external nofollow">README</a> الخاص بالفعل على GitHub واستخدام ما يناسبك.
</p>

<p>
	وكما سترى في التوثيق سيزداد الرقم الثانوي لرقم الإصدار تلقائيًا minor bump.
</p>

<p>
	عدّل التهيئة في الشيفرة السابقة لكي يكون هناك زيادةٌ تلقائية في رقم الترميم patch bump لرقم الإصدار، أي سيزداد الرقم الأخير تلقائيًا.
</p>

<p>
	تذكر أننا نريد فقط زيادة رقم الإصدار عندما تطرأ تغييرات على الفرع الرئيسي. لذلك أضف العبارة الشرطية "if" لمنع زيادة رقم الإصدار عند فتح طلب سحب كما فعلنا في التمرين 11.14.
</p>

<p>
	أكمل مخطط العمل وحاول تجربته، ولا تضفه مثل خطوة إضافية فقط، وإنما أجري إعداده على أنه عمل منفصل <a href="https://docs.github.com/en/actions/using-workflows/advanced-workflow-features#creating-dependent-jobs" rel="external nofollow">يعتمد</a> على العمل المهتم بتدقيق وكشف الأخطاء والفحص والنشر. أجرِ التغييرات التالية على تعريف مخطط العمل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5039_26" style="">
<span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Deployment</span><span class="pln"> pipeline

on</span><span class="pun">:</span><span class="pln">
  push</span><span class="pun">:</span><span class="pln">
    branches</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> master
  pull_request</span><span class="pun">:</span><span class="pln">
    branches</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">master</span><span class="pun">]</span><span class="pln">
    types</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">opened</span><span class="pun">,</span><span class="pln"> synchronize</span><span class="pun">]</span><span class="pln">

jobs</span><span class="pun">:</span><span class="pln">
  simple_deployment_pipeline</span><span class="pun">:</span><span class="pln">
    runs</span><span class="pun">-</span><span class="pln">on</span><span class="pun">:</span><span class="pln"> ubuntu</span><span class="pun">-</span><span class="lit">20.04</span><span class="pln">
    steps</span><span class="pun">:</span><span class="pln">
      </span><span class="com">// steps here</span><span class="pln">
  tag_release</span><span class="pun">:</span><span class="pln">
    needs</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">simple_deployment_pipeline</span><span class="pun">]</span><span class="pln">
    runs</span><span class="pun">-</span><span class="pln">on</span><span class="pun">:</span><span class="pln"> ubuntu</span><span class="pun">-</span><span class="lit">20.04</span><span class="pln">
    steps</span><span class="pun">:</span><span class="pln">
      </span><span class="com">// steps here</span></pre>

<p>
	تُنفَّذ أعمال مخطط العمل على التوازي كما ذكرنا سابقًا، لكن طالما أننا نريد إجراء التدقيق والاختبار والنشر أولاً، سنحتاج لضبط اعتمادية <code>tag_release</code> بحيث تنتظر عملًا آخر ليُنفّذ أولًا نظرًا لأننا لا نريد وضع علامة على الإصدار ما لم يجتاز الاختبارات ويُنشر.
</p>

<p>
	إذا لم تكن متأكدًا من إعدادات التهيئة، يمكنك ضبط قيمة <code>DRY_RUN</code> على <code>true</code>، وبالتالي سيعطي الفعل رقم الإصدار التالي دون إنشاء وسم للإصدار.
</p>

<p>
	حالما يعمل مخطط العمل على نحوٍ صحيح، سيشير المستودع إلى وجود بعض الوسوم:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105732" href="https://academy.hsoub.com/uploads/monthly_2022_08/repo_show_tags_05.png.180928c477bbaa97db7f107996449ca2.png" rel=""><img alt="repo_show_tags_05.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105732" data-unique="hihqijg5r" src="https://academy.hsoub.com/uploads/monthly_2022_08/repo_show_tags_05.png.180928c477bbaa97db7f107996449ca2.png" style="width: 700px; height: auto;"></a>
</p>

<p>
	وبالنقر على هذه الوسوم ستُعرض ضمن قائمة بكل الوسوم (والتي تمثل آلية لتعريف الإصدار ووسمه):
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105734" href="https://academy.hsoub.com/uploads/monthly_2022_08/tag_list_06.png.55c0888e60aa2e356cf2658ff1013519.png" rel=""><img alt="tag_list_06.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105734" data-unique="bmshjhizf" src="https://academy.hsoub.com/uploads/monthly_2022_08/tag_list_06.png.55c0888e60aa2e356cf2658ff1013519.png" style="width: 700px; height: auto;"></a>
</p>

<h3>
	11.16: تجاوز إنشاء وسوم لحزمة شيفرة معتمدة ونشرها
</h3>

<p>
	من الأفضل في أغلب الأوقات نشر الفرع الرئيسي إلى وضع الإنتاج.، لكن قد تكون هناك بعض الأسباب المقنعة لتجاوز إنشاء وسم شيفرة معتمدة أو طلب سحب جرى الموافقة على دمجه أو نشرهما.
</p>

<p>
	عدّل إعداداتك لكي لا تُنشر إلى وضع الإنتاج أية شيفرة جرى دمجها إن احتوت رسالة طلب السحب الكلمة "skip#"، ولا توسم برقم الإصدار.
</p>

<p>
	<strong>تلميح</strong>: أسهل طريقة لإنجاز ذلك هو تبديل العبارة الشرطية "if" للخطوة المناسبة. ويمكنك كما فعلنا في التمرين 11.14 الحصول على المعلومات المطلوبة من <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#github-context" rel="external nofollow">سياق GitHub</a> لمخطط العمل:
</p>

<p>
	يمكنك الإستعانة بالشيفرة التالية مثل نقطة انطلاق:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5039_31" style="">
<span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Testing</span><span class="pln"> stuff

on</span><span class="pun">:</span><span class="pln">
  push</span><span class="pun">:</span><span class="pln">
    branches</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> main

jobs</span><span class="pun">:</span><span class="pln">
  a_test_job</span><span class="pun">:</span><span class="pln">
    runs</span><span class="pun">-</span><span class="pln">on</span><span class="pun">:</span><span class="pln"> ubuntu</span><span class="pun">-</span><span class="lit">20.04</span><span class="pln">
    steps</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> uses</span><span class="pun">:</span><span class="pln"> actions</span><span class="pun">/</span><span class="pln">checkout@v2
      </span><span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> gihub context
        env</span><span class="pun">:</span><span class="pln">
          GITHUB_CONTEXT</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{{</span><span class="pln"> toJson</span><span class="pun">(</span><span class="pln">github</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
        run</span><span class="pun">:</span><span class="pln"> echo </span><span class="str">"$GITHUB_CONTEXT"</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> commits
        env</span><span class="pun">:</span><span class="pln">
          COMMITS</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{{</span><span class="pln"> toJson</span><span class="pun">(</span><span class="pln">github</span><span class="pun">.</span><span class="pln">event</span><span class="pun">.</span><span class="pln">commits</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
        run</span><span class="pun">:</span><span class="pln"> echo </span><span class="str">"$COMMITS"</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> commit messages
        env</span><span class="pun">:</span><span class="pln">
          COMMIT_MESSAGES</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{{</span><span class="pln"> toJson</span><span class="pun">(</span><span class="pln">github</span><span class="pun">.</span><span class="pln">event</span><span class="pun">.</span><span class="pln">commits</span><span class="pun">.*.</span><span class="pln">message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">}}</span><span class="pln">
        run</span><span class="pun">:</span><span class="pln"> echo </span><span class="str">"$COMMIT_MESSAGES"</span></pre>

<p>
	لاحظ ما الذي طُبع في سجل مخطط العمل.
</p>

<p>
	<strong>ملاحظة</strong>: يمكنك الوصول إلى الشيفرة المعتمدة أو رسائلها عند الدفع أو الدمج مع الفرع الرئيسي فقط، وبالتالي لن تحمل الخاصية <code>github.event.commits</code> أية قيمة. لا حاجة لهذه القيمة على أية حال طالما أننا نرغب في تجاوز هذه الخطوة.
</p>

<p>
	ستحتاج غالبًا إلى الدالتين <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#contains" rel="external nofollow">contains</a> و <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#join" rel="external nofollow">join</a> ضمن عبارة if الشرطية.
</p>

<p>
	لا يُعد نشر مخطط العمل أمرًا سهلًا، وغالبًا ما يكون الخيار المتاح هو المحاولة والخطأ، وينصح أحيانًا بوجود مستودع منفصل للتأكد من صحة تهيئة الإعدادات، ثم تُنقل هذه الإعدادات إلى المستودع الفعلي إن كانت صحيحة.
</p>

<p>
	كما يمكن أيضًا تثبيت أداة مثل <a href="https://github.com/nektos/act" rel="external nofollow">act</a>، التي تساعدك على تشغيل مخطط العمل على جهازك. وعندما تواجهك حالات مختلفة عن التي ناقشناها، كأن تنشئ <a href="https://docs.github.com/en/free-pro-team@latest/actions/creating-actions" rel="external nofollow">أفعال خاصة بك</a>، فإن اقتحام غمار أدوات مثل act وتجربتها سيستحق العناء.
</p>

<h2>
	ملاحظة لاستخدام الأفعال التي يطورها طرف ثالث
</h2>

<p>
	عندما تستخدم فعل يؤمنه طرف ثالث مثل <code>github-tag-action</code>، من الأفضل أن تحدد أسلوب hash لإدارة الإصدار بدلًا من رقم الإصدار. والسبب في ذلك أن رقم الإصدار الذي أنجز بوسم git قد يكون عرضة للتغيير، فالإصدار الذي يحمل الرقم 1.33.0 هذا اليوم على سبيل المثال، قد يشير إلى شيفرة مختلفة عن تلك الموجودة في نفس الإصدار لكن في الأسبوع القادم.
</p>

<p>
	مع ذلك، فالشيفرة المعتمدة على ترميز hash محددة لا تتغير أيًا كانت الظروف، لذا فإن أردنا التيقن 100% من الشيفرة التي نستخدمها، سيكون الخيار الأكثر أمانًا hash.
</p>

<p>
	إنّ الإصدار <a href="https://github.com/anothrNick/github-tag-action/releases" rel="external nofollow">1.33.0</a> من الفعل السابق يتوافق مع شيفرة ترميز hash لها هو الإصدار: <code>eca2b69f9e2c24be7decccd0f15fdb1ea5906598</code>، لذلك من الأفضل أن نغير إعدادات التهيئة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_5039_33" style="">
<span class="pln">    </span><span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Bump</span><span class="pln"> version and push tag
      uses</span><span class="pun">:</span><span class="pln"> anothrNick</span><span class="pun">/</span><span class="pln">github</span><span class="pun">-</span><span class="pln">tag</span><span class="pun">-</span><span class="pln">action@eca2b69f9e2c24be7decccd0f15fdb1ea5906598      
env</span><span class="pun">:</span><span class="pln">
        GITHUB_TOKEN</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{{</span><span class="pln"> secrets</span><span class="pun">.</span><span class="pln">GITHUB_TOKEN </span><span class="pun">}}</span></pre>

<p>
	وعلينا أن نثق عند استخدام أفعال تزودنا بها GitHub بأنها لن تعبث بوسوم الإصدار version tags وأن نختبر شيفرتها بدّقة.
</p>

<p>
	وقد ينتهي بك المطاف عند استخدام أفعال من طرف ثالث أمام أفعال تحتوي ثغرات أو حتى مثيرة للشبهة، وحتى لو لم تكن نية المطور الذي كتب الشيفرة سيئة، فقد ينس معلومات الاستيثاق الخاصة به في ركن إنترنت عام، ومن يدري ما قد يحدث بعدها.
</p>

<p>
	سنتأكد عند استخدام أسلوب hash أن هذه شيفرة التي نستخدمها لتشغيل مخطط العمل لن تتغير، لأن تغيير الشيفرة المعتمدة المسؤولة عن تنفيذ المخطط سيؤدي إلى تغيير hash.
</p>

<h2>
	أبق الفرع الرئيسي آمنا
</h2>

<p>
	سيسمح لك غيت هب أن تهيئ فروعًا محمية، فمن المهم حماية الفرع الأكثر أهمية الذي يجب ألا يخفق أبدًا وهو الفرع الرئيسي. يمكنك أن تختار بين عدة مستويات حماية عبر إعدادات المستودع. لن نغطي كل هذه الخيارات، لكن يمكنك طبعًا الاطلاع عليها من خلال توثيق GitHub.
</p>

<p>
	ومن وجهة نظر منظومة CI، ستتمثل الحماية الأكثر أهمية في نجاح خطوات التحقق من الحالة قبل أن يدمج طلب السحب ضمن الفرع الرئيسي. فلو أعددت أفعال GitHub لتنفذ مهام التدقيق والاختبار، فلن يُدمج طلب السحب قبل تصحيح جميع أخطاء التدقيق وقبل أن تنجح جميع الاختبارات. ونظرًا لكونك مدير مستودعك، ستجد خيارًا لتخطي التقييدات هذه. لن يظهر هذا الخيار لغير المدراء.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105730" href="https://academy.hsoub.com/uploads/monthly_2022_08/repo_override_restrictions_08.png.c82cb9eaff05ec99a70421b5cdcff372.png" rel=""><img alt="repo_override_restrictions_08.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105730" data-unique="s6fnl08dw" src="https://academy.hsoub.com/uploads/monthly_2022_08/repo_override_restrictions_08.png.c82cb9eaff05ec99a70421b5cdcff372.png" style="width: 700px; height: auto;"></a>
</p>

<p>
	لتهيئ إعدادات الحماية للفرع الرئيسي، توجه إلى إعدادات "Settings" المستودع من القائمة العليا ضمن المستودع. اختر من قائمة الجهة اليسرى فروع "Branches" ثم انقر زر أضف قاعدة "Add rule" بجوار العنصر قواعد حماية الفروع "Branches protection rules". اكتب نموذج لاسم الفرع ("master" سيكون مناسبًا)، ثم اختر طريقة الحماية التي تريد إعدادها. اختر على الأقل "يجب التحقق من نجاح خطوات التحقق من الحالة قبل الدمج Require status checks to pass before merging" لتضمن تسخير الإمكانات الكاملة لاستضافة GitHub Actions. وتحت هذا الخيار عليك اختيار "يجب أن تكون الفروع محدّثة قبل الدمج Require branches to be up to date before merging"، وفعّل جميع الخيارات التي تتحقق من الحالة قبل دمج أية طلبات سحب.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105731" href="https://academy.hsoub.com/uploads/monthly_2022_08/repo_protection_options_09.png.5ac16363ac3a4d380c5007f71ed5fce8.png" rel=""><img alt="repo_protection_options_09.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105731" data-unique="78i57nncw" src="https://academy.hsoub.com/uploads/monthly_2022_08/repo_protection_options_09.thumb.png.50d32572b6ca2b5f207bb702fb601f4d.png"></a>
</p>

<h2>
	التمرين 11.17
</h2>

<h3>
	إضافة حماية إلى الفرع الرئيسي
</h3>

<p>
	أضف حماية لفرعك الرئيسي. إذ عليك حمايته بضمان التالي:
</p>

<ul>
<li>
		الموافقة على جميع طلبات السحب قبل دمجها.
	</li>
	<li>
		نجاح جميع خطوات التحقق من الحالة قبل الدمج.
	</li>
</ul>
<p>
	لا تفعِّل الخيار "Include administrators" حاليًا، لأنك لو فعلت ذلك فعليك الانتظار حتى يراجع شخص ما طلب السحب الذي قدمته قبل نشر الشيفرة.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part11/keping_green" rel="external nofollow">Keeping Green</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%AA%D9%88%D8%B2%D9%8A%D8%B9%D9%87%D8%A7-%D9%88%D9%81%D9%82-%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r646/" rel="">نشر التطبيقات وتوزيعها وفق نهج التسليم المستمر </a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/%d9%86%d8%b4%d8%b1-%d8%aa%d8%b7%d8%a8%d9%8a%d9%82%d8%a7%d8%aa-%d8%a7%d9%84%d9%88%d9%8a%d8%a8-%d8%a7%d9%84%d9%85%d9%88%d8%ac%d9%87%d8%a9-%d9%84%d8%a8%d9%8a%d8%a6%d8%a9-%d8%a7%d9%84%d8%a5%d9%86%d8%aa%d8%a7%d8%ac-r132/" rel="">نشر تطبيقات الويب الموجهة لبيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-ios-%D8%B9%D9%84%D9%89-%D9%85%D8%AA%D8%AC%D8%B1-apple-store-r650/" rel="">نشر تطبيقات iOS على متجر Apple Store</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">647</guid><pubDate>Tue, 06 Sep 2022 17:02:00 +0000</pubDate></item><item><title>&#x646;&#x634;&#x631; &#x627;&#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642;&#x627;&#x62A; &#x648;&#x62A;&#x648;&#x632;&#x64A;&#x639;&#x647;&#x627; &#x648;&#x641;&#x642; &#x646;&#x647;&#x62C; &#x627;&#x644;&#x62A;&#x633;&#x644;&#x64A;&#x645; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631;</title><link>https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%AA%D9%88%D8%B2%D9%8A%D8%B9%D9%87%D8%A7-%D9%88%D9%81%D9%82-%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r646/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_08/Deployment.png.49b09703bf09fa8b519f9857d1832a33.png" /></p>

<p>
	الآن وبعد أن كتبنا تطبيقًا جيدًا، قد حان الوقت لنشره وتوزيعه على المستخدمين الفعليين.
</p>

<p>
	لقد فعلنا ذلك في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-nodejs-%D9%88express-r1099/" rel="">القسم 3</a> بدفع مستودع git إلى خوادم الاستضافة السحابية <a href="https://www.heroku.com/home" rel="external nofollow">Heroku</a> بكل بساطة. تُعد عملية إصدار برمجيات على منصة هيروكو Heroku أمرًا بسيطًا موازنةً بغيرها من الاستضافات، لكن لا تزال هناك طبعًا بعض المخاطر، فلا شيء سيمنعنا من الدفع بشيفرة لا تعمل إلى وضع الإنتاج عن طريق الخطأ.
</p>

<p>
	سنتعرف في الفقرات القادمة على مبادئ النشر الآمن للتطبيقات، ومبادئ نشر البرمجيات بالمقاييس الصغيرة والواسعة.
</p>

<h2>
	يمكن لأي شيء أن يحدث خطأ
</h2>

<p>
	نريد تحديد بعض القواعد التي تضبط عملية نشرنا للتطبيقات، لكن علينا بداية إلقاء نظرةٍ على بعض المحدوديات الواقعية:
</p>

<p>
	"إن أمكن لشيءٍ ما أن يخفق، فسيخفق": هي عبارة يتضمنها قانون مورفي، ومن المهم تذكرها دائمًا عند التخطيط لمنظومة النشر التي سنتبعها، ومن الأشياء التي ينبغي أخذها بالحسبان أيضًا:
</p>

<ul>
<li>
		ماذا لو توقف الحاسوب عن العمل أثناء نشر التطبيق؟
	</li>
	<li>
		ماذا سيحدث إن انقطع الاتصال بالإنترنت أثناء نشر التطبيق؟
	</li>
	<li>
		ماذا سيحدث إن أخفقت أية تعليمات من سكربت أو منظومة النشر؟
	</li>
	<li>
		ماذا سيحدث إن لم يعمل تطبيقي، لسببٍ أو لآخر، على الخادم بعد نشره؟ هل يمكنني التراجع rollback إلى النسخة السابقة؟
	</li>
	<li>
		ماذا سيحدث إذ أرسل مستخدم ما <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-%D8%B4%D8%B1%D8%AD-%D8%A7%D9%84%D8%AA%D8%AE%D8%A7%D8%B7%D8%A8-%D8%A8%D9%8A%D9%86-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-%D9%88%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-r74/" rel="">طلب HTTP</a> إلى التطبيق قبل أن يكتمل نشره (لا يوجد وقتٌ لإرسال استجابة له)؟
	</li>
</ul>
<p>
	هذه عينة صغيرة من المشاكل التي قد تواجهك أثناء النشر أو بالأحرى عينة من الأمور التي ينبغي التخطيط لها. لا ينبغي لمنظومة النشر التي نعتمدها أن تترك البرنامج في حالة إخفاق أيًا كان سببه، كما ينبغي علينا أن نعرف (أو أن نجد بسهولة) حالة منظومة النشر.
</p>

<p>
	وطالما نتحدث عن النشر وعن التكامل المستمر CI عمومًا، هناك قاعدةٌ مهمةٌ أخرى عليك تذكرها: "الأخطاء الصامتة قاتلة".
</p>

<p>
	لا يعني هذا طبعًا عرض الأخطاء على مستخدم البرنامج، بل يعني أنه يجب علينا الانتباه إلى أي شيء قد يخفق. فلو كنا على دراية بمشكلة محتملة الوقوع، يمكننا إصلاحها. فإن لم تعطي منظومة النشر أية أخطاء لكنها فشلت، سنصل إلى المرحلة التي نعتقد فيها أننا أصلحنا كل الأخطاء الحرجة ومع ذلك تخفق المنظومة، وبالتالي سنترك الثغرة في بيئة الإنتاج غير مدركين لخطورة الوضع.
</p>

<h2>
	ما الذي تمنحه منظومة النشر الجيدة؟
</h2>

<p>
	من الصعب وضع قواعد محددة أو متطلبات خاصة بمنظومة النشر، لكننا سنحاول ذلك:
</p>

<ul>
<li>
		عندما تخفق المنظومة في أية خطوة، يجب أن تخفق دون أية آثار جانبية.
	</li>
	<li>
		لا ينبغي أبدًا أن تترك المنظومة البرنامج في حالة فشل.
	</li>
	<li>
		ينبغي أن تخبرنا المنظومة عن أية حالات إخفاق، فمن الأفضل تنبيهنا إلى حالات الإخفاق عوضًا عن النجاح.
	</li>
	<li>
		ينبغي على المنظومة أن تسمح لنا بالتراجع إلى النسخة المنشورة سابقًا.
	</li>
	<li>
		يفضل أن نتراجع بسهولة إلى نسخة أقل عرضة للإخفاق بالموازنة مع النسخة المنشورة كاملةً.
	</li>
	<li>
		يبقى الخيار الأفضل لنا هو التراجع الآلي في حال أخفق النشر.
	</li>
	<li>
		ينبغي أن تكون المنظومة قادرةً على التعامل مع طلبات HTTP قبل انتهاء أو أثناء عملية النشر.
	</li>
	<li>
		ينبغي أن تتأكد المنظومة أنّ البرنامج الذي ننشره سيلبّي المتطلبات التي وضعناها لعملية النشر (لا تنشر مثلًا برنامج أخفق في أحد الاختبارات).
	</li>
</ul>
<p>
	لنحدد أيضًا بعض الأشياء التي نريدها في منظومة النشر الافتراضية التي نقترحها:
</p>

<ul>
<li>
		نريدها أن تكون سريعة.
	</li>
	<li>
		لا نريد وقتًا يتوقف فيه البرنامج عن العمل downtime أثناء النشر (هذا أمر مختلف عن معالجة طلبات المستخدمين قبل انتهاء أو أثناء النشر).
	</li>
</ul>
<h2>
	التمارين 11.10 - 11.12
</h2>

<p>
	قبل أن نبدأ بالتمارين عليك إعداد تطبيقك لبيئة عمل <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-nodejs-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%AE%D8%AF%D9%85%D8%A9-%D9%87%D9%8A%D8%B1%D9%88%D9%83%D9%88-heroku-%D9%85%D8%AB%D8%A7%D9%84%D9%8B%D8%A7-r1100/" rel="">هيروكو Heroku</a>. وهنا لن ندفع الشيفرة بأنفسنا، إذ سينفِّذ مخطط عمل GitHub Actions المهمة من أجلنا.
</p>

<p>
	تأكد من تثبيت <a href="https://devcenter.heroku.com/articles/heroku-cli#download-and-install" rel="external nofollow">Heroku CLI</a> ثم سجِّل دخولك مستخدمًا واجهة سطر الأوامر CLI عن طريق <code>heroku login</code>.
</p>

<p>
	أنشئ تطبيقًا على هيروكو HeroKu باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_510_9" style="">
<span class="pln">heroku create </span><span class="pun">--</span><span class="pln">region eu </span><span class="pun">{</span><span class="pln">your</span><span class="pun">-</span><span class="pln">app</span><span class="pun">-</span><span class="pln">name</span><span class="pun">}‎</span></pre>

<p>
	وانتق <a href="https://devcenter.heroku.com/articles/regions" rel="external nofollow">منطقةً</a> قريبةً من موقعك الجغرافي.
</p>

<p>
	ولِّد شهادة تحقق Token لملفك على Heroku باستخدام الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_510_11" style="">
<span class="pln">heroku authorizations</span><span class="pun">:</span><span class="pln">create</span></pre>

<p>
	ثم خزّن معلومات الاستيثاق على ملف ضمن حاسوبك لكن <strong>لا تدفعه إلى GitHub</strong>.
</p>

<p>
	ستحتاج إلى شهادة التحقق من أجل مخطط عمل نشر التطبيق. اطلع أكثر على شهادة تحقق هيروكو عبر <a href="https://devcenter.heroku.com/articles/platform-api-quickstart" rel="external nofollow">الإنترنت</a>.
</p>

<h3>
	11.10: نشر تطبيقك على منصة هيروكو Heroku
</h3>

<p>
	وسّع مخطط العمل بخطوة لنشر تطبيقك على منصة هيروكو Heroku.
</p>

<p>
	نفترض فيما سيأتي أنك ستستخدم <a href="https://github.com/AkhileshNS/heroku-deploy" rel="external nofollow">AkhileshNS/heroku-deploy</a> وهو فعل GitHub Actions الخاص بالنشر على هيروكو والذي طوره مجتمع GitHub Actions.
</p>

<p>
	تحتاج إلى شهادة التفويض التي حصلت عليها سابقًا من أجل نشر تطبيقك. ومن الأفضل تمرير قيمتها إلى GitHub Actions باستخدام أسرار المستودع repository secrets:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105723" href="https://academy.hsoub.com/uploads/monthly_2022_08/passing_token_to_gha_01.png.c395c1d5880dfd244b34b9b05963f8c9.png" rel=""><img alt="passing_token_to_gha_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105723" data-unique="wzs9p25pu" src="https://academy.hsoub.com/uploads/monthly_2022_08/passing_token_to_gha_01.png.c395c1d5880dfd244b34b9b05963f8c9.png" style="width: 650px; height: auto;"></a>
</p>

<p>
	يمكنك الآن الوصول إلى قيمة الشهادة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-py prettyprinted" id="ips_uid_510_15" style="">
<span class="pln">$</span><span class="pun">{{</span><span class="pln">secrets</span><span class="pun">.</span><span class="pln">HEROKU_API_KEY</span><span class="pun">}}</span></pre>

<p>
	إذا جرى كل شيء على ما يرام، سيبدو مخطط العمل على الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105725" href="https://academy.hsoub.com/uploads/monthly_2022_08/workflow_deploy_02.png.2e348ad36b71f21a1ad3839d007803c7.png" rel=""><img alt="workflow_deploy_02.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105725" data-unique="hkfcoq153" src="https://academy.hsoub.com/uploads/monthly_2022_08/workflow_deploy_02.png.2e348ad36b71f21a1ad3839d007803c7.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	جرِّب تطبيقك عبر متصفح، لكن الأخطاء ستظهر غالبًا. سنجد أنّ منصة هيروكو تفترض وجود الملف "Procfile" في المستودع والذي يوجّهه لطريقة تشغيل البرنامج.
</p>

<p>
	أضف إذًا ملف "Procfile" مناسب إلى المستودع وتأكد أنّ التطبيق سيعمل جيدًا.
</p>

<p>
	<strong>تذكرة</strong>: راقب باستمرار ما يحدث عبر سِجِل الخادم عندما تجرب عملية النشر. استخدم لذلك الأمر <code>heroku logs</code> دائمًا.
</p>

<h3>
	11.11: التحقق من عمل التطبيق
</h3>

<p>
	قبل التوسّع أكثر في التطبيق، تحقق أن التطبيق يعمل جيدًا بعد النشر.
</p>

<p>
	لا نحتاج في الواقع لخطوة جديدة في مخطط العمل، إذ يحتوي الفعل <a href="https://github.com/marketplace/actions/deploy-to-heroku" rel="external nofollow">deploy-to-heroku</a> على خيار يفي بالغرض.
</p>

<p>
	أضف وصلة تخديم end point بسيطة مهمتها التحقق من عمل التطبيق في الواجهة الخلفية. ويمكنك أيضًا نسخ الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_510_18" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/health'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'ok'</span><span class="pun">)</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	من الجيد أيضًا وجود وصلة تخديم اختبارية في التطبيق، لكي يمتلك إمكانية إجراء بعض التعديلات في الشيفرة ويتأكد من أن التغيرات قد ظهرت أيضًا في النسخة المنشورة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_510_20" style="">
<span class="pln">app</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'/version'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'1'</span><span class="pun">)</span><span class="pln"> </span><span class="com">// غير هذه القيمة للتأكد من أنها نُشرت في النسخة الجديدة</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	راجع <a href="https://github.com/marketplace/actions/deploy-to-heroku" rel="external nofollow">التوثيق</a> لتعرف كيف ستضم آلية التحقق من عمل التطبيق إلى خطوة النشر، واستخدم عنوان وصلة التخديم التي أنشأتها للتحقق من عمل التطبيق، وقد تحتاج غالبًا إلى الخيار "checkstring" حتى يُنفَّذ الأمر.
</p>

<p>
	تأكد من أنّ GitHub Actions سينتبه إذا ما سبب النشر إخفاق التطبيق. استخدم لاختبار ذلك أمر إقلاع خاطئ مثلًا في الملف "Procfile".
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105721" href="https://academy.hsoub.com/uploads/monthly_2022_08/check_app_failing_03.png.c22f5d01d8b24c46759be4c0d181985d.png" rel=""><img alt="check_app_failing_03.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105721" data-unique="v6256vo3z" src="https://academy.hsoub.com/uploads/monthly_2022_08/check_app_failing_03.png.c22f5d01d8b24c46759be4c0d181985d.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	قبل الانتقال إلى التمرين التالي، أصلح مشاكل خطوة النشر وتأكد أن التطبيق سيعمل بالشكل المطلوب مجددًا.
</p>

<h3>
	11.12: التراجع
</h3>

<p>
	من الأفضل إذا أخفق التطبيق بعد النشر أن تتراجع إلى الإصدار السابق. ولحسن الحظ، يجعل هيروكو Heroku هذا الأمر بسيطًا جدًا. ينتج عن كل عملية نشر على Heroku <a href="https://blog.heroku.com/releases-and-rollbacks#releases" rel="external nofollow">إصدار</a>، ويمكنك معرفة الإصدارات الموجودة لتطبيق بتنفيذ الأمر <code>heroku releases</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_510_23" style="">
<span class="pln">$ heroku releases
</span><span class="pun">===</span><span class="pln"> calm</span><span class="pun">-</span><span class="pln">wildwood</span><span class="pun">-</span><span class="lit">40210</span><span class="pln"> </span><span class="typ">Releases</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Current</span><span class="pun">:</span><span class="pln"> v8
v8  </span><span class="typ">Deploy</span><span class="pln"> de15fc2b  mluukkai@iki</span><span class="pun">.</span><span class="pln">fi  </span><span class="lit">2022</span><span class="pun">/</span><span class="lit">03</span><span class="pun">/</span><span class="lit">02</span><span class="pln"> </span><span class="lit">19</span><span class="pun">:</span><span class="lit">14</span><span class="pun">:</span><span class="lit">22</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0200</span><span class="pln"> </span><span class="pun">(~</span><span class="pln"> </span><span class="lit">8m</span><span class="pln"> ago</span><span class="pun">)</span><span class="pln">
v7  </span><span class="typ">Deploy</span><span class="pln"> </span><span class="lit">8748a04e</span><span class="pln">  mluukkai@iki</span><span class="pun">.</span><span class="pln">fi  </span><span class="lit">2022</span><span class="pun">/</span><span class="lit">03</span><span class="pun">/</span><span class="lit">02</span><span class="pln"> </span><span class="lit">19</span><span class="pun">:</span><span class="lit">06</span><span class="pun">:</span><span class="lit">28</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0200</span><span class="pln"> </span><span class="pun">(~</span><span class="pln"> </span><span class="lit">16m</span><span class="pln"> ago</span><span class="pun">)</span><span class="pln">
v6  </span><span class="typ">Deploy</span><span class="pln"> a617a93d  mluukkai@iki</span><span class="pun">.</span><span class="pln">fi  </span><span class="lit">2022</span><span class="pun">/</span><span class="lit">03</span><span class="pun">/</span><span class="lit">02</span><span class="pln"> </span><span class="lit">19</span><span class="pun">:</span><span class="lit">00</span><span class="pun">:</span><span class="lit">02</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0200</span><span class="pln"> </span><span class="pun">(~</span><span class="pln"> </span><span class="lit">23m</span><span class="pln"> ago</span><span class="pun">)</span><span class="pln">
v5  </span><span class="typ">Deploy</span><span class="pln"> </span><span class="lit">70f9b219</span><span class="pln">  mluukkai@iki</span><span class="pun">.</span><span class="pln">fi  </span><span class="lit">2022</span><span class="pun">/</span><span class="lit">03</span><span class="pun">/</span><span class="lit">02</span><span class="pln"> </span><span class="lit">18</span><span class="pun">:</span><span class="lit">48</span><span class="pun">:</span><span class="lit">47</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0200</span><span class="pln"> </span><span class="pun">(~</span><span class="pln"> </span><span class="lit">34m</span><span class="pln"> ago</span><span class="pun">)</span><span class="pln">
v4  </span><span class="typ">Deploy</span><span class="pln"> </span><span class="lit">0b2db00d</span><span class="pln">  mluukkai@iki</span><span class="pun">.</span><span class="pln">fi  </span><span class="lit">2022</span><span class="pun">/</span><span class="lit">03</span><span class="pun">/</span><span class="lit">02</span><span class="pln"> </span><span class="lit">17</span><span class="pun">:</span><span class="lit">53</span><span class="pun">:</span><span class="lit">24</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0200</span><span class="pln"> </span><span class="pun">(~</span><span class="pln"> </span><span class="lit">1h</span><span class="pln"> ago</span><span class="pun">)</span><span class="pln">
v3  </span><span class="typ">Deploy</span><span class="pln"> f1cd250b  mluukkai@iki</span><span class="pun">.</span><span class="pln">fi  </span><span class="lit">2022</span><span class="pun">/</span><span class="lit">03</span><span class="pun">/</span><span class="lit">02</span><span class="pln"> </span><span class="lit">17</span><span class="pun">:</span><span class="lit">44</span><span class="pun">:</span><span class="lit">32</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0200</span><span class="pln"> </span><span class="pun">(~</span><span class="pln"> </span><span class="lit">1h</span><span class="pln"> ago</span><span class="pun">)</span><span class="pln">
v2  </span><span class="typ">Enable</span><span class="pln"> </span><span class="typ">Logplex</span><span class="pln">   mluukkai@iki</span><span class="pun">.</span><span class="pln">fi  </span><span class="lit">2022</span><span class="pun">/</span><span class="lit">03</span><span class="pun">/</span><span class="lit">02</span><span class="pln"> </span><span class="lit">17</span><span class="pun">:</span><span class="lit">00</span><span class="pun">:</span><span class="lit">26</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0200</span><span class="pln"> </span><span class="pun">(~</span><span class="pln"> </span><span class="lit">2h</span><span class="pln"> ago</span><span class="pun">)</span><span class="pln">
v1  </span><span class="typ">Initial</span><span class="pln"> release  mluukkai@iki</span><span class="pun">.</span><span class="pln">fi  </span><span class="lit">2022</span><span class="pun">/</span><span class="lit">03</span><span class="pun">/</span><span class="lit">02</span><span class="pln"> </span><span class="lit">17</span><span class="pun">:</span><span class="lit">00</span><span class="pun">:</span><span class="lit">25</span><span class="pln"> </span><span class="pun">+</span><span class="lit">0200</span><span class="pln"> </span><span class="pun">(~</span><span class="pln"> </span><span class="lit">2h</span><span class="pln"> ago</span><span class="pun">)</span></pre>

<p>
	يمكن <a href="https://blog.heroku.com/releases-and-rollbacks#rollbacks" rel="external nofollow">التراجع</a> إلى إصدار محدد بكتابة أمر واحد ضمن سطر الأوامر، والأفضل من ذلك سيتولى الفعل <a href="https://github.com/marketplace/actions/deploy-to-heroku" rel="external nofollow">deploy-to-heroku</a> مهمة التراجع نيابةً عنا. اقرأ <a href="https://github.com/marketplace/actions/deploy-to-heroku" rel="external nofollow">توثيق</a> GitHub Actions مجددًا وعدّل مخطط العمل لتمنع إخفاق التطبيق عند النشر. يمكنك محاكاة الأمر أيضًا بكتابة أمر خاطئ في الملف "Procfile" والتجربة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105724" href="https://academy.hsoub.com/uploads/monthly_2022_08/rollback_04.png.f43acb54126435885bd41fd083427e4f.png" rel=""><img alt="rollback_04.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105724" data-unique="u96pryk08" src="https://academy.hsoub.com/uploads/monthly_2022_08/rollback_04.png.f43acb54126435885bd41fd083427e4f.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	تحقق أنّ التطبيق سيستمر في العمل حتى لو أخفق النشر.
</p>

<p>
	<strong>تنبيه</strong>: على الرغم من إمكانية التراجع التلقائي، قد يخفق البناء في العالم الحقيقي. لذلك يُعد إيجاد سبب المشكلة وإصلاحها بسرعة أمرًا جوهريًا. ويبقى سجل هيروكو Heroku عادةً هو المكان الأنسب للبحث والتقصي عن المسببات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105722" href="https://academy.hsoub.com/uploads/monthly_2022_08/heoku_log_05.png.9de26891b01936f110ba0656382783e7.png" rel=""><img alt="heoku_log_05.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105722" data-unique="bysv5nma0" src="https://academy.hsoub.com/uploads/monthly_2022_08/heoku_log_05.png.9de26891b01936f110ba0656382783e7.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part11/deployment" rel="external nofollow">Deployment</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/devops/deployment/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-github-actions-%D9%84%D8%AA%D8%AD%D9%82%D9%8A%D9%82-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r645/" rel="">استخدام GitHub Actions لتحقيق التكامل المستمر والنشر المستمر</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/servers/%d9%86%d8%b4%d8%b1-%d8%aa%d8%b7%d8%a8%d9%8a%d9%82%d8%a7%d8%aa-%d8%a7%d9%84%d9%88%d9%8a%d8%a8-%d8%a7%d9%84%d9%85%d9%88%d8%ac%d9%87%d8%a9-%d9%84%d8%a8%d9%8a%d8%a6%d8%a9-%d8%a7%d9%84%d8%a5%d9%86%d8%aa%d8%a7%d8%ac-r132/" rel="">نشر تطبيقات الويب الموجهة لبيئة الإنتاج</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-ios-%D8%B9%D9%84%D9%89-%D9%85%D8%AA%D8%AC%D8%B1-apple-store-r650/" rel="">نشر تطبيقات iOS على متجر Apple Store</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">646</guid><pubDate>Thu, 25 Aug 2022 10:58:24 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; GitHub Actions &#x644;&#x62A;&#x62D;&#x642;&#x64A;&#x642; &#x627;&#x644;&#x62A;&#x643;&#x627;&#x645;&#x644; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631; &#x648;&#x627;&#x644;&#x646;&#x634;&#x631; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631;</title><link>https://academy.hsoub.com/devops/deployment/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-github-actions-%D9%84%D8%AA%D8%AD%D9%82%D9%8A%D9%82-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r645/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_08/Communicating-with-server.png.ca1a0ed05fc56701f1f68c434d4f66a9.png" /></p>

<p>
	سنلقي نظرةً قبل أن نبدأ التعامل مع GitHub Actions على ماهيته والطريقة التي يعمل بها.
</p>

<p>
	يعمل GitHub Actions على مبدأ مخططات العمل <a href="https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/introduction-to-github-actions#workflows" rel="external nofollow">workflows</a>؛ ومخطط العمل هو سلسلة من <a href="https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/introduction-to-github-actions#jobs" rel="external nofollow">الأعمال</a> التي تُنفَّذ عندما يقع حدثٌ معين، وتتضمن هذه الأعمال بحد ذاتها التعليمات التي يجب أن يُنفّذها GitHub Actions.
</p>

<p>
	سيبدو تنفيذ مخطط عمل نموذجي على النحو التالي:
</p>

<ul>
<li>
		وقوع حدثٌ ما، مثل دفع push شيفرة إلى الفرع الرئيسي.
	</li>
	<li>
		البدء بتنفيذ مخطط العمل.
	</li>
	<li>
		الإنهاء والتنظيف.
	</li>
</ul>
<h2>
	الاحتياجات الأساسية
</h2>

<p>
	نحتاج عمومًا إلى المتطلبات التالية لتشغيل منظومة التكامل المتواصل Continuous Integration -أو اختصارًا CI- ضمن مستودع:
</p>

<ul>
<li>
		مستودع.
	</li>
	<li>
		بعض التعريفات بالمهام التي ستنفذها منظومة CI، وقد يكون ذلك على هيئة ملف ضمن المستودع، أو يمكن أن يُعرَّف ضمن منظومة CI.
	</li>
	<li>
		يجب أن تعرف المنظومة أنّ المستودع (والملف في داخله) موجودٌ فعلًا.
	</li>
	<li>
		تحتاج المنظومة إلى الأذونات اللازمة لتنفيذ الأفعال التي يفترض أن تُنفذها، فلو أردنا من المنظومة أن تكون قادرةً على النشر في بيئة الإنتاج مثلًا، فستحتاج إلى بيانات الاستيثاق الخاصة بهذه البيئة.
	</li>
</ul>
<p>
	سنحتاج ما ذكرناه على الأقل في النموذج التقليدي للمنظومة. سنرى لاحقًا كيفية اختصار بعض الخطوات أو تنفيذها بطريقة مريحة لا تسبب لك أية مشاكل تقلق منها.
</p>

<p>
	لاستضافة GitHub Actions إيجابياتٌ عظيمة بالموازنة مع الاستضافة الذاتية؛ إذ سيُستضاف المستودع مع مزوِّد لمنظومة CI، أي سيؤمن لك GitHub Actions المستودع ومنظومة CI معًا، ويعني ذلك أننا عندما نُمكِّن الأفعال ضمن المستودع، سيكون GitHub Actions على علم مسبقًا بأنه لدينا مخططات عمل مُعرًّفة، وبما تعنيه هذه التعريفات.
</p>

<h2>
	التمرين 11.2
</h2>

<p>
	سننفذ في معظم تمرينات هذا القسم خط إنتاج لمنظومة CI/CD من أجل <a href="https://github.com/smartlyio/fullstackopen-cicd" rel="external nofollow">المشروع الصغير</a> الموجود على غيت هب GitHub.
</p>

<p>
	<strong>تنبيه</strong>: قد لا تعمل الشيفرة مع الإصدار 15 من Node، وإذا حدث ذلك ولم يُقلع المشروع، انتقل للعمل على الإصدار 14 وإلا عليك حل مشاكلك بنفسك.
</p>

<h3>
	المشروع النموذجي
</h3>

<p>
	عليك أولًا إنشاء نسخةٍ من مستودع المشروع ضمن حسابك الخاص ولاستخدامك الشخصي.
</p>

<p>
	لاشتقاق المستودع، انقر على الزر "Fork" في أعلى ويمين واجهة المستودع بجانب الزر "Star":
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105713" href="https://academy.hsoub.com/uploads/monthly_2022_08/fork_repository_01.png.f22e1cca53021fc7e7168f8e168ecbd3.png" rel=""><img alt="fork_repository_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105713" data-unique="8obuf65vs" src="https://academy.hsoub.com/uploads/monthly_2022_08/fork_repository_01.png.f22e1cca53021fc7e7168f8e168ecbd3.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	سيبدأ بعد ذلك بإنشاء مستودع جديد يُدعى "github_username}/full-stack-open-pokedex}"، وستُنقل بعد انتهاء العملية إلى مستودعك الجديد تلقائيًا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105716" href="https://academy.hsoub.com/uploads/monthly_2022_08/new_repository_created_02.png.6a95092b8ba97cf74992fbe34fc27732.png" rel=""><img alt="new_repository_created_02.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105716" data-unique="dv7cregto" src="https://academy.hsoub.com/uploads/monthly_2022_08/new_repository_created_02.png.6a95092b8ba97cf74992fbe34fc27732.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	انسخ المشروع الآن إلى جهازك، وكما جرت العادة إبدأ بتفقد الملف "package.json" فهو المكان الأنسب للاطلاع على تفاصيل مشروع جديد.
</p>

<p>
	حاول أن تنفّذ ما يلي:
</p>

<ul>
<li>
		تثبيت الاعتماديات باستخدام الأمر <code>npm install</code>.
	</li>
	<li>
		تشغيل الشيفرة في وضع التطوير.
	</li>
	<li>
		إجراء الاختبارات.
	</li>
	<li>
		تدقيق lint الشيفرة.
	</li>
</ul>
<p>
	قد تلاحظ وجود بعض أخطاء التدقيق والاختبارات المخفقة في المشروع، دعها كما هي حاليًا، إذ سنتكفل بحلها في التمارين القادمة.
</p>

<p>
	وكما ذكرنا في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-nodejs-%D9%88express-r1099/" rel="">القسم 3</a>، لا ينبغي تشغيل شيفرة <a href="https://academy.hsoub.com/programming/javascript/react/%D9%85%D8%A7-%D9%87%D9%8A-react%D8%9F-r773/" rel="">React</a> في وضع التطوير بعد نشرها في وضع الإنتاج، لذلك حاول أن تُنفِّذ ما يلي:
</p>

<ul>
<li>
		أنشئ نسخة إنتاج من المشروع.
	</li>
	<li>
		شغل نسخة الإنتاج على جهازك محليًا.
	</li>
</ul>
<p>
	ستجد سكربتًا خاصًا لتنفيذ المهمتين السابقتين في المشروع.
</p>

<p>
	خذ وقتًا لدراسة هيكيلية المشروع.
</p>

<p>
	وكما ستلاحظ، فإن الواجهتين الأمامية والخلفية موجودتان في نفس <a href="https://fullstackopen.com/en/part7/class_components_miscellaneous#frontend-and-backend-in-the-same-repository" rel="external nofollow">المستودع</a>، وقد اعتدنا في الأقسام الأولى على وجودهما في مستودعين منفصلين. لكن هذا الأمر سيبسط الأمور عند إعداد بيئة CI.
</p>

<p>
	ستجد بالمقابل أنّ الواجهة الأمامية في معظم مشاريع منهاجنا لم تُنفّذ باستخدام "create-react-app"، لكنها تمتلك إعدادات تهيئة بسيطة باستخدام <a href="https://fullstackopen.com/en/part7/webpack" rel="external nofollow">webpack</a> تتكفل بإنشاء بيئة التطوير وبإنشاء حزمة الإنتاج.
</p>

<h2>
	التعامل مع مخططات العمل
</h2>

<p>
	تُعد <a href="https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/introduction-to-github-actions#workflows" rel="external nofollow">مخططات العمل</a> الأساس الذي تبنى عليه منظومات CI في غيت هب GitHub، فهي مساراتٌ لعملية يمكنك إعدادها لتنفيذ مهام تلقائيًا، مثل بناء التطبيقات واختبارها وتدقيقها وإصدارها ونشرها. تبدو عادةً هيكلية مخططات العمل على النحو التالي:
</p>

<p>
	مخطط العمل
</p>

<ul>
<li>
		مهمة
	</li>
	<li>
		خطوة
	</li>
	<li>
		خطوة
	</li>
	<li>
		مهمة
	</li>
	<li>
		خطوة
	</li>
</ul>
<p>
	ينبغي أن يحدد كل مخطط <a href="https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/introduction-to-github-actions#jobs" rel="external nofollow">مهمةً</a> واحدةً على الأقل تحتوي على <a href="https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/introduction-to-github-actions#steps" rel="external nofollow">خطوات</a> لتنفيذ إجراءات مستقلة. ستُشغّل هذه المهمات على التوازي بينما تُنفًّذ الخطوات في كل مهمة على التتالي.
</p>

<p>
	تتنوع الخطوات من تنفيذ سطر أوامر محدد إلى الأفعال المعرَّفة مسبقًا، لهذا تحمل الاستضافة اسم أفعال غيت هب "GitHub Actions". يمكنك إنشاء <a href="https://docs.github.com/en/free-pro-team@latest/actions/creating-actions" rel="external nofollow">الأفعال الخاصة بك</a> أو أن تستخدم أفعالُا نشرها آخرون، وهي في الواقع كثيرة، وسنعود إليها لاحقًا.
</p>

<p>
	ينبغي أن تُحدِّد مخططات العمل الخاصة بك ضمن المجلد "github/workflows." في مستودعك كي يميزها غيت هب، ولكل مخطط عمل ملفٌ مستقلٌ خاصٌ به والذي يجب تهيئته باستخدام لغة تقسيم البيانات YAML.
</p>

<p>
	يُشتق الاسم YAML من العبارة "YAML Ain't Markup Language" والتي تعني "ليست لغة توصيف"؛ فكما تُلمّح إليه التسمية، فالهدف منها أن تكون مفهومةً للبشر. تُستخدم هذه اللغة في كتابة ملفات التهيئة، وستجد أنها سهلة الفهم فعلًا.
</p>

<p>
	عليك الانتباه إلى أهمية الانزياحات Indentation في بداية الأسطر البرمجية في YAML، وللاطلاع أكثر حول قواعد اللغة يمكنك الرجوع إلى <a href="https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html" rel="external nofollow">شبكة الإنترنت</a>.
</p>

<p>
	يتضمن مخطط العمل في مستند YAML هذه العناصر الثلاث:
</p>

<ul>
<li>
		name الاسم: ويشير إلى اسم المخطط.
	</li>
	<li>
		triggers المُسبب: الأحداث التي تؤدي إلى تنفيذ المخطط.
	</li>
	<li>
		jobs المهام: المهام المختلفة التي سيُنفّذها المخطط، وقد يحتوي المخطط البسيط مهمةً واحدةً فقط.
	</li>
</ul>
<p>
	سيبدو تعريف مخطط بسيط على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_3090_21" style="">
<span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Hello</span><span class="pln"> </span><span class="typ">World</span><span class="pun">!</span><span class="pln">

on</span><span class="pun">:</span><span class="pln">
  push</span><span class="pun">:</span><span class="pln">
    branches</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> master

jobs</span><span class="pun">:</span><span class="pln">
  hello_world_job</span><span class="pun">:</span><span class="pln">
    runs</span><span class="pun">-</span><span class="pln">on</span><span class="pun">:</span><span class="pln"> ubuntu</span><span class="pun">-</span><span class="lit">20.04</span><span class="pln">
    steps</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Say</span><span class="pln"> hello
        run</span><span class="pun">:</span><span class="pln"> </span><span class="pun">|</span><span class="pln">
          echo </span><span class="str">"Hello World!"</span></pre>

<p>
	وكما هو واضح، سيكون المُسبب هو عملية دفع الشيفرة إلى الفرع الرئيسي. يحتوي المخطط أيضًا على مهمة واحدة اسمها <code>hello_world_job</code> سيجري تنفيذها ضمن بيئة افتراضية تعمل على نظام التشغيل أوبنتو Ubuntu 20.04، وتتضمن هذه المهمة خطوة واحدة اسمها <code>Say hello</code> ستُنفِّذ الأمر <code>"!echo "Hello World</code> ضمن الصدفة shell.
</p>

<p>
	ربما تتساءل، متى يبدأ غيت هب بتنفيذ مخطط معين؟ هناك العديد من <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows" rel="external nofollow">الخيارات</a> المتاحة، لكن وبصراحة، يمكنك أن تهيئ المخطط ليعمل حالما:
</p>

<ul>
<li>
		يقع حدث ما، مثل الحالة التي يدفع فيها أحدهم اعتمادًا commit إلى مستودع، أو عندما تحدث مشكلة أو يقدم أحدهم طلب سحب pull request.
	</li>
	<li>
		تُنفَّذ مهمةٌ مجدولةٌ مسبقًا ومحددة باستخدام تعابير <a href="https://academy.hsoub.com/devops/linux/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%AC%D8%AF%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%85%D9%87%D8%A7%D9%85-%D8%B9%D8%A8%D8%B1-cron-r412/" rel="">cron</a>.
	</li>
	<li>
		يقع حدث خارجي، كأن يُنفَّذ أمر ضمن تطبيق خارجي، مثل تطبيق الرسائل <a href="https://slack.com/" rel="external nofollow">Slack</a>.
	</li>
</ul>
<p>
	وللاطلاع على مزيدٍ من الأحداث المسببة لبدء تنفيذ مخطط عمل، عُد إلى <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows" rel="external nofollow">توثيق</a> GitHub Actions.
</p>

<h2>
	التمرينان 11.3- 11.4
</h2>

<p>
	لنربط كل ما خلصنا إليه، دعونا نشغِّل GitHub Actions ضمن المشروع النموذجي.
</p>

<h3>
	11.3: تطبيق Hello world
</h3>

<p>
	أنشئ مخططًا يُظهر العبارة "!Hello World" للمستخدم. عليك أن تنشئ المجلد "github/workflows."، والملف "hello.yml" الذي سيحتوي الإعدادات ضمن مستودعك.
</p>

<p>
	ولتطلع على ما أنجزه مخطط عمل GitHub Actions الخاص بك، يمكن التوجه إلى النافذة <strong>Action</strong> ضمن واجهة غيت هب GitHub، حيث من المفترض أن ترى مخطط العمل في مستودعك والخطوات التي أنجزها. ستبدو نتيجة تنفيذ مخططك على النحو التالي إن كانت تهيئة المخطط صحيحة:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105717" href="https://academy.hsoub.com/uploads/monthly_2022_08/workflows_output_03.png.ab358ae7fa60ed4aaf707d81b3dcdf0e.png" rel=""><img alt="workflows_output_03.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105717" data-unique="vjj64rmlc" src="https://academy.hsoub.com/uploads/monthly_2022_08/workflows_output_03.png.ab358ae7fa60ed4aaf707d81b3dcdf0e.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	يُفترض أن ترى الرسالة "!Hello World" نتيجةً لتنفيذ المخطط، وإذا حدث ذلك، ستكون جميع الخطوات قد نُفِّذت بنجاح، وسيكون أول مخططات عمل GitHub Actions الخاصة بك قيد العمل.
</p>

<p>
	سيعطيك أيضًا GitHub Actions معلومات عن البيئة (<a href="https://academy.hsoub.com/files/24-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D9%84%D9%84%D9%85%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D9%86/" rel="">نظام التشغيل</a>، و<a href="https://github.com/actions/virtual-environments/blob/ubuntu18/20201129.1/images/linux/Ubuntu1804-README.md" rel="external nofollow">إعداداته</a>) التي يعمل ضمنها مخطط العمل. وتظهر أهمية ذلك أثناء حدوث أمر طارئ، إذ سيسهل هذا الأمر تنقيح الأخطاء إن استطعت تكرار كل الخطوات على جهازك.
</p>

<h3>
	11.4: التاريخ ومحتويات المجلد
</h3>

<p>
	وسًع مخطط العمل لإضافة خطوات لطباعة التاريخ ومحتويات المجلد الحالي بالصياغة الطويلة long format. يمكن تنفيذ هاتين الخطوتين باستخدام الأمرين <a href="https://man7.org/linux/man-pages/man1/date.1.html" rel="external nofollow">date</a> و <a href="https://man7.org/linux/man-pages/man1/ls.1.html" rel="external nofollow">ls</a> بكل بساطة.
</p>

<p>
	سيبدو مخطط عملك الآن على النحو التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105712" href="https://academy.hsoub.com/uploads/monthly_2022_08/extended_workflow_04.png.8a5f2fbd26ba229bd3055deece1f91ea.png" rel=""><img alt="extended_workflow_04.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105712" data-unique="hwk1vr9ew" src="https://academy.hsoub.com/uploads/monthly_2022_08/extended_workflow_04.png.8a5f2fbd26ba229bd3055deece1f91ea.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	وطالما أن الأمر <code>ls -l</code> سيُظهر افتراضيًا البيئة الافتراضية التي يعمل عليها مخطط العمل، ستلاحظ أنها لا تحتوي على أية شيفرات.
</p>

<h2>
	إعداد وتجهيز خطوات التدقيق والاختبار والبناء
</h2>

<p>
	بعد إنجاز التمارين الأولى، سيكون لدينا مخطط عمل بسيط لكنه لن يفيدنا في عملية الإعداد. لنجعل مخطط العمل قادرًا على فعل مهمة حقيقية.
</p>

<p>
	سننجز فعلًا action قادرًا على تدقيق الشيفرة. فإذا أخفق التدقيق، سيُظهر Github Actions الحالة الحمراء.
</p>

<p>
	سيبدو المخطط الذي سنخزنه ضمن الملف "pipeline.yml" مبدئيًا على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_3090_17" style="">
<span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Deployment</span><span class="pln"> pipeline

on</span><span class="pun">:</span><span class="pln">
  push</span><span class="pun">:</span><span class="pln">
    branches</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> master

jobs</span><span class="pun">:</span></pre>

<p>
	قبل أن نتمكن من تنفيذ الأمر الذي سيبدأ خطوة التدقيق، لا بدّ من تنفيذ فعلين لإعداد البيئة الملائمة للمهمة.
</p>

<h3>
	إعداد بيئة العمل
</h3>

<p>
	إعداد بيئة العمل أمرٌ ضروري لتهيئة خط الإنتاج Pipeline، لذلك سنستخدم بيئة العمل الافتراضية Ubuntu 20.04 لأنها ستكون البيئة التي ستعمل عليها نسخة الإنتاج.
</p>

<p>
	ولا بدّ من تطابق البيئة نفسها في منظومة CI وفي وضع الإنتاج قدر المستطاع، لتحاشي الحالات التي تعمل فيها الشيفرة نفسها بصورةٍ مختلفة في كليهما، وبالتالي لن تتحقق الغاية من استخدام CI.
</p>

<p>
	سنوضح تاليًا الخطوات في مهمة "البناء" لكي تستطيع منظومة CI تنفيذها. وكما لاحظنا في التمارين السابقة، لن تحتوي بيئة العمل افتراضيًا أية شيفرات، لذلك لا بدّ من التحقق من الشيفرة من المستودع.
</p>

<p>
	هذه الخطوة سهلة وهي على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-ruby prettyprinted" id="ips_uid_3090_19" style="">
<span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Deployment</span><span class="pln"> pipeline

on</span><span class="pun">:</span><span class="pln">
  push</span><span class="pun">:</span><span class="pln">
    branches</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> master

jobs</span><span class="pun">:</span><span class="pln">
  simple_deployment_pipeline</span><span class="pun">:</span><span class="pln">    
    runs</span><span class="pun">-</span><span class="pln">on</span><span class="pun">:</span><span class="pln"> ubuntu</span><span class="pun">-</span><span class="lit">20.04</span><span class="pln">
    steps</span><span class="pun">:</span><span class="pln">      
     </span><span class="pun">-</span><span class="pln"> uses</span><span class="pun">:</span><span class="pln"> actions</span><span class="pun">/</span><span class="pln">checkout@v3</span></pre>

<p>
	تُستخدم التعليمة <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsuses" rel="external nofollow">uses</a> لإخبار المخطط أن يُنفِّذ فعلًا محددًا. ويُعرَّف الفعل بأنه قطعة من الشيفرة قابلة لإعادة الاستخدام مثل الدوال، ويمكن تعريف الأفعال داخل المستودع ضمن ملف مستقل، أو استخدام الأفعال الموجودة في المستودعات العامة.
</p>

<p>
	سنستخدم هنا الفعل العام <a href="https://github.com/actions/checkout" rel="external nofollow">actions/checkout</a> وبنسخة محددة هي "v3@" لتلافي فشل التعديلات إن جرى تحديث الفعل. ويُنفِّذ الفعل <code>checkout</code> ما يوحي به اسمه، إذ يتحقق من شيفرة المشروع المصدرية من git.
</p>

<p>
	وطالما أنّ التطبيق قد كتب باستخدام <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-javascript-r550/" rel="">جافا سكربت</a>، فلا بدّ من تهيئة "Node.js" لكي نتمكن من استخدام الأوامر الموجودة في الملف "package.json". ولإعداد "Node.js" يمكن استخدام الفعل <a href="https://github.com/actions/setup-node" rel="external nofollow">actions/setup-node</a>. سنختار الإصدار "16" لأنه الإصدار الذي تستخدمه بيئة الإنتاج.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3090_25" style="">
<span class="pun">#</span><span class="pln"> name and trigger not shown anymore</span><span class="pun">...</span><span class="pln">

jobs</span><span class="pun">:</span><span class="pln">
  simple_deployment_pipeline</span><span class="pun">:</span><span class="pln">
    runs</span><span class="pun">-</span><span class="pln">on</span><span class="pun">:</span><span class="pln"> ubuntu</span><span class="pun">-</span><span class="lit">20.04</span><span class="pln">
    steps</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> uses</span><span class="pun">:</span><span class="pln"> actions</span><span class="pun">/</span><span class="pln">checkout@v3
      </span><span class="pun">-</span><span class="pln"> uses</span><span class="pun">:</span><span class="pln"> actions</span><span class="pun">/</span><span class="pln">setup</span><span class="pun">-</span><span class="pln">node@v2        
        </span><span class="kwd">with</span><span class="pun">:</span><span class="pln">          
        node</span><span class="pun">-</span><span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">'16'</span></pre>

<p>
	تُستخدم التعليمة <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswith" rel="external nofollow">with</a> لإسناد معامل parameter إلى الفعل، إذ يحدد المعامل هنا نسخة <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-nodejs-r1463/" rel="">Node.js</a> التي ننوي استخدامها.
</p>

<p>
	أخيرًا، لا بُد من تثبيت الاعتماديات اللازمة. وكما تفعل عادةً على جهازك، نفِّذ الأمر <code>npm install</code>. ستبدو خطوات المهمة على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3090_29" style="">
<span class="pln">jobs</span><span class="pun">:</span><span class="pln">
  simple_deployment_pipeline</span><span class="pun">:</span><span class="pln">
    runs</span><span class="pun">-</span><span class="pln">on</span><span class="pun">:</span><span class="pln"> ubuntu</span><span class="pun">-</span><span class="lit">20.04</span><span class="pln">
    steps</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> uses</span><span class="pun">:</span><span class="pln"> actions</span><span class="pun">/</span><span class="pln">checkout@v3
      </span><span class="pun">-</span><span class="pln"> uses</span><span class="pun">:</span><span class="pln"> actions</span><span class="pun">/</span><span class="pln">setup</span><span class="pun">-</span><span class="pln">node@v2
        </span><span class="kwd">with</span><span class="pun">:</span><span class="pln">
          node</span><span class="pun">-</span><span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">'16'</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> npm install
        run</span><span class="pun">:</span><span class="pln"> npm install</span></pre>

<p>
	وهكذا ستكون البيئة الآن جاهزةً لأداء مهمة حقيقية ومهمة.
</p>

<h3>
	التدقيق Lint
</h3>

<p>
	يمكنك الآن تنفيذ السكربتات التي يحتويها الملف "package.json" كما لو أنك تنفذها على حاسوبك الشخصي، فكل ما عليك فعله لتدقيق الشيفرة، هو إضافة إعدادٍ لتشغيل الأمر <code>npm run eslint</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3090_31" style="">
<span class="pln">jobs</span><span class="pun">:</span><span class="pln">
  simple_deployment_pipeline</span><span class="pun">:</span><span class="pln">
    runs</span><span class="pun">-</span><span class="pln">on</span><span class="pun">:</span><span class="pln"> ubuntu</span><span class="pun">-</span><span class="lit">20.04</span><span class="pln">
    steps</span><span class="pun">:</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> uses</span><span class="pun">:</span><span class="pln"> actions</span><span class="pun">/</span><span class="pln">checkout@v3
      </span><span class="pun">-</span><span class="pln"> uses</span><span class="pun">:</span><span class="pln"> actions</span><span class="pun">/</span><span class="pln">setup</span><span class="pun">-</span><span class="pln">node@v2
        </span><span class="kwd">with</span><span class="pun">:</span><span class="pln">
          node</span><span class="pun">-</span><span class="pln">version</span><span class="pun">:</span><span class="pln"> </span><span class="str">'16'</span><span class="pln">
      </span><span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> npm install 
        run</span><span class="pun">:</span><span class="pln"> npm install  
      </span><span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> lint        
        run</span><span class="pun">:</span><span class="pln"> npm run eslint</span></pre>

<h2>
	التمرينات 11.5 - 11.9
</h2>

<h3>
	11.5: تدقيق مخطط عمل
</h3>

<p>
	نفّذ أو انسخ والصق مخطط العمل "Lint" واعتمد شيفرته في مستودعك، ثم أنشئ ملف yml جديد لهذا المخطط، يمكنك تسميته "pipeline.yml".
</p>

<p>
	ادفع بشيفرتك إلى الفرع الرئيسي ثم توجه إلى النافذة "ِActions" وانقر على المخطط الذي أنشأته على اليسار. ستجد أن هذا المخطط سيفشل في الإقلاع:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105714" href="https://academy.hsoub.com/uploads/monthly_2022_08/Lint_workflow_failed_05.png.05630fc25d017d5bced9275f60e22aa5.png" rel=""><img alt="Lint_workflow_failed_05.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105714" data-unique="uq7eow4c1" src="https://academy.hsoub.com/uploads/monthly_2022_08/Lint_workflow_failed_05.png.05630fc25d017d5bced9275f60e22aa5.png" style="width: 750px; height: auto;"></a>
</p>

<h3>
	11.6: إصلاح الشيفرة
</h3>

<p>
	ستجد بعض المشاكل في الشيفرة تتطلب الإصلاح. افتح سِجِل مخطط العمل وتحقق من وجود أخطاء.
</p>

<p>
	إليك تليمحين مهمين: من الأفضل أن تصلح أحد الأخطاء بتحديد ملف مناسب لعملية التدقيق (راجع <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-nodejs-%D9%88express-r1099/" rel="">القسم الثالث</a> لمزيد من المعلومات عن كيفية تنفيذ ذلك)؛ كما يمكن إيقاف أحد الاعتراضات التي تخص تنفيذ الأمر <code>console.log</code> بإيقاف القاعدة التي تسببه في السطر الذي توجد به.
</p>

<p>
	أجرِ التعديلات اللازمة على الشيفرة المصدرية لكي ينجح مخطط العمل في الإقلاع، وحالما تعتمد الشيفرة الجديدة سيعمل المخطط وستجد خرجًا مُحدَّثًا باللون الأخضر من جديد.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105715" href="https://academy.hsoub.com/uploads/monthly_2022_08/lint_workflow_starts_06.png.c93a688e631ecee873afedfcfa4fd232.png" rel=""><img alt="lint_workflow_starts_06.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105715" data-unique="yg8swzaya" src="https://academy.hsoub.com/uploads/monthly_2022_08/lint_workflow_starts_06.png.c93a688e631ecee873afedfcfa4fd232.png" style="width: 750px; height: auto;"></a>
</p>

<h3>
	11.7: البناء والاختبار
</h3>

<p>
	لنوسِّع مخطط العمل الذي يُنفِّذ حاليًا مهمة التدقيق. أضف أوامر البناء والاختبار إلى مخطط العمل، وستبدو النتيجة قريبةً من التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105719" href="https://academy.hsoub.com/uploads/monthly_2022_08/workflow_test_07.png.c1f1616ed59c08d7398ef4986279989b.png" rel=""><img alt="workflow_test_07.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105719" data-unique="6ha8m2q9m" src="https://academy.hsoub.com/uploads/monthly_2022_08/workflow_test_07.png.c1f1616ed59c08d7398ef4986279989b.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	ستلاحظ أيضًا وجود بعض المشاكل.
</p>

<h3>
	11.8: العودة إلى الوضع الصحيح
</h3>

<p>
	تحقق من الاختبارات التي أخفقت وأصلح المشكلة في الشيفرة (لا تُغيّر الاختبارات). سيعمل المخطط ويظهر الخرج باللون الأخضر مجددًا عند إصلاح المشاكل.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105720" href="https://academy.hsoub.com/uploads/monthly_2022_08/workflow_test_works_08.png.7ef51d42115943f2cc5c88ae7f66773e.png" rel=""><img alt="workflow_test_works_08.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105720" data-unique="3g1gwaf9a" src="https://academy.hsoub.com/uploads/monthly_2022_08/workflow_test_works_08.png.7ef51d42115943f2cc5c88ae7f66773e.png" style="width: 750px; height: auto;"></a>
</p>

<h3>
	11.9: اختبار مشترك بسيط للواجهتين
</h3>

<p>
	تستخدم مجموعة الاختبارات الحالية <a href="https://jestjs.io/" rel="external nofollow">jest</a> لتتأكد من عمل مكونات <a href="https://wiki.hsoub.com/React" rel="external">React</a> بالطريقة المطلوبة، وهذا تمامًا ما فعلناه في فصل "اختبار تطبيقات React" من <a href="https://academy.hsoub.com/programming/javascript/react/%D8%AA%D8%B3%D8%AC%D9%8A%D9%84-%D8%A7%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D9%81%D9%8A-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-r1136/" rel="">القسم 5</a>.
</p>

<p>
	يُعد اختبار كل مكوًن بصورةٍ منفصلة أمرًا مفيدًا لكنه لا يضمن عمل كل النظام بالطريقة المطلوبة. وللتأكد أكثر، سنكتب اختبارًا بسيطًا مشتركًا بين الواجهتين الأمامية والخلفية end to end testing، مستخدمين المكتبة <a href="https://www.cypress.io/" rel="external nofollow">Cypress</a> وعلى نحوٍ مشابه للعمل الذي أنجزناه في الفصل الذي يحمل العنوان "الاختبار المشترك للواجهتين" في القسم 5.
</p>

<p>
	سنهيئ cypress (ستجد الطريقة في الفصل الذي أشرنا إليه)، ثم سننفذ الاختبار التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3090_36" style="">
<span class="pln">describe</span><span class="pun">(</span><span class="str">'Pokedex'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  it</span><span class="pun">(</span><span class="str">'front page can be opened'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    cy</span><span class="pun">.</span><span class="pln">visit</span><span class="pun">(</span><span class="str">'http://localhost:5000'</span><span class="pun">)</span><span class="pln">
    cy</span><span class="pun">.</span><span class="pln">contains</span><span class="pun">(</span><span class="str">'ivysaur'</span><span class="pun">)</span><span class="pln">
    cy</span><span class="pun">.</span><span class="pln">contains</span><span class="pun">(</span><span class="str">'Pokémon and Pokémon character names are trademarks of Nintendo.'</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">})</span></pre>

<p>
	عرِّف سكربت npm التالي: <code>test:e2e</code> لتشغيل الاختبار المشترك للواجهتين "e2e" باستخدام سطر الأوامر.
</p>

<p>
	<strong>تنبيه</strong>: لا تستخدم الكلمة "spec" في تسمية ملف اختبار cypress، لأنها ستجعل jest ينفذ شيفرة الملف أيضًا، وقد يسبب ذلك عدة مشاكل.
</p>

<p>
	<strong>تنبيه آخر</strong>: على الرغم من قدرة الصفحة على تصيير أسماء "مخلوقات البوكيمون Pokemon" لتبدأ بأحرف كبيرة، إلا أنها مكتوبةٌ بأحرف صغيرة في المصدر، فاسم البوكيمون "Ivysaur" هو أصلًا "ivysaur"
</p>

<p>
	تحقق من نجاح الاختبار على جهازك، وتذكر أن الاختبارات ستفترض أن تطبيقك يعمل عندما تُنفِّذ الاختبار. وإن كنت قد نسيت بعض التفاصيل، راجع القسم 5.
</p>

<p>
	حالما يعمل الاختبار المشترك للواجهتين على جهازك، ضعه في مخطط عمل GitHub Action، وأسهل الطرق لتنفيذ ذلك هو استخدام الفعل الجاهز <a href="https://github.com/cypress-io/github-action" rel="external nofollow">cypress-io/github-action</a>، وستكون الخطوة المناسبة لحالتنا على النحو التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3090_38" style="">
<span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> e2e tests
  uses</span><span class="pun">:</span><span class="pln"> cypress</span><span class="pun">-</span><span class="pln">io</span><span class="pun">/</span><span class="pln">github</span><span class="pun">-</span><span class="pln">action@v2
  </span><span class="kwd">with</span><span class="pun">:</span><span class="pln">
    command</span><span class="pun">:</span><span class="pln"> npm run test</span><span class="pun">:</span><span class="pln">e2e
    start</span><span class="pun">:</span><span class="pln"> npm run start</span><span class="pun">-</span><span class="pln">prod
    wait</span><span class="pun">-</span><span class="pln">on</span><span class="pun">:</span><span class="pln"> http</span><span class="pun">:</span><span class="com">//localhost:5000</span></pre>

<p>
	لقد استخدمنا ثلاثة خيارات:
</p>

<ul>
<li>
		<a href="https://github.com/cypress-io/github-action#custom-test-command" rel="external nofollow">command</a>: ويحدد كيف سنشغل اختبار cypress.
	</li>
	<li>
		<a href="https://github.com/cypress-io/github-action#start-server" rel="external nofollow">start</a>: يزودنا بسكربت npm الذي يُشغِّل الخادم.
	</li>
	<li>
		<a href="https://github.com/cypress-io/github-action#wait-on" rel="external nofollow">wait-on</a>: يمنع الاختبار من العمل قبل أن يقلع <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخادم</a> على العنوان <a href="http://localhost:5000/" rel="external nofollow">http://localhost:5000</a>.
	</li>
</ul>
<p>
	عندما تتأكد من عمل خط الإنتاج، اكتب اختبارًا آخر للتأكد أنّ المستخدم قادرٌ على الانتقال من الصفحة الرئيسية إلى صفحة "بوكيمون" محدد مثل "ivysaur". لا حاجة لتعقيد الاختبار، بل تأكد فقط أن الصفحة التي تنتقل إليها بنقر الرابط، ستحتوي بعض المعلومات الصحيحة مثل النص "chlorophyll" في حالة البوكيمون "ivysaur".
</p>

<p>
	<strong>ملاحظة:</strong> تُكتب أسماء البوكيمون بالأحرف الصغيرة، ويمكن الكتابة بالأحرف الكبيرة في <a href="https://academy.hsoub.com/programming/css/%d8%aa%d8%b9%d8%b1%d9%91%d9%81-%d8%b9%d9%84%d9%89-%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a7%d8%aa-css-r70/" rel="">CSS</a>، لذلك لا تكتب أثناء البحث الاسم "Chlorophyll"، بل اكتب "chlorophyll".
</p>

<p>
	<strong>ملاحظة:</strong> لا تحاول الانتقال إلى صفحة البوكيمون "bulbasaur"، فربما لسببٍ ما لن تعمل صفحة هذا البوكيمون.
</p>

<p>
	ستبدو النتيجة النهائية على الشكل التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105718" href="https://academy.hsoub.com/uploads/monthly_2022_08/workflow_e2e_test_09.png.7299ea431725608a336ca9d1bc5e20b3.png" rel=""><img alt="workflow_e2e_test_09.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105718" data-unique="v1e7efxxt" src="https://academy.hsoub.com/uploads/monthly_2022_08/workflow_e2e_test_09.png.7299ea431725608a336ca9d1bc5e20b3.png" style="width: 750px; height: auto;"></a>
</p>

<p>
	إن الأمر الجيد المتعلق بالاختبار المشترك للواجهتين هو الثقة بأنّ البرنامج سيعمل على نحوٍ جيد من وجهة نظر المستخدم النهائي. لكن سيكون الثمن الذي تدفعه بالمقابل هو الاستجابة البطيئة، إذ سيستغرق تنفيذ مخطط العمل بالكامل وقتًا أطول بكثير.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part11/getting_started_with_git_hub_actions" rel="external nofollow">Getting started with GitHub Actions</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/devops/deployment/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd-r644/" rel="">مدخل إلى التكامل المستمر والنشر المستمر CI/CD</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-concourse-ci-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-r356/" rel="">التكامل المستمر: تثبيت Concourse CI على أوبنتو</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D8%AF%D9%85%D8%AA%D9%8A%D9%86-circleci-%D9%88coveralls-r1276/" rel="">إعداد التكامل المستمر والنشر المستمر باستخدام الخدمتين CircleCI وCoveralls</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D8%B9%D8%A7%D8%AF%D8%A9-%D8%AA%D8%A3%D8%B3%D9%8A%D8%B3-%D8%AA%D9%81%D8%B1%D9%8A%D8%B9%D8%A7%D8%AA-%D8%B7%D9%84%D8%A8-%D8%A7%D9%84%D8%B3%D8%AD%D8%A8-%D9%88%D8%AA%D8%AD%D8%AF%D9%8A%D8%AB%D9%87-%D9%81%D9%8A-git-r1584/" rel="">إعادة تأسيس تفريعات طلب السحب وتحديثه في git</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">645</guid><pubDate>Wed, 24 Aug 2022 17:09:00 +0000</pubDate></item><item><title>&#x645;&#x62F;&#x62E;&#x644; &#x625;&#x644;&#x649; &#x627;&#x644;&#x62A;&#x643;&#x627;&#x645;&#x644; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631; &#x648;&#x627;&#x644;&#x646;&#x634;&#x631; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631; CI/CD</title><link>https://academy.hsoub.com/devops/deployment/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-cicd-r644/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_08/Introduction-to-CICD.png.474d8d9acecc8318a8df8728b176bcd8.png" /></p>

<p>
	سنبني خلال هذا المقال خط إنتاج Pipeline لنشر <a href="https://github.com/smartlyio/full-stack-open-pokedex" rel="external nofollow">المشروع النموذجي</a> الذي بدأناه في التمرين 11.2. إذ <a href="https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/fork-a-repo" rel="external nofollow">سننشئ نسخة</a> خاصةً بنا من مستودع المشروع لكي نتعامل معها، ثم سنبني في آخر تمرينين خط إنتاج آخر لنشر بعض التطبيقات الخاصة بنا والتي بنيناها سابقًا.
</p>

<p>
	يضم القسم 21 تمرينًا من المفترض إكمالها جميعًا حتى تنهي هذا المنهاج، وعليك أن تسلم حلول هذه التمرينات إلى <a href="https://studies.cs.helsinki.fi/stats/courses/fs-cicd" rel="external nofollow">منظومة تسليم التمارين</a> بنفس الأسلوب المُتبع في القسم السابق، إذ تُسلَّم التمرينات إلى نسخة مختلفة من المنهاج.
</p>

<p>
	ستعتمد المعلومات في هذا القسم على مفاهيم اطلعنا عليها سابقًا، لذلك ننصحك بإكمال الأقسام 0-5 قبل البدء بهذا القسم.
</p>

<p>
	لن تكتب الكثير من أسطر الشيفرة في هذا القسم، فهو يغطي في معظم الأوقات مواضيع عن التهيئة. وعلى الرغم من أن تنقيح الشيفرة صعب، لكن تنقيح أوامر التهيئة أصعب بكثير، فعليك إذًا التحلي بالصبر والمثابرة في هذا القسم.
</p>

<h2>
	تجهيز التطبيقات لمرحلة الإنتاج
</h2>

<p>
	ستضطر بعد الانتهاء من كتابة الشيفرة عاجلًا أم آجلًا إلى نقل تطبيقك إلى مرحلة الإنتاج، أي إلى مستخدميه الحقيقيين، ثم ستضطر بعدها إلى صيانة هذا التطبيق باستمرار وإصدار نسخٍ جديدة منه والعمل مع مطورين آخرين لتوسيعه.
</p>

<p>
	لقد استخدمنا حاليًا غيت هب GitHub لتخزين الشيفرة المصدرية، لكن ما الحل عندما نعمل ضمن فريق من المطورين؟
</p>

<p>
	ستظهر عدة مشاكل عند تعاون عدة مطورين، فقد يعمل التطبيق جيدًا على حاسوبي، لكن قد يخفق على حواسيب مطورين آخرين يعملون على أنظمة تشغيل مختلفة، أو يستخدمون نسخًا مختلفة من المكتبات التي أدرجناها في التطبيق. يُشار إلى هذه المشكلة بالعبارة "التطبيق يعمل على جهازي works on my machine".
</p>

<p>
	هناك مشكلة أخرى، فلو أجرى مطورين تعديلات على شيفرة التطبيق ولم يتفقا من سينشر التعديلات التي أنجزها، من سيمنع أحدهما من محي تعديلات الآخر وكتابة تعديلاته مكانها؟
</p>

<p>
	سنغطي في هذا القسم أساليب العمل المشترك لبناء ونشر التطبيقات بطريقة منضبطة ومحددة جيدًا، يظهر فيها بكل وضوح ما الذي سيحدث وتحت أية ظروف.
</p>

<h2>
	بعض المصطلحات المفيدة
</h2>

<p>
	نستخدم في هذا القسم مصطلحات قد لا تعرفها أو لم تفهمها جيدًا، سنناقش هذه المصطلحات تاليًا، وحتى لو كنت على دراية بهذه المصطلحات، اطلع على هذه الفقرة لنكون على وفاق في فهم المصطلح عند وروده.
</p>

<h3>
	الفروع Branches
</h3>

<p>
	يسمح نظام غيت Git بوجود نسخ، أو مسارات streams، أو إصدارات مختلفة من الشيفرة تتعايش معًا دون الحاجة لإلغاء بعضها بعضًا؛ فعندما تُنشئ مستودعًا سيكون هذا المستودع بمثابة الفرع الرئيسي (ندعوه في غيت بالاسم "main" أو "master"، لكن الأمر مختلف في المشاريع القديمة). لا بأس بهذا الأسلوب إذا كان هناك مطور واحد يستخدم هذا الفرع، ويعمل على تطوير ميزة واحدة للتطبيق كل مرة.
</p>

<p>
	سيكون استخدام الفروع مفيدًا عندما تتعقد بيئة التطوير، إذ سيعمل كل مطور على فرع أو أكثر، وسيمثل كل فرع نسخةً مختلفةً قليلًا عن نسخة الفرع الرئيسي. وعندما تكتمل الميزة التي يجري تطويرها في أحد الفروع ستُدمج مع الفرع الرئيسيٍ، وبالتالي ستصبح هذه التغييرات أو الميزات الجديدة جزءًا من التطبيق الرئيسي. وهكذا سيتمكن كل مطور من العمل على مجموعة من التعديلات بحيث لا يعيق بقية المطورين من العمل على أفكارهم الخاصة قبل إكمال هذه التعديلات.
</p>

<p>
	لكن ما الذي سيحدث لبقية الفروع الخاصة بالمطورين الآخرين عندما يدمج أحد المطورين تعديلاته ضمن الفرع الرئيسي؟ إذ سيبتعد بقية المطورين عن النسخة القديمة التي يعملون عليها. وكيف سيعرف المطورون أن تعديلاتهم التي يجرونها على النسخة السابقة ستكون متوافقةً مع الحالة الجديدة التي يفرضها دمج التعديلات ضمن الفرع الرئيسي؟ سنحاول الإجابة عن هذا السؤال الجوهري في هذا القسم.
</p>

<p>
	يمكنك الاطلاع على مزيدٍ من المعلومات حول الفروع من خلال قراءة المقالة "<a href="https://academy.hsoub.com/programming/workflow/git/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D9%81%D8%B1%D9%8A%D8%B9-branching-%D9%88%D8%A7%D9%84%D8%AF%D9%85%D8%AC-merging-%D9%81%D9%8A-git-r276/" rel="">أساسيات التفريع (Branching) والدمج (Merging) في Git</a>".
</p>

<h3>
	طلبات السحب Pull requests
</h3>

<p>
	يجري عادةً دمج فرع ضمن الفرع الرئيسي وفق آلية تُعرف <a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B7%D9%84%D8%A8-%D8%B3%D8%AD%D8%A8-%D8%B9%D9%84%D9%89-github-r1581/" rel="">بطلب السحب</a>، أو كما يُعرف أحيانًا بالاختصار "PR"، إذ يطلب المطور الذي أجرى بعض التعديلات أن تُدمج التعديلات التي أجراها ضمن الفرع الرئيسي، وحالما يُقبل هذا الطلب أو يُفتتح للمراجعة، يمكن لمطور آخر أن يتحقق من أن كل شيء سيجري على مايرام بعد الدمج.
</p>

<p>
	لو اقترحت مثلًا تعديلًا على مادة منهجنا، فقد قدمت فعلًا طلب سحب.
</p>

<h3>
	بناء التطبيق Build
</h3>

<p>
	لهذا المصطلح معانٍ مختلفة في لغات البرمجة المختلفة. فلا حاجة في بعض اللغات المترجمة، مثل <a href="https://wiki.hsoub.com/Ruby" rel="external">روبي Ruby</a>، أو <a href="https://wiki.hsoub.com/Python" rel="external">بايثون Python</a> إلى خطوة بناء فعلية إطلاقًا.
</p>

<p>
	عندما نتكلم عن البناء عمومًا، فإننا نعني بذلك إعداد التطبيق ليعمل على المنصة التي صُمِّم لأجلها، وقد يعني ذلك، على سبيل المثال، أننا لو أردنا كتابة التطبيق بلغة <a href="https://wiki.hsoub.com/TypeScript" rel="external">TypeScript</a> ثم تنفيذه على Node، فستكون خطوة البناء هي عملية نقل شيفرة <a href="https://academy.hsoub.com/programming/javascript/typescript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-typescript-r1214/" rel="">TypeScript</a> إلى <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%B9-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-javascript-r550/" rel="">جافا سكربت JavaScript</a>.
</p>

<p>
	ستغدو هذه الخطوة إجبارية وأكثر تعقيدًا في اللغات المُصرَّفة، مثل C و Rust، إذ تتطلب شيفرتها التصريف إلى شيفرة قابلة للتنفيذ.
</p>

<p>
	وكنا قد اطلعنا في <a href="https://academy.hsoub.com/programming/javascript/react/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-react-router-r1166/" rel="">القسم 7</a> على برنامج <a href="https://webpack.js.org/" rel="external nofollow">webpack</a>، وهو أداةٌ رائدة حاليًا في بناء نسخ إنتاج من تطبيقات React أو تطبيقات واجهة أمامية مكتوبة بلغة جافاسكربت JavaScript أو TypeScript.
</p>

<h3>
	نشر التطبيقات Deploy
</h3>

<p>
	يشير مصطلح "النشر" إلى وضع التطبيق في المكان الذي يمكن للمستخدم النهائي الوصول إليه واستخدامه؛ فقد يعني هذا المصطلح بالنسبة للمكتبات مثلًا وضعها ضمن حزم ثم دفعها إلى أرشيف للحزم (مثل npmjs.com) بحيث يمكن للمستخدمين إيجادها وتضمينها في مشاريعهم.
</p>

<p>
	تختلف صعوبة نشر خدمة (مثل تطبيق ويب) حسب تعقيدها، فقد اشتمل مخططنا لنشر التطبيق في <a href="https://academy.hsoub.com/programming/javascript/nodejs/express/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-nodejs-%D9%88express-r1099/" rel="">القسم 3</a> مثلًا على تنفيذ بعض السكربتات يدويًا ثم دفع شيفرة المستودع إلى خدمة استضافة <a href="https://www.heroku.com/" rel="external nofollow">Heroku</a>.
</p>

<p>
	سنطور في هذا القسم "خط إنتاج" لنشر كل جزء من شيفرتك آليًا إلى Heroku إن لم تسبب هذه الشيفرة أية مشاكل.
</p>

<p>
	وقد يكون النشر أحيانًا على درجة عالية من التعقيد خاصةً إذا تطلب التطبيق احتياجات خاصة، كأن يكون متاحًا دائمًا للعمل أثناء تطويره "لا إيقاف من أجل النشر zero downtime deployment"، أو كان علينا أن نأخذ في الحسبان بعض الأمور مثل نقل قواعد البيانات. لن نغطي النقاط المتعلقة بالنشر الذي يحمل تعقيدات مثل التي ذكرناها، لكن من المهم أن تدرك وجودها.
</p>

<h2>
	ما هو التكامل المتواصل؟
</h2>

<p>
	يختلف المفهوم الدقيق لمصطلح التكامل المتواصل Continuous Integration -أو اختصارًا "CI"- عن كيفية استخدامه في مجال التطوير. ستجد نقاشًا قديمًا لكنه يحمل أثرًا كبيرًا عن استخدام هذا المصطلح في <a href="https://www.martinfowler.com/articles/continuousIntegration.html" rel="external nofollow">مدونة "مارتن فلور"</a>.
</p>

<p>
	وإذا أردنا التحديد الدقيق للمصطلح CI سنقول أنه يعني غالبًا "دمج التغييرات التي نفذها المطور مع الفرع الرئيسي" ثم أضافت ويكيبيديا Wikipedia العبارة "عدة مرات في اليوم" إلى المصطلح. وهذا الأمر صحيح عادةً، لكن عندما نشير إلى CI في مجال البرمجيات، فنحن نتحدث غالبًا عما سيحدث بعد عملية الدمج الفعلية للتغيرات، إذ قد نحتاج إلى إنجاز بعضٍ من هذه الخطوات:
</p>

<ul>
<li>
		التدقيق lint: للحفاظ على الشيفرة واضحة وقابلة للصيانة.
	</li>
	<li>
		البناء build: وضع كل الشيفرة المُنجزة في برنامج واحد.
	</li>
	<li>
		الاختبار test: لضمان عدم إخفاق أية ميزات موجودة مسبقًا.
	</li>
	<li>
		التحزيم packaging: وضع كل ما نحتاجه في حزمة سهل الحمل والنقل.
	</li>
	<li>
		التحميل/ النشر deploy: لجعل البرنامج متاحًا للاستخدام.
	</li>
</ul>
<p>
	سنناقش كل خطوة من تلك الخطوات (عندما نجد فائدةً من ذلك) بشيء من التفصيل لاحقًا. وعلينا أن نتذكر دائمًا أنّ هذه العملية ستكون محددةً بدقة.
</p>

<p>
	يعيق التحديد الدقيق strict definition عادةً سرعة التطوير أو الإبداع في إيجاد الحلول، لكن من المفترض أن لا يكون هذا صحيحًا بالنسبة للتكامل المتواصل، إذ يجب وضع هذه القيود بطريقة مرنة تسمح بالتطوير والعمل المشترك. سيسمح استخدام أنظمة CI جيدة، مثل GitHub Actions الذي سنغطيه في هذا القسم بتنفيذ كل ما ذكرناه آليًا وبصورة سحرية.
</p>

<h2>
	التحزيم والنشر مثل جزء من نظام التكامل المتواصل CI
</h2>

<p>
	ينبغي التذكير من حين إلى آخر، أنّ عملية التحزيم وعملية النشر خاصة لا تُعدان أحيانًا جزءًا من نظام CI، لكننا سنعدّهما جزءًا من النظام، لأنه من المنطقي في العالم الحقيقي أن نضع كل الخطوات التي أشرنا إليها معًا. ويعود جزءٌ من ذلك للحفاظ على التسلسل في سياق العمل وخط إنتاج البرنامج (الطريق إلى إيصال المنتج إلى المستخدم النهائي)، بينما يعود الأمر في شقه الآخر إلى حقيقة أنّ هذه النقطة هي ما يسبب إخفاق العملية ككل.
</p>

<p>
	ستظهر المشاكل غالبًا في مرحلة التحزيم من نظام CI كونها مرحلةٌ لا تختبر محليًا، فمن المنطقي إذًا اختبار عملية تحزيم المشروع خلال سير العمل في CI حتى لو لم نضع هذه الحزمة موضع العمل، كما يمكن في بعض مسارات العمل أن نختبر حزمةً قد بُنيت فعلًا مسبقًا. سيؤكد ذلك أننا اختبرنا الشيفرة بنفس الشكل الذي سننشرها فيه خلال مرحلة الإنتاج.
</p>

<p>
	إذًا ما هو وضع عملية النشر؟ سنتحدث عن الاستمرارية وإمكانية التكرار مطولًا في الفصول القادمة، لكن تجدر الإشارة هنا إلى أننا نحتاج إلى عملية تبدو متماثلةً دائمًا، سواءٌ أجرينا الاختبارات في فرع تطوير أو في الفرع الرئيسي. نحتاج في الواقع إلى العملية نفسها "حرفيًا" بحيث يظهر الاختلاف فقط في نهايتها، وذلك عند تحديد ما إذا كنا في الفرع الرئيسي أم لا، وإن كنا نريد النشر أم لا. وفي هذا السياق من المنطقي تضمين خطوة النشر في سير عمل CI طالما أنها ستخضع للصيانة في نفس الوقت الذي نطورها فيه.
</p>

<p>
	يشير المصطلحان: "التسليم المتواصل Continuous Delivery" و"النشر المتواصل Continuous Deployment" -أو اختصارًا CD- إلى منظومة CI عندما تتضمن مرحلة النشر. لن نزعجك بالتعريف الدقيق، إذ يمكنك الاطلاع عليه من خلال <a href="https://ar.wikipedia.org/wiki/%D8%AA%D8%B3%D9%84%D9%8A%D9%85_%D9%85%D8%B3%D8%AA%D9%85%D8%B1" rel="external nofollow">Wikipedia</a>، أو منشورات <a href="https://martinfowler.com/bliki/ContinuousDelivery.html" rel="external nofollow">مدونة "مارتن فلور"</a>، أو من <a href="https://academy.hsoub.com/questions/12096-%D9%85%D8%A7%D9%87%D9%8A-cicd/" rel="">خلال أكاديمية حسوب</a>، لكننا نستخدم الاختصار CD ليشير إلى أسلوب ممارسة عملي يقتضي إبقاء الفرع الرئيسي متاحًا للنشر والتطوير في أي وقت.
</p>

<p>
	لكن ماذا عن المنطقة الغامضة بين مصطلحي CI وCD؟ فلو كان علينا مثلًا أن نجري بعض الاختبارات قبل أن ندمج أية شيفرات جديدة ضمن الفرع الرئيسي، هل نعد ذلك شكلًا من أشكال CI لأننا سنجري دمجًا متواصلًا مع الفرع الرئيسي؟ أم شكلًا من أشكال CD لأننا نتحقق بذلك أن الفرع الرئيسي قابلًا للنشر باستمرار؟
</p>

<p>
	تقع بعض المفاهيم إذًا على الخط الفاصل بين المصطلحين، فمن المنطقي كما أشرنا سابقًا، أن نعد CD جزءًا من CI. لهذا السبب، سيُستخدم غالبًا المصطلح CI/CD للدلالة على العملية برمتها. وانتبه إلى أننا سنستخدم المصطلحين CI و CI/CD بالتناوب طوال الوقت في هذا القسم.
</p>

<h2>
	أين تكمن أهمية استخدام نظام CI؟
</h2>

<p>
	لقد ذكرنا سابقًا مشكلة "يعمل على جهازي" ومشكلة نشر تغييرات متعددة المصادر، لكن ماذا عن بقية المشاكل؟ ماذا لو حملّت "سارة" الشيفرة مباشرةً إلى الفرع الرئيسي؟ ماذا لو استخدم "أحمد" فرعًا ولم يكلف نفسه عناء إجراء بعض الاختبارات قبل دمج ما عدّله ضمن الفرع الرئيسي؟ ماذا لو حاول "عبد الله" بناء نسخة إنتاج من التطبيق لكن بمعاملات خاطئة؟
</p>

<p>
	سيمكِّننا استخدام التكامل المتواصل وفق طريقة منهجية في العمل في:
</p>

<ul>
<li>
		منع رفع الشيفرات مباشرةً إلى الفرع الرئيسي.
	</li>
	<li>
		تنفيذ عملية CI لكل طلبات السحب إلى الفرع الرئيسي وتمكينها من دمج التغييرات.
	</li>
	<li>
		بناء حزم إنتاج للبرامج التي نطورها في بيئة معروفة تمتلك نظام CI.
	</li>
</ul>
<p>
	وهنالك أيضًا بعض الإيجابيات لتوسيع هذه الإعدادات:
</p>

<ul>
<li>
		إذا استخدمنا CD في كل مرةٍ ننفذ فيها عملية دمج مع الفرع الرئيسي، سنكون واثقين من عمل الفرع الرئيسي وفق نظام الإنتاج.
	</li>
	<li>
		إذا سمحنا بإتمام عملية الدمج فقط في الحالة التي يكون فيها الفرع الرئيسي محدّثًا، سنضمن أن لا يلغي المطورون التغييرات التي نفذها غيرهم.
	</li>
</ul>
<p>
	<strong>تنبيه</strong>: سنفترض في هذا القسم أن الشيفرة الموجودة في الفرع الرئيسي ("mian" أو "master") ستعمل وفق نظام الإنتاج. ونظرًا لوجود عدد هائل من <a href="https://www.atlassian.com/git/tutorials/comparing-workflows" rel="external nofollow">مخططات العمل</a> (workflows) على git، قد يكون هناك على سبيل المثال حالات يظهر فيها فرع خاص للإصدار release branch يحتوي على الشيفرة التي تعمل وفق نظام الإنتاج.
</p>

<h2>
	مبادئ هامة
</h2>

<p>
	من المهم أن نتذكر أنّ CI/CD ليس هدفًا بحد ذاته، لكن الهدف هو تطوير أسرع وأفضل للتطبيقات بأقل عددٍ من الثغرات، وتحقيق أفضل تعاون بين المطورين.
</p>

<p>
	ولهذا لا بدّ من تهيئة نظام CI بما يلبّي المهمة التي ننفذها والمشروع بحد ذاته، كما ينبغي أن نبقي الهدف الرئيسي من استخدامه في حساباتنا دائمًا. يمكننا التفكير بنظام CI بمثابة جوابٍ على الأسئلة التالية:
</p>

<ul>
<li>
		كيف سنتأكد أنّ جميع الاختبارات ستُطبّق على كامل الشيفرة التي ستُنشر؟
	</li>
	<li>
		كيف سنضمن جاهزية الفرع الرئيسي للنشر في كل الأوقات؟
	</li>
	<li>
		كيف سنتأكد أنّ النسخ التي تُبنى ستكون متوافقة، وستعمل دائمًا على المنصات التي صُمِّمت للنشر عليها؟
	</li>
	<li>
		كيف سنضمن أن التغييرات الجديدة لن تلغي التغييرات الأقدم؟
	</li>
	<li>
		كيف سننجز عملية النشر بضغطة زر أو بطريقة آلية، عندما يدمج مطور التغييرات التي أجراها مع الفرع الرئيسي؟
	</li>
</ul>
<p>
	وهناك أيضًا دلائل علمية على الفائدة الهائلة التي يقدمها نظام CI/CD، فبناءً على دراسة واسعة في كتاب "<a href="https://itrevolution.com/book/accelerate/" rel="external nofollow">: Building and Scaling High Performing Technology Organizations</a>"، يقترن استخدام CI/CD بشدة مع النجاحات على صعيد المؤسسات (تحسين الأرباح وجودة المنتج وزيادة الحصة السوقية وتقليل زمن التسويق)، وكذلك من ناحية المطورين، فقد جعلتهم أكثر سعادةً وأقل إرهاقًا.
</p>

<h3>
	سلوك موثق
</h3>

<p>
	تشيع في أوساط المبرمجين طرفةٌ مفادها أنّ الثغرات هي "ميزات غير موثقة". لكن يجب علينا تفادي أية حالات لا نعلم تمامًا نتيجتها. فلو اعتمدنا مثلًا على عنوان طلب السحب لنحدد ما إذا كان إصدار البرنامج رئيسيًا major، أو ثانويًا minor، أو ترميميًا patch (سنتحدث عن معنى كل إصدار لاحقًا)، سيكون علينا الانتباه إلى الحالة التي يُغفل فيها المطور من وضع عنوانٍ لطلب السحب الذي قدمه. وماذا لو وضع هذا العنوان بعد بدء عملية البناء أو الاختبار؟ أو أنه غيَّر العنوان خلال مرحلةٍ ما، فما الذي سيكون عليه هذا الإصدار؟
</p>

<p>
	من الممكن أن تغطي جميع الحالات التي قد تتوقعها، ومع ذلك، قد تظهر بعض الثغرات عندما يُنفّذ المطور شيئًا "إبداعيًا" لم تتوقعه إطلاقًا، فمن الجيد إذًا أن تنتهي العملية التي ستخفق بأمان في هذه الحالة.
</p>

<p>
	فلو عدنا إلى المثال السابق عندما يغير المطور عنوان الطلب أثناء تنفيذ عملية البناء. إذا لم نتمكن من توقع سيناريو مثل هذا مسبقًا، فمن الأفضل ربما أن تتوقف عملية البناء، وأن نبلِّغ المستخدم بأنّ شيئًا غير متوقع قد حدث، فنشر إصدار خاطئ سينتج حتمًا مشاكلًا أكبر، وسيكون إيقاف العملية وإخطار المستخدم هو الحل الأكثر أمانًا.
</p>

<h3>
	يحدث الشيء ذاته في كل مرة
</h3>

<p>
	قد نملك أفضل اختبارات لبرمجياتنا يمكن تخيلها، وقد تكون قادرةً على اصطياد أية مشكلة محتملة، لكنها ستبقى عديمة الجدوى إن لم ننفذها على الشيفرة قبل نشرها.
</p>

<p>
	علينا أن نضمن أنّ الاختبارات قابلةً للتنفيذ، كما نريد أن نضمن أنها ستعمل مع الشيفرة التي سننشرها فعليًا. فلن يفيدنا الاختبار الذي ينجح مثلًا ضمن فرع "سارة" ويفشل عند دمجه مع الفرع الرئيسي. وطالما أننا سننشر محتويات الفرع الرئيسي، لذا علينا أن نتأكد من نجاح الاختبارات على نسخة من الفرع الرئيسي بعد أن ندمج معه فرع "سارة".
</p>

<p>
	ستوصلنا المناقشة السابقة إلى مبدأ جوهري وهو التأكد أنّ السلوك نفسه سيحدث كل مرة، أو أنّ المهام المطلوبة ستُنفَّذ جميعها وبالترتيب الصحيح.
</p>

<h3>
	يجب أن تبقى الشيفرة قابلة للنشر دائما
</h3>

<p>
	إنّ وجود شيفرة قابلة للتوزيع والنشر دائمًا أمر مريح جدًا وخاصةً عندما يحتوي الفرع الرئيسي على شيفرة تعمل في وضع الإنتاج؛ فإذا ظهرت ثغرةٌ مثلًا وتطلب الأمر معالجتها، يمكنك سحب نسخة من الفرع الرئيسي (وهي الشيفرة التي تُنفًّذ في وضع الإنتاج) وحل المشكلة، ثم تقديم طلب سحب لإعادة الشيفرة إلى الفرع الرئيسي (دمج من جديد). ويجري هذا الأمر عادةً مباشرةً دون أية مشاكل.
</p>

<p>
	إذا اختلفت الشيفرة الموجودة في الفرع الرئيسي عن شيفرة الإنتاج ولم يكن الفرع الرئيسي قابلًا للنشر، سيكون عليك البحث عن الشيفرة التي تعمل في وضع الإنتاج وسحب نسخة عنها، ثم إيجاد الثغرة ومعالجتها، كما عليك إيجاد طريقة لإعادة الشيفرة إلى الفرع الرئيسي والعمل على نشر الجزء المحدد الذي أصلحته. لن يكون هذا الأمر مريحًا وستكون طريقة تنفيذه مختلفةً كليًا عن مخطط نشر التطبيقات الاعتيادي.
</p>

<h3>
	معرفة أجزاء الشيفرة المنشورة- مجموع SHA/الإصدار
</h3>

<p>
	من المهم غالبًا تحديد الشيفرة التي تعمل فعليًا في وضع الإنتاج. ولقد ناقشنا سابقًا أن شيفرة الفرع الرئيسي هي من تعمل في هذا الوضع في الحالة النموذجية، لكن هذا الأمر ليس ممكنًا دائمًا؛ فقد تتعرض عملية البناء للإخفاق عندما نحاول تشغيل شيفرة الفرع الرئيسي في وضع الإنتاج، وقد نجري عدة تغييرات على الشيفرة ونريدها أن تُنشر جميعها مباشرةً.
</p>

<p>
	ما نحتاج إليه في حالات مثل هذه (وهي فكرة جيدة عمومًا)، هو معرفة الشيفرة التي تعمل في وضع الإنتاج بالضبط؛ إذ يمكن معرفة ذلك أحيانًا عن طريق رقم الإصدار version، وأحيانًا بمجموع SHA sum (وهو جدول HASH يعرِّف بصورةٍ فريدة اعتماد git) متصل بالشيفرة. سنناقش موضوع تحديد رقم الإصدار لاحقًا في هذا القسم.
</p>

<p>
	الآلية الأكثر فائدةً هي ربط معلومات الإصدار مع تاريخ جميع الإصدارات، فلو اكتشفنا مثلًا وجود ثغرة في اعتماد محدد، يمكننا أن نعرف متى أُصدر هذا الاعتماد وكم عدد المستخدمين الذين تأثروا بالثغرة. وتظهر فائدة هذه المقاربة عندما تسبب الثغرة أخطاءً في بيانات <a href="https://academy.hsoub.com/programming/sql/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%B9%D9%86-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-r584/" rel="">قواعد البيانات</a>، وهكذا من الممكن تقفي أثر البيانات الخاطئة بناءً على تاريخ إنشائها.
</p>

<h2>
	أنماط إعدادات منظومة CI
</h2>

<p>
	علينا تخصيص خادم منفصل لتشغيل مهام منظومة التكامل المتواصل CI لكي نلبي بعضًا من المتطلبات التي ذكرناها سابقًا، إذ سيقلل وجود خادم منفصل المخاطر الناتجة عن اختلاط عمليات أخرى بعملية منظومة CI/CD جاعلةً نتيجتها غير متوقعة.
</p>

<p>
	هنالك خياران: استضافة خادم خاص بنا أو الخدمة السحابية cloud service.
</p>

<h3>
	استضافة Jenkins وإعدادات بيئة الاستضافة الذاتية self-host
</h3>

<p>
	تُعد استضافة <a href="https://www.jenkins.io/" rel="external nofollow">Jenkins</a> الخيار الأكثر شعبيةً من بين خيارات الاستضافة الذاتية، فهي مرنةٌ جدًا وتمتلك عدة إضافات plugin لكل شيء تقريبًا (ماعدا الشئ الوحيد الذي تريده). تمثل هذه الاستضافة خيارًا جيدًا للعديد من التطبيقات. يعني استخدام الاستضافة الذاتية أن تكون بيئة الاستضافة تحت سيطرتك بالكامل، وكذلك جميع الموارد، ولن تكون أسرارك عرضةً للكشف من قبل الآخرين، كما ستتمكن من فعل ما تشاء بالعتاد الصلب.
</p>

<p>
	لكن ولسوء الحظ هناك جانبٌ مظلم لاستضافة Jenkins فهي صعبة الإعداد، إذ تتطلب المرونة الكبيرة في الإعداد وجود العديد من قوالب template الشيفرة التي تتطلب الإعداد ليعمل كل شيء، ويتطلب استخدام Jenkins بالتحديد إعداد منظومة CI/CD باستخدام لغة Jenkins الخاصة بالنطاق domain-specific. ولا ننس مخاطر إخفاق العتاد الصلب والذي قد يسبب مشكلةً في حالات الازدحام.
</p>

<p>
	تعتمد فاتورة الاستضافة الذاتية على العتاد الصلب الذي تختاره غالبًا، فأنت تدفع مقابل <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخادم</a> وما ستفعله على الخادم لن يغير من فاتورتك.
</p>

<h3>
	استخدام GitHub Actions وحلول أخرى معتمدة على الاستضافة السحابية
</h3>

<p>
	لن تضطر للتفكير كثيرًا عند إعداد بيئة الاستضافة في الاستضافة السحابية، فكل ما عليك فعله هو أن تطلب من الاستضافة ما تريده، ويتضمن ذلك عادةً وضع ملف في مستودعك (ثم إخبار منظومة CI بقراءته)، أو التحقق من وجود ملف كهذا في المستودع.
</p>

<p>
	سيكون ملف تهيئة CI في الاستضافة السحابية أبسط قليلًا، وخاصةً إن لم تتعدّى الحدود "الطبيعية" للاستخدام، لكن قد تغدو خيارات هذه الإستضافة محدودةً قليلًا إن أردت فعل أشياء خاصة بين الفينة والأخرى، أو قد تجد صعوبةً في ذلك، خاصةً في إنجاز مهام محددة لم تُبنى المنصة السحابية لكي تنجزها.
</p>

<p>
	سنركز في هذا القسم على الاستخدامات التي تعد "طبيعية"، فقد تتطلب إعدادات بعض المهام الخاصة عتادًا صلبًا خاصًا على سبيل المثال.
</p>

<p>
	وبعيدًا عن إعدادات التهيئة التي ذكرناها سابقًا، قد تواجهنا مشاكلًا تتعلق بمحدودية الموارد على الاستضافة السحابية، فلو بدت النسخة بطيئة على الاستضافة الذاتية، يمكنك ببساطة حجز خادم أقوى وتضع ما تحتاجه من الموارد ضمنه، لكن هذا الأمر قد يكون مستحيلًا في الاستضافة السحابية. ستعمل العقد التي تبنيها في <a href="https://github.com/features/actions" rel="external nofollow">GitHub Actions</a> مثلًا على معالجين افتراضيين (vCPU) و8 جيجابايت من ذواكر الوصول العشوائي.
</p>

<p>
	ستعتمد فاتورتك في الاستضافة السحابية على الوقت المستغرق في بناء تطبيقك، وهذا أمر يجدر أخذه بالحسبان.
</p>

<h3>
	لماذا نفضل خيارا على آخر؟
</h3>

<p>
	إن كان مشروعك صغيرًا إلى متوسط الحجم ولن يحتاج أية متطلبات خاصة (مثل الحاجة إلى بطاقة رسومية لتنفيذ الاختبارات)، ستكون الاستضافة السحابية الحل المناسب غالبًا، إذ ستكون الإعدادات بسيطةً ولن تدخل دوامة إعدادات النظام الخاص بك، كما ستكون الكلفة أقل للمشاريع الصغيرة خاصة؛ أما بالنسبة للمشاريع الأضخم والتي تحتاج إلى موارد أكبر أو بالنسبة للشركات الكبيرة التي تضم عدة فرق للتطوير وعدة مشاريع، قد يكون اختيار منظومة استضافة ذاتية هو الخيار الأنسب.
</p>

<h3>
	لماذا استخدمنا GitHub Actions في هذه السلسلة؟
</h3>

<p>
	يُعدّ GitHub Actions الخيار الأنسب كوننا نستخدم غيت هب GitHub، إذ سيؤمن لنا منظومة CI قوية تعمل مباشرةً دون الحاجة إلى إعداد خادم أو خدمة سحابية يؤمنها طرف ثالث.
</p>

<p>
	إضافةً إلى سهولة استخدامه، يُعد <a href="https://github.com/features/actions" rel="external nofollow">GitHub Actions</a> خيارًا جيدًا من عدة جوانب، فقد يكون أفضل خدمة سحابية موجودة حتى اللحظة، إذ يحظى بشعبية كبيرة منذ إصدار النسخة الأساسية منه في نوفمبر (تشرين الثاني) عام 2019.
</p>

<h2>
	التمرين 11.1
</h2>

<p>
	قبل أن ننغمس في إعداد خطوط إنتاج لأنظمة CI/CD، لا بدّ من مراجعة ما قرأناه.
</p>

<h3>
	لنتهيأ للانطلاق
</h3>

<p>
	فكر بحالة افتراضية نكون فيها أمام تطبيق يجري تطويره من قِبل فريق من 6 مطورين وسيصدر قريبًا.
</p>

<p>
	لنفترض أنّ اللغة المستخدمة في تطوير التطبيق ليست JavaScript/TypeScript فلنقل <a href="https://academy.hsoub.com/programming/python/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A8%D8%A7%D9%8A%D8%AB%D9%88%D9%86-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-r211/" rel="">Python</a>، أو <a href="https://academy.hsoub.com/programming/java/%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D9%85%D8%A7-%D9%87%D9%8A%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7-java-r1515/" rel="">Java</a>، أو <a href="https://academy.hsoub.com/programming/ruby/%D8%AA%D8%B9%D8%B1%D9%91%D9%81-%D8%B9%D9%84%D9%89-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-ruby-r636/" rel="">Ruby</a>، أو أية لغات قد تفكر بها. اكتب موضوعُا بسيطًا من 200-300 كلمة تجيب فيه أو تناقش النقاط التي سنطرحها تاليًا. يمكنك التحقق من عدد الكلمات التي تكتبها عبر موقع <a href="ttps://wordcounter.net/" rel="external nofollow">wordcounter</a>. خزّن إجابتك في ملف اسمه "exercise1.md" في جذر المستودع الذي ستنشئه في <a href="https://fullstackopen.com/en/part11/getting_started_with_git_hub_actions#exercise-11-2" rel="external nofollow">التمرين 11.2</a>
</p>

<p>
	ناقش النقاط التالية:
</p>

<ul>
<li>
		بعض الخطوات الشائعة في إعداد CI بما فيها التدقيق والاختبار والبناء، وكذلك الأدوات اللازمة لتنفيذ هذه الخطوات في بيئة عمل اللغة التي تختارها. يمكنك البحث عن الأجوبة عبر غوغل Google.
	</li>
	<li>
		ما هي بدائل إعداد منظومات CI عن GitHub Actions أو Jenkins؟ يمكنك الاستعانة بمحرك البحث غوغل.
	</li>
	<li>
		أين سيكون تطبيق هذه الإعدادات أفضل، على استضافة ذاتية أو استضافة سحابية؟ لماذا؟ ما هي المعلومات التي ستحتاجها لاتخاذ قرارك؟
	</li>
</ul>
<p>
	تذكر أنه لا توجد إجابات صحيحة أو خاطئة لجواب هذا السؤال.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part11/introduction_to_ci_cd" rel="external nofollow">Introduction to CICD</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D9%85%D8%A7-%D9%87%D9%88-git%D8%9F-r1592/" rel="">ما هو Git</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/workflow/git/%D8%A5%D8%B9%D8%A7%D8%AF%D8%A9-%D8%AA%D8%A3%D8%B3%D9%8A%D8%B3-%D8%AA%D9%81%D8%B1%D9%8A%D8%B9%D8%A7%D8%AA-%D8%B7%D9%84%D8%A8-%D8%A7%D9%84%D8%B3%D8%AD%D8%A8-%D9%88%D8%AA%D8%AD%D8%AF%D9%8A%D8%AB%D9%87-%D9%81%D9%8A-git-r1584/" rel="">إعادة تأسيس تفريعات طلب السحب وتحديثه في git</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D8%AF%D9%85%D8%AA%D9%8A%D9%86-circleci-%D9%88coveralls-r1276/" rel="">إعداد التكامل المستمر والنشر المستمر باستخدام الخدمتين CircleCI وCoveralls</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-concourse-ci-%D8%B9%D9%84%D9%89-%D8%A3%D9%88%D8%A8%D9%86%D8%AA%D9%88-r356/" rel="">التكامل المستمر: تثبيت Concourse CI على أوبنتو</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">644</guid><pubDate>Thu, 18 Aug 2022 17:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x62A;&#x648;&#x633;&#x639; &#x623;&#x643;&#x62B;&#x631; &#x641;&#x64A; &#x646;&#x647;&#x62C; &#x627;&#x644;&#x62A;&#x643;&#x627;&#x645;&#x644; &#x648;&#x627;&#x644;&#x62A;&#x633;&#x644;&#x64A;&#x645; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631;</title><link>https://academy.hsoub.com/devops/deployment/%D8%A7%D9%84%D8%AA%D9%88%D8%B3%D8%B9-%D8%A3%D9%83%D8%AB%D8%B1-%D9%81%D9%8A-%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r648/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_08/Extending-further.png.d8f73b7e5b8efd00ced6999cd780bd5e.png" /></p>

<p>
	انصب تركيزنا في هذا القسم على بناء منظومة تكامل مستمر CI بسيطة وفعالة ومحكمة، تساعد المطورين على العمل معًا والحفاظ على جودة الشيفرة ومن ثم نشرها بأمان. ما الذي يمكننا فعله أكثر؟ لنتذكر أنه في العالم الحقيقي ستمتد أيادٍ إلى الكعكة غير أياد المطورين والمستخدمين. حتى وإن لم يكن ذلك صحيحًا، وخاصة بالنسبة للمطورين، إلا أنّ هناك الكثير الذي تقدمه منظومة CI غير ما اطلعنا عليه.
</p>

<h2>
	مجال الرؤية والفهم
</h2>

<p>
	في جميع الشركات ما عدا الصغيرة منها، لا تُتخذ القرارات حول تطوير المنتجات حصرًا من طرف المطورين. يشير مصطلح أصحاب الشأن "stakeholder" إلى الأشخاص داخل وخارج فريق التطوير، الذين لديهم مصلحة في مراقبة تقدم عملية التطوير.عند هذا المستوى، هناك غالبًا تكاملٌ بين <a href="https://academy.hsoub.com/programming/workflow/git/%D9%85%D8%A7-%D9%87%D9%88-git%D8%9F-r1592/" rel="">git</a> وأي برنامج لإدارة المشاريع أو تتبع الثغرات قد يستخدمه الفريق.
</p>

<p>
	يُعد امتلاك مرجع إلى نظام التتبع في طلبات السحب أو في الشيفرة المعتمدة أكثر الطرق شيوعًا لتنفيذ ذلك. وهكذا، عندما تعمل على النقطة رقم 123 مثلًا، بإمكانك عندها تسمية طلب السحب الخاص بك <code>BUG-123: Fix user copy issue</code>، وسيلاحظ نظام تتبع الثغرات عندها الجزء الأول من اسم طلب السحب، وسينقل النقطة إلى قائمة النقاط المنفَّذة <code>Done</code> عندما يُدمج الطلب.
</p>

<h2>
	تنبيهات
</h2>

<p>
	عندما تنتهي عملية CI بسرعة، فمن المناسب أن نراقب العملية فقط وننتظر النتائج، لكن مع ازدياد حجم المشاريع ستزداد أيضًا فترة تنفيذ عمليات بناء واختبار الشيفرة. سيقودنا ذلك إلى الحالة التي تستغرق فيها ظهور نتائج بناء الشيفرة وقتًا طويلًا، مما يدفع المطور للتفكير في العمل على مهمة أخرى، وهذا سيقود بدوره إلى نسيان عملية البناء.
</p>

<p>
	ستظهر المشكلة في طرحنا هذا خاصة عندما نتحدث عن دمج طلبات السحب التي قد تؤثر على عمل مطور آخر، إما بالتسبب بمشاكل في عمله أو تأخير هذا العمل. وقد يقودنا ذلك أيضًا إلى الحالة التي تعتقد فيها أنك نشرت شيئًا، لكنك في الواقع لم تنهي عملية النشر، وسيؤدي ذلك إلى سوء التواصل مع أعضاء فريقك أو مع الزبائن (كأن تقول: "جرب من جديد، لا بدّ أنّ المشكلة قد حُلَّت").
</p>

<p>
	هناك عدة أساليب لحل هذه المشكلة، تتدرج من إنشاء تنبيهات بسيطة إلى عمليات أكثر تعقيدًا تدمج ببساطة الشيفرة التي تمرر إليها إن تحققت جملةٌ من الشروط الموضوعة مسبقًا. سنناقش حاليًا التنبيهات مثل حل بسيط، كونها الوحيدة التي تتعلق بسير مخطط عمل الفريق.
</p>

<p>
	يرسل غيت هب افتراضيًا بريدًا إلكترونيًا عند إخفاق البناء. يمكن تغيير ذلك لإرسال تنبيهات بغض النظر عن حالة البناء، كما يمكن تهيئته لإنذارك على واجهة ويب غيت هب GitHub. طبعًا هذا الأمر رائع، لكن ماذا لو أردنا أكثر من ذلك؟ ماذا لو لم يعمل هذا الحل لسببٍ ما مع حالتنا؟
</p>

<p>
	يمكن أن يتكامل غيت هب GitHub مع عدة تطبيقات رسائل مثل <a href="https://slack.com/intl/en-fi/" rel="external nofollow">Slack</a> لإرسال التنبيهات، وستكون هذه التطبيقات قادرةً على تحديد متى ترسل هذه التنبيهات بناءً على منطق يفرضه غيت هب GitHub.
</p>

<h2>
	التمرين 11.18
</h2>

<p>
	أعددنا قناة fullstack_webhookعلى مجموعة Discord على العنوان "https://study.cs.helsinki.fi/discord/join/fullstack" لاختبار تكامل منظومة الرسائل. ستحتاج طبعًا إلى بريد إلكتروني للتسجيل، وانتبه إلى ضرورة استخدام عنوان خطاف الويب webhook الخاص بالتطبيق Discord لتنفيذ هذا التمرين. ستجد هذا الخطاف في الرسالة المنبثقة القناة "fullstack_webhook". ويُستحسن عدم الالتزام بالخطاف على غيت هب GitHub.
</p>

<h3>
	أفعال لتنبيه المستخدم إلى نجاح أو فشل البناء
</h3>

<p>
	ستجد العشرات من أفعال GitHub Actions التي طورها مطورون مستقلون على <a href="https://github.com/marketplace?type=actions" rel="external nofollow">GitHub Action Marketplace</a> بمجرد أن تبحث عن العبارة <a href="https://github.com/marketplace?type=actions&amp;query=discord" rel="external nofollow">discord</a>. اختر واحدًا لتستخدمه في هذا التمرين. وقد اخترنا <a href="https://github.com/marketplace/actions/discord-webhook-notify" rel="external nofollow"> discord-webhook-notify</a> لأنه حصل على تقييم مرتفع وله توثيق جيد.
</p>

<p>
	هيئ الفعل بحيث يعطي نوعين من التنبيهات:
</p>

<ul>
<li>
		مؤشر على النجاح إن نُشرت النسخة.
	</li>
	<li>
		مؤشر على الفشل إن أخفق البناء.
	</li>
</ul>
<p>
	في حال وقوع خطأ، لا بدّ أن تكون رسالة الخطأ مسهبة لمساعدة المطور على إيجاد الخطأ بسرعة وإيجاد الشيفرة المعتمدة التي سببت هذا الخطأ.
</p>

<p>
	ابحث في <a href="https://docs.github.com/en/actions/learn-github-actions/expressions#status-check-functions" rel="external nofollow">توثيق</a> GitHub عن كيفية التحقق من حالة المهمة.
</p>

<p>
	يمكن أن تبدو التنبيهات على النحو التالي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="105739" href="https://academy.hsoub.com/uploads/monthly_2022_08/discord_notifications_01.png.610d1cb4aa55b6c281e100055801d0fc.png" rel=""><img alt="discord_notifications_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="105739" data-unique="wjsugrv2f" src="https://academy.hsoub.com/uploads/monthly_2022_08/discord_notifications_01.png.610d1cb4aa55b6c281e100055801d0fc.png" style="width: 600px; height: auto;"></a>
</p>

<h2>
	المقاييس Metrics
</h2>

<p>
	لقد أشرنا في الفقرة السابقة أنه عندما ينمو المشروع ستزداد مدة إنجاز خطوات البناء والاختبار، وهذا ليس بالأمر المثالي، فكلما طالت حلقة وصول النتائج ستصبح دورة التطوير بطيئة.
</p>

<p>
	وعلى الرغم من إمكانية القيام بأشياء لتقليل الوقت اللازم للبناء، من المفيد أيضًا أن ننظر جيدًا إلى الصورة الكلية للوضع. فمن الجيد أن نعرف كم استغرقت عملية البناء قبل أشهر عدة موازنةً مع ما تستغرقه الآن. هل يسلك الاختلاف منحًى خطيًا أم يظهر على هيئة قفزات فجائية؟ كما أنّ معرفة السبب الكامن خلف زيادة زمن البناء سيساعدنا جدًا في حل المشكلة؛ فإذا زاد زمن التنفيذ على نحوٍ خطي من 5 إلى 10 دقائق خلال سنة، فيمكننا أن نتوقع بأنه سيزداد أيضًا بضع دقائق خلال عدة أشهر قادمة ليصل إلى 15 دقيقة، وستكون لدينا فكرةً عن قيمة ما ننفقه من وقت لتسريع عمل منظومة CI.
</p>

<p>
	يمكن أن تكون المقاييس ذاتية- التسجيل self-reported وتدعى أيضًا مقاييس "دفع-Push"، إذ تسجل كل عملية بناء الوقت الذي تستغرقه، أو أن نحضر البيانات من واجهة برمجية بعد انتهاء العملية وتدعى أحيانًا مقاييس "سحب-Pull". يتمثل خطر التسجيل الذاتي في الوقت الذي ستستغرقه عملية التسجيل بحد ذاتها، والتي قد يكون لها أثر على الوقت الكلي المستغرق للبناء.
</p>

<p>
	يمكن أن تُرسل هذه البيانات إلى قاعدة بيانات تعمل وفق سلاسل زمنية time-series DB، أو إلى سِجِل من نوع آخر. هناك عدة خدمات سحابية لتجميع قياساتك بكل سهولة، ومن الخيارات الجيدة الخدمة <a href="https://www.datadoghq.com/" rel="external nofollow">Datadog</a>.
</p>

<h2>
	المهام الدورية
</h2>

<p>
	هناك بعض المهام الدورية التي يجب تنفيذها ضمن فرق تطوير البرمجيات، إذ يمكن أتمتة بعضها من خلال بعض الأدوات المتوفرة، وعليك أتمتة بعضها الآخر.
</p>

<p>
	تتضمن الفئة الأولى من المهام الدورية التحقق من الحزم لتفادي الاختراقات الأمنية، وقد تساعدك في ذلك العديد من الأدوات الجاهزة. سيكون بعض هذه الأدوات مجانيًا لمشاريع محددة (<a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D8%A7%D9%84%D9%85%D9%82%D8%B5%D9%88%D8%AF-%D8%A8%D9%85%D8%B5%D8%B7%D9%84%D8%AD-%D9%85%D9%81%D8%AA%D9%88%D8%AD-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1-open-source%D8%9F-r885/" rel="">مفتوحة المصدر</a> مثلًا) مثل الأداة <a href="https://dependabot.com/" rel="external nofollow">Dependabot</a> التي يقدمها GitHub.
</p>

<p>
	وإليك النصيحة التالية: من الأفضل دائمًا استخدام أدوات جاهزة لأداء هذه المهام، إن سمحت بذلك ميزانيتك، بدلًا من الاعتماد على حلولك الخاصة، فإن لم تكن مهتمًا بتطوير أدوات تتعلق بأمور الأمن مثلًا، استخدم Dependabot للتحقق من الثغرات الأمنية بدلًا من تطوير الأداة بنفسك.
</p>

<p>
	لكن ماذا عن المهام التي لا تتوفر لها أدوات مساعدة؟ يمكنك أتمتة هذه المهام بنفسك عن طريق GitHub Actions أيضًا، إذ يزودك GitHub Actions بمُطلِقات أحداث المجدولة scheduled trigger التي يمكن استخدامها لتنفيذ مهمة محددة في وقت محدد.
</p>

<h2>
	التمارين 11.19 -11.21
</h2>

<h3>
	11.19: التحقق الدوري من سلامة البرمجيات
</h3>

<p>
	لدينا الثقة الآن بأنّ خط الإنتاج الذي عملنا عليه سيمنع نشر الشيفرة المخفقة، لكن مع ذلك هناك مصادر عديدة للأخطاء؛ فعندما يعتمد تطبيقنا مثلًا على قاعدة بيانات قد لا تكون متوفرة دائمًا، سيتوقف التطبيق عن العمل في لحظةٍ ما. لهذا من المفيد جدًا التحقق دوريًا من سلامة عمل التطبيق عن طريق إرسال طلب HTTP-GET إلى الخادم. ندعو هذه العملية عادة بعملية التحقق من الاتصال "ping".
</p>

<p>
	من الممكن أن نجدول أفعال GitHub لتُحدّث دوريًا وبصورة منتظمة. استخدم الفعل <a href="https://github.com/marketplace/actions/url-health-check" rel="external nofollow">url-health-check</a> أو أي بديل له وجدول عملية التحقق من <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">الخادم</a> الذي تنشر عليه التطبيق. حاول محاكاة الحالة التي يخفق فيها تطبيقك، تأكد حينها أن عملية التحقق ستكتشف الخطأ. اكتب مخطط العمل الدوري الذي أوجدته على ملف خاص.
</p>

<p>
	<strong>تنبيه</strong>: سيستغرق <a href="https://academy.hsoub.com/devops/deployment/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-github-actions-%D9%84%D8%AA%D8%AD%D9%82%D9%8A%D9%82-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r645/" rel="">GitHub Actions</a> وقتًا طويلًا حتى يبدأ بتنفيذ مخطط العمل الذي جدولته للمرة الأولى، فقد تطلب منا الأمر قرابة الساعة، لذلك تُعد فكرة إطلاق مخطط العمل مباشرةً بتنفيذ عملية دفع للشيفرة إلى الاستضافة فكرةً جيدة. انتقل بعد ذلك لتنفيذ المخطط المجدول بعد أن تنجح معك هذه الحيلة.
</p>

<p>
	<strong>تنبيه</strong>: عندما تنجح في تنفيذ ما طلبناه، من الأفضل أن تقلل مرات التحقق من الاتصال مع الخادم إلى مرة كل 24 ساعة أو عطل القاعدة، وإلا ستكلفك عملية التحقق المستمرة <a href="https://devcenter.heroku.com/articles/free-dyno-hours" rel="external nofollow">ساعاتك المجانية</a> المتاحة خلال الشهر.
</p>

<h3>
	11.20: خط إنتاج خاص بك
</h3>

<p>
	ابنِ خط إنتاج CI/CD لبعض التطبيقات التي أنجزتها سابقًا. من التطبيقات المرشحة للعمل عليها تطبيق دليل الهاتف الذي طورناه في القسمين 2 و3 من المنهاج، أو تطبيق المدونة الذي طورناه في <a href="https://academy.hsoub.com/programming/javascript/nodejs/%D9%87%D9%8A%D9%83%D9%84-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%AE%D9%84%D9%81%D9%8A%D8%A9-%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-unit-tests-r1132/" rel="">القسم 4</a> و<a href="https://academy.hsoub.com/programming/javascript/react/%D8%AA%D8%B3%D8%AC%D9%8A%D9%84-%D8%A7%D9%84%D8%AF%D8%AE%D9%88%D9%84-%D9%81%D9%8A-%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%85%D9%8A%D8%A9-r1136/" rel="">القسم 5</a>، أو تطبيق الطرائف الذي بنيناه بالاستعانة بالمكتبة Redux في <a href="https://academy.hsoub.com/programming/javascript/react/%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%A7%D9%84%D9%85%D8%B9%D9%85%D8%A7%D8%B1%D9%8A%D8%A9-flux-%D9%88%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-redux-%D9%84%D8%A5%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D8%AD%D8%A7%D9%84%D8%A9-%D9%81%D9%8A-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-react-r1162/" rel="">القسم 6</a>.
</p>

<p>
	ستضطر غالبًا إلى إعادة هيكلية التطبيق لتجمع قطع الشيفرة المتفرقة، وعليك في الخطوة الأولى وضع الواجهتين الأمامية والخلفية في مستودع واحد. لست مجبرًا على ذلك طبعًا، لكنه سيسهل عليك الأمر كثيرًا.
</p>

<p>
	إحدى الهيكليات المقترحة هي وضع الواجهة الخلفية في جذر المستودع وأن تضع الواجهة الخلفية ضمن مجلد فرعي، ويمكنك أيضًا نسخ ولصق بنية التطبيق النموذجي الذي نفذناه في هذا القسم، أو أن تحاول الاستفادة من <a href="https://github.com/fullstack-hy2020/create-app" rel="external nofollow">التطبيق النموذجي</a> الذي اطلعنا عليه في <a href="https://academy.hsoub.com/programming/javascript/react/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D8%B3%D8%AA%D8%B9%D9%85%D8%A7%D9%84-%D8%A7%D9%84%D9%85%D9%83%D8%AA%D8%A8%D8%A9-react-router-r1166/" rel="">القسم 7</a>.
</p>

<p>
	ربما من الأفضل أن تنشئ مستودعًا جديدًا لهذا التمرين، ومن ثم تنسخ وتلصق الشيفرة ضمنه. قد تضطر غالبًا في العمل الواقعي إلى تنفيذ ذلك ضمن المستودع القديم، لكن الإنطلاقة الجديدة الآن ستسهل علينا الأمر.
</p>

<p>
	سيستغرق منك هذا التمرين وقتًا وجهدًا، لكن الحالة التي تواجهها هنا، وهي بناء خط إنتاج لنشر شيفرة قديمة، شائعة جدًا في الحياة العملية.
</p>

<p>
	لن يُنفّذ هذا التمرين في نفس المستودع الذي نفذت فيه التمارين السابقة، لأنك لا تستطيع أن تعيد سوى مستودع واحد إلى منظومة تسليم التمارين، ضع رابطًا إذًا للمستودع الآخر ضمن المستودع الذي أشرت إليه في نموذج التسليم.
</p>

<h3>
	11.21: حماية الفرع الرئيسي وتقديم طلب سحب
</h3>

<p>
	احمِ الفرع الرئيسي للمستودع الذي أنجزت فيه التمرين السابق، وامنع المدراء أيضًا هذه المرة من دمج الشيفرة مع الفرع الرئيسي دون مراجعتها.
</p>

<p>
	قدم طلب سحب، واطلب من أحد مستخدمي GitHub التالية أسماؤهم <a href="https://github.com/mluukkai" rel="external nofollow">mluukkai</a> و/ أو <a href="https://github.com/kaltsoon" rel="external nofollow">kaltsoon</a> أن يراجع شيفرتك، وبعد أن تنتهي المراجعة، ادمج الشيفرة مع الفرع الرئيسي، وانتبه إلى أن يكون المراجع من المتعاونين معك في المستودع. اتصل بنا على Discord لكي نراجع شيفرتك، ومن الأفضل إرسال رسالة خاصة تتضمن رابط دعوة.
</p>

<p>
	بعدها يكون العمل قد أُنجز.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://fullstackopen.com/en/part11/expanding_further" rel="external nofollow">Expanding further</a> من سلسلة <a href="https://fullstackopen.com/en/" rel="external nofollow">Deep Dive Into Modern Web Development</a>
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال التالي: مدخل إلى الحاويات
	</li>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%A5%D8%B5%D8%AF%D8%A7%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D8%A8%D8%A3%D9%85%D8%A7%D9%86-%D9%81%D9%8A-%D9%85%D9%86%D8%B8%D9%88%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r647/" rel="">نشر إصدارات التطبيق بأمان في منظومة التكامل والتسليم المستمر</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D9%88%D8%AA%D9%88%D8%B2%D9%8A%D8%B9%D9%87%D8%A7-%D9%88%D9%81%D9%82-%D9%86%D9%87%D8%AC-%D8%A7%D9%84%D8%AA%D8%B3%D9%84%D9%8A%D9%85-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-r646/" rel="">نشر التطبيقات وتوزيعها وفق نهج التسليم المستمر</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF-%D8%A7%D9%84%D8%AA%D9%83%D8%A7%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%88%D8%A7%D9%84%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AE%D8%AF%D9%85%D8%AA%D9%8A%D9%86-circleci-%D9%88coveralls-r1276/" rel="">إعداد التكامل المستمر والنشر المستمر باستخدام الخدمتين CircleCI وCoveralls</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">648</guid><pubDate>Fri, 12 Aug 2022 17:00:00 +0000</pubDate></item><item><title>&#x646;&#x634;&#x631; &#x645;&#x648;&#x642;&#x639; &#x648;&#x64A;&#x628;</title><link>https://academy.hsoub.com/devops/deployment/%D9%86%D8%B4%D8%B1-%D9%85%D9%88%D9%82%D8%B9-%D9%88%D9%8A%D8%A8-r628/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_07/62c00c6f9fcd5_--.png.bcd2e755ca40d333eae7f6e6f0d0c3fd.png" /></p>

<p>
	لابد من <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%B3%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-%D9%81%D9%8A-%D9%81%D9%87%D9%85-%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%B1%D9%81%D8%B9-%D8%A7%D9%84%D9%85%D9%88%D9%82%D8%B9-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r533/" rel="">نشر الموقع على شبكة الإنترنت</a> حالما تنتهي من كتابة الشيفرة وتنظيم الملفات التي تكوّنه ليتمكن الجميع من الوصول إليه، إذ سنشرح في هذا المقال كيف تنشر موقعك التجريبي البسيط على <a href="https://academy.hsoub.com/devops/networking/%D8%A2%D9%84%D9%8A%D8%A9-%D8%B9%D9%85%D9%84-%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r571/" rel="">الإنترنت</a> بأقل جهد ممكن.
</p>

<h2>
	ماهي الخيارات المتاحة
</h2>

<p>
	يُعَدّ نشر موقع ويب موضوعًا معقّدًا لوجود طرق وأساليب كثيرة لتنفيذه، ولن نحاول في هذا المقال توثيق كل الطرق الممكنة، وإنما سنصف إيجابيات وسلبيات ثلاثة نهج مناسبة للمبتدئين، ثم نفصّل إحداها والتي تُعَدّ الأفضل للكثير من القراء.
</p>

<h3>
	الحصول على استضافة واسم نطاق
</h3>

<p>
	يفضِّل الكثيرون الدفع مقابل الحصول على استضافة و<a href="https://academy.hsoub.com/devops/networking/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D9%85%D8%A7%D8%A1-%D8%A7%D9%84%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%81%D9%8A-%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r573/" rel="">اسم نطاق</a> وذلك لتحكّم أفضل في محتوى ومظهر موقع الويب:
</p>

<ul>
<li>
		<strong>الاستضافة Hosting</strong>: هي مساحة تخزين مُستأجَرة على <a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">خادم ويب</a> تابع لشركة الاستضافة، إذ توضَع ملفات الموقع على هذا الخادم الذي يتيح محتواه للزائرين.
	</li>
	<li>
		<strong>اسم النطاق Domain name</strong>: هو العنوان الفريد الذي ستجد عليه موقع ويب محدَّد مثل <a href="http://www.hsoub.com" rel="external">‎/hsoub</a> أو <a href="http://www.google.com" rel="external nofollow">‎/google</a>، ويمكنك استئجار اسم النطاق لعام أو أكثر من شركة مُسجّلة Domain Registrar.
	</li>
</ul>
<p>
	تستخدِم معظم مواقع ويب الاحترافية الطريقة السابقة لتنشر محتواها على شبكة الإنترنت، وبالإضافة إلى ذلك، سيتطلب الأمر برنامجًا يستخدِم <a href="https://academy.hsoub.com/apps/web/wordpress/3-%D8%A3%D8%AE%D8%B7%D8%A7%D8%A1-%D8%B4%D8%A7%D8%A6%D8%B9%D8%A9-%D8%B9%D9%86%D8%AF-%D8%B1%D9%81%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A8%D8%B1%D9%88%D8%AA%D9%88%D9%83%D9%88%D9%84-ftp-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D8%B5%D9%84%D8%A7%D8%AD%D9%87%D8%A7-r116/" rel="">بروتوكول نقل الملفات FTP</a> لنقل ملفاتك إلى الخادم، ويمكنك الاطلاع على مقال <a href="https://academy.hsoub.com/devops/networking/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D9%85%D8%A7%D8%A1-%D8%A7%D9%84%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%81%D9%8A-%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r573/" rel="">أساسيات تحديد الكلفة المادية الكاملة لبناء موقع ويب</a>، كما تتنوع برامج FTP كثيرًا، لكن وظيفتها بالمجمل هي استخدام المعلومات التي تزودك بها شركة الاستضافة وهي اسم المستخدِم وكلمة المرور واسم المضيف عادةً، وذلك لتأمين الاتصال مع خادم الاستضافة ونقل الملفات إليه، ثم يُظهِر البرنامج بعد الاتصال ملفاتك المحلية وملفات خادم الويب في نافذتين متجاورتين ليسهل نقل الملفات بين المكانين.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="102413" href="https://academy.hsoub.com/uploads/monthly_2022_07/01_ftp_program.png.9814472a03dcdd911c9ec7e1efac431d.png" rel=""><img alt="01_ftp_program.png" class="ipsImage ipsImage_thumbnailed" data-fileid="102413" data-unique="8pg9h71pw" src="https://academy.hsoub.com/uploads/monthly_2022_07/01_ftp_program.png.9814472a03dcdd911c9ec7e1efac431d.png" style="width: 750px; height: auto;"></a>
</p>

<h4>
	نصائح لإيجاد استضافة ونطاق
</h4>

<ul>
<li>
		لا نرشح في هذا المقال أيّ شركة استضافة أو شركة مسجِّلة، فعليك البحث بنفسك عما يناسبك، إذ تتيح لك كل المسجلات وسيلةً للتحقق من توفر اسم نطاق معين وحجزه.
	</li>
	<li>
		تمنحك بعض مزودات الخدمة الموجودة على حاسوبك الشخصي أو ضمن شبكة مكتبك إمكانية استضافة محدودة لموقعك، إذ لن تكون الميزات المتاحة كثيرةً، لكنها ممتازة لتجربته الموقع.
	</li>
	<li>
		ستجد أيضًا خدمات استضافة مجانية مثل Neocities و Google sites و Blogger و Wordpress، وقد تجني فائدة ما تدفع، لكن موارد مجانية مثل هذه قد تكوِّن خيارًا تجريبيًا ممتازًا أيضًا.
	</li>
	<li>
		تزوّدك بعض الشركات بخدمتي الاستضافة وحجز أسماء نطاقات معًا.
	</li>
</ul>
<h3>
	استخدام أدوات على شبكة الإنترنت مثل جيت-هاب و جوجل آب
</h3>

<p>
	تساعدك بعض الأدوات في نشر موقعك مثل:
</p>

<ul>
<li>
		جيت-هاب GitHub: وهو موقع كتابة شيفرة تشاركي يسمح لك برفع ملفات الشيفرة إلى مستودعات تخزين على منظومة التحكم بالإصدار Git، إذ تستطيع بعد ذلك مشاركة شيفرة المشاريع المختلفة والتعاون في العمل عليها، وطالما أنّ المنظومة مفتوحة المصدر، فيمكن لأيّ كان الوصول إلى الشيفرة على جيت-هاب واستخدامها والتعلم منها وتطويرها، كما تقدِّم جيت-هاب أيضًا ميزةً مفيدةً هي صفحات جيت-هاب GitHub Pages التي تسمح لك باستضافة شيفرة موقعك على ويب.
	</li>
	<li>
		محرّك جوجل آب Google App Engine: وهي منصة قوية تسمح لك ببناء وتشغيل التطبيقات بالاستفادة من البنية التحتية لجوجل، سواءً أردت بناء تطبيق ويب من الصفر أو استضافة موقع ويب ساكن، ولمزيد من المعلومات راجع مقال <a href="https://academy.hsoub.com/devops/servers/%D8%B1%D9%81%D8%B9-%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%85%D9%88%D9%82%D8%B9-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%B9%D9%84%D9%89-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r610/" rel="">رفع موقع ويب إلى شبكة الإنترنت</a>.
	</li>
</ul>
<p>
	هناك عدة خيارات أخرى مجانية، لكنك قد تتخطى بسهولة حدود الميزات المتاحة.
</p>

<h3>
	استخدام بيئة عمل متكاملة مبنية على ويب
</h3>

<p>
	هناك العديد من تطبيقات الويب التي تحاكي بيئة تطوير مواقع ويب وتسمح لك بإدخال شيفرة <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> و <a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> و<a href="https://wiki.hsoub.com/JavaScript" rel="external">جافاسكربت</a> واستعراض نتيجة تنفيذ الشيفرة على أساس موقع ويب حقيقي داخل نافذة المتصفح، وتُعَدّ هذه الأدوات عمومًا سهلة الاستخدام نسبيًا وممتازةً لأغراض التعلّم ومشاركة الشيفرة، إذ بإمكانك إذا أردت مشاركة أسلوب برمجي أو طلب مساعدة في تنقيح شيفرة من زميل في مكتب مجاور، كما أنها مجانية لبعض الميزات الأساسية، لكنها لا تزوّدك عادةً بمساحة لتخزين ملفات الدعم أو ملفات المساعدة والتي تدعى بالأصول assets مثل الصور، وحاول أن تجرب بعضها مثل JSFiddle و Glitch و JS Bin و CodePen g لتجد ما يناسبك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="102414" href="https://academy.hsoub.com/uploads/monthly_2022_07/02_web_based_IDE_JSBin.png.ad9efcacdda9a4fae17315c6a0e5ad52.png" rel=""><img alt="02_web_based_IDE_JSBin.png" class="ipsImage ipsImage_thumbnailed" data-fileid="102414" data-unique="e0kvpi8ap" src="https://academy.hsoub.com/uploads/monthly_2022_07/02_web_based_IDE_JSBin.png.ad9efcacdda9a4fae17315c6a0e5ad52.png" style="width: 750px; height: auto;"></a>
</p>

<h2>
	النشر باستخدام جيت-هاب
</h2>

<p>
	سنناقش الآن كيف تنشر موقعك على صفحات جيت-هاب بسهولة:
</p>

<ol>
<li>
		سجّل في موقع جيت-هاب أولًا وأكِّد امتلاكك عنوان البريد الإلكتروني الذي استخدمته في التسجيل.
	</li>
	<li>
		أنشئ مستودعًا لتخزين الملفات.
	</li>
	<li>
		أدخِل في الصندوق Repository name الظاهر في الصفحة الموضحة في الشكل التالي العبارة username.github.io، إذ يمثل username اسم المستخدم، فقد يُدخِل "مازن" الاسم "mazen.github.io" مثلًا، ثم فعِّل بعد ذلك الخيار Initialize this repository with a README وانقر بعدها زر أنشئ مستودعًا create repository.
	</li>
</ol>
<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="102415" href="https://academy.hsoub.com/uploads/monthly_2022_07/03_create_github_repo.png.808d03c133980c21720eccd2efdf2c17.png" rel=""><img alt="03_create_github_repo.png" class="ipsImage ipsImage_thumbnailed" data-fileid="102415" data-unique="4a8owqejs" src="https://academy.hsoub.com/uploads/monthly_2022_07/03_create_github_repo.png.808d03c133980c21720eccd2efdf2c17.png"></a>
</p>

<ol start="4">
<li>
		اسحب وأفلت محتوى الموقع ضمن المستودع واحفظ التغييرات.
	</li>
	<li>
		انتقل باستخدام متصفحك إلى الموقع "username.github.io -وفقًا لما اخترته-، وسيعرض محتوى موقعك.
	</li>
</ol>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظات:</strong>
		</p>

		<ul>
<li>
				 تأكد من وجود ملف باسم index.html ضمن ملفات موقعك.
			</li>
			<li>
				قد يستغرق الأمر عدة دقائق ليصبح موقعك جاهزًا للعرض على المتصفحات، فإذا لم يعرض متصفحك الموقع، فانتظر قليلًا ثم أعد المحاولة.
			</li>
		</ul>
</div>
</blockquote>

<p>
	ترجمة -وبتصرف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/Publishing_your_website" rel="external nofollow">Publishing your website</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/devops/servers/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%AE%D8%A7%D8%AF%D9%85-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r574/" rel="">مدخل إلى خادم ويب</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/networking/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D9%85%D8%A7%D8%A1-%D8%A7%D9%84%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%81%D9%8A-%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r573/" rel="">مدخل إلى أسماء النطاقات على شبكة الإنترنت</a>.
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/networking/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%B3%D9%85%D8%A7%D8%A1-%D8%A7%D9%84%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%81%D9%8A-%D8%B4%D8%A8%D9%83%D8%A9-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-r573/" rel="">أساسيات تحديد الكلفة المادية الكاملة لبناء موقع ويب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">628</guid><pubDate>Sat, 02 Jul 2022 16:08:03 +0000</pubDate></item><item><title>&#x645;&#x631;&#x62D;&#x644;&#x629; &#x646;&#x634;&#x631; &#x627;&#x644;&#x62A;&#x637;&#x628;&#x64A;&#x642; &#x641;&#x64A; &#x639;&#x645;&#x644;&#x64A;&#x629; &#x62A;&#x637;&#x648;&#x64A;&#x631; &#x627;&#x644;&#x648;&#x64A;&#x628;</title><link>https://academy.hsoub.com/devops/deployment/%D9%85%D8%B1%D8%AD%D9%84%D8%A9-%D9%86%D8%B4%D8%B1-%D8%A7%D9%84%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-%D9%81%D9%8A-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r589/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_02/6206199abab22_-------.png.1befc31623093f9250d3b0e8f81f7286.png" /></p>

<p>
	سنأخذ مثال سلسلة الأدوات الذي أنشأناه في المقال السابق، ثم نضيفه لنتمكن من نشر تطبيقنا، إذ سنرفع الشيفرة على GitHub، وننشر التطبيق باستخدام Netlify، وسنوضح كيفية تطبيق اختبار بسيط عليه.
</p>

<ul>
<li>
		<strong>المتطلبات الأساسية</strong>: الإلمام بأساسيات لغات <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> و<a href="https://wiki.hsoub.com/CSS" rel="external">CSS</a> و<a href="https://wiki.hsoub.com/JavaScript" rel="external">جافاسكربت</a>.
	</li>
	<li>
		<strong>الهدف</strong>: إنهاء العمل من خلال دراسة الحالة الكاملة لسلسلة الأدوات مع التركيز على مرحلة نشر التطبيق.
	</li>
</ul>
<h2>
	مرحلة ما بعد التطوير
</h2>

<p>
	يُحتَمل أن تواجه العديد من المشاكل التي يجب حلها في هذه المرحلة من دورة حياة المشروع، لذلك يجب إنشاء سلسلة أدوات تعالج هذه المشاكل بطريقة تتطلب أقل قدر ممكن من التدخل اليدوي.
</p>

<p>
	إليك بعض الأشياء التي يجب مراعاتها في المشروع:
</p>

<ul>
<li>
		إنشاء بنية إنتاج: ضمان تصغير الملفات وتقسيمها وتطبيق تقنية هز الشجرة وتعطيل ذاكرة الإصدارات المخبئية Cache Busted للمتصفح.
	</li>
	<li>
		تشغيل الاختبارات: يمكن أن تتراوح من "هل نُسِّقت هذه الشيفرة تنسيقًا صحيحًا؟" إلى "هل يطبّق هذا الشيء ما هو متوقع منه؟"، والتأكد من أن الاختبارات الفاشلة تمنع النشر.
	</li>
	<li>
		نشر الشيفرة المحدثة فعليًا إلى <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%88-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%81%D9%8A-%D8%A7%D9%84%D9%88%D9%8A%D8%A8%D8%9F-r1435/" rel="">عنوان URL</a> مباشر: أو عنوان URL مرحلي لمراجعتها أولًا.
	</li>
</ul>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظة</strong>: يُعَد تعطيل الذاكرة المخبئية Cache Busting مصطلحًا جديدًا، وهي إستراتيجية كسر آلية التخبئة الخاصة بالمتصفح، والتي تجبر المتصفح على تنزيل نسخة جديدة من شيفرتك. ستنشئ الأداة Parcel والعديد من الأدوات الأخرى أسماء ملفات فريدة لكل بناء جديد، وسيعطلُ اسم هذا الملف الفريد ذاكرة المتصفح المخبئية، وبالتالي يتأكد من أن ينزّل المتصفح الشيفرة الجديدة في كل تحديث للشيفرة المنشورة.
		</p>
	</div>
</blockquote>

<p>
	كما تنقسم المهام السابقة إلى مهام أخرى، لأن معظم فرق <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D9%84%D9%85-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">تطوير الويب</a> لها شروطها وعملياتها الخاصة لجزء من مرحلة ما بعد التطوير على الأقل.
</p>

<p>
	سنستخدم في مشروعنا عرض الاستضافة الثابت من Netlify لاستضافة مشروعنا. تمنحنا Netlify استضافة أو عنوان URL لعرض المشروع عبر الإنترنت ومشاركته مع أصدقائك وعائلتك وزملائك.
</p>

<p>
	يكون النشر على الاستضافة في نهاية دورة حياة المشروع، ولكن تعمل خدمات مثل Netlify على خفض تكلفة النشر من الناحية المالية والوقت المطلوب للنشر الفعلي، إذ يمكن النشر أثناء التطوير إلى مشاركة العمل أو الحصول على إصدار تجريبي لأغراض الأخرى.
</p>

<p>
	تتيح خدمة Netlify تشغيل مهام ما قبل النشر، وهو ما يعني في حالتنا أنه يمكن تنفيذ جميع عمليات إنشاء شيفرة الإنتاج ضمن Netlify وإذا نجح الإصدار، فستُنشَر تغييرات موقع الويب.
</p>

<p>
	تقدّم Netlify <a href="https://app.netlify.com/drop" rel="external nofollow">خدمة النشر بالسحب والإفلات Drag and Drop Deployment Service</a>، لكننا نعتزم بدء نشر جديد إلى Netlify في كل مرة نرفع فيها الشيفرة على مستودع GitHub.
</p>

<p>
	إنها بالضبط أنواع الخدمات المتصلة التي نشجعك على البحث عنها عند اتخاذ قرار بشأن سلسلة أدوات البناء الخاصة بك. يمكننا رفع شيفرتنا على GitHub، وستشغّل الشيفرة المحدثة تلقائيًا منهج البناء الكامل. إذا كان كل شيء على ما يرام، فسيُنشَر التغيير المباشر تلقائيًا، لكن الإجراء الوحيد الذي نحتاجه هو الرفع الأولي.
</p>

<h2>
	عملية البناء
</h2>

<p>
	بما أننا نستخدم Parcel للتطوير، فإن خيار البناء سهل الإضافة. يمكننا تشغيل الخادم باستخدام الأمر <code>npx parcel build src/index.html</code> بدلًا من الأمر <code>npx parcel src/index.html</code>، وستبني Parcel كل شيء جاهزًا للإنتاج بدلًا من تشغيله لأغراض التطوير والاختبار فقط، ويتضمن ذلك تصغير الشيفرة وتطبيق تقنية هز الشجرة عليها، وتعطيل الذاكرة المخبئية على أسماء الملفات.
</p>

<p>
	تُوضَع شيفرة الإنتاج التي أنشأناها في دليل جديد يسمى <code>dist</code> يحتوي على جميع الملفات المطلوبة لتشغيل موقع الويب، ويكون جاهزًا للتحميل على الخادم. ليس تطبيق هذه الخطوة يدويًا هدفنا النهائي، بل نريد أن يحدث البناء تلقائيًا وأن تُنشَر نتيجة الدليل <code>dist</code> مباشرة على موقعنا على الإنترنت.
</p>

<p>
	يجب إعداد شيفرتنا وGitHub وNetlify للتواصل مع بعضها بعضًا، ليكتشف Netlify التغييرات تلقائيًا ويشغّل مهام البناء ويصدر تحديثًا جديدًا في كل مرة نحدّث فيها مستودع شيفرة GitHub.
</p>

<p>
	سنضيف أمر البناء إلى الملف <code>package.json</code> بوصفه سكربت npm، ليشغّل الأمر <code>npm run build</code> عملية البناء. ليست هذه الخطوة ضرورية، لكنها أفضل ممارسة جيدة لعادة الإعداد في جميع المشاريع، ثم يمكننا الاعتماد على الأمر <code>npm run build</code> لتطبيق خطوة البناء الكاملة، دون الحاجة إلى تذكر وسطاء أمر البناء المحدَّدة لكل مشروع.
</p>

<ol>
<li>
		افتح الملف <code>package.json</code> في الدليل الجذر لمشروعك، وابحث عن الخاصية <code>scripts</code>.
	</li>
	<li>
		سنضيف الأمر <code>build</code> الذي يمكننا تشغيله لبناء شيفرتنا. أضف السطر التالي إلى مشروعك:
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3548_15" style="">
<span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">...</span><span class="pln">
  </span><span class="str">"build"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"parcel build src/index.html"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	<strong>ملاحظة</strong>: إذا احتوت الخاصية <code>scripts</code> على أمر ضمنها، فضع فاصلة في نهايتها حسب <a href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B9%D9%84%D9%85-json-r604/" rel="">صيغة JSON</a>.
</p>

<ol start="3">
<li>
		يجب أن تكون الآن قادرًا على تشغيل الأمر التالي في جذر دليل مشروعك لتشغيل خطوة بناء الإنتاج، ولكن أنهِ أولًا عملية التشغيل باستخدام الاختصار <code>Ctrl + C</code>:
	</li>
</ol>
<pre class="ipsCode">
npm run build
</pre>

<p>
	يكون خرج الأمر السابق كما يلي، ويوضح هذا الخرج ملفات الإنتاج المُنشَأة، وحجمها، والمدة التي استغرقتها للبناء:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3548_19" style="">
<span class="pln">dist</span><span class="pun">/</span><span class="pln">src</span><span class="pun">.</span><span class="lit">99d8a31a</span><span class="pun">.</span><span class="pln">js</span><span class="pun">.</span><span class="pln">map       </span><span class="lit">446.15</span><span class="pln"> KB     </span><span class="lit">63ms</span><span class="pln">
dist</span><span class="pun">/</span><span class="pln">src</span><span class="pun">.</span><span class="lit">99d8a31a</span><span class="pun">.</span><span class="pln">js           </span><span class="lit">172.51</span><span class="pln"> KB    </span><span class="lit">5.55s</span><span class="pln">
dist</span><span class="pun">/</span><span class="pln">stars</span><span class="pun">.</span><span class="lit">7f1dd035.svg</span><span class="pln">          </span><span class="lit">6.31</span><span class="pln"> KB    </span><span class="lit">145ms</span><span class="pln">
dist</span><span class="pun">/</span><span class="pln">asteroid2</span><span class="pun">.</span><span class="lit">3ead4904.svg</span><span class="pln">      </span><span class="lit">3.51</span><span class="pln"> KB    </span><span class="lit">155ms</span><span class="pln">
dist</span><span class="pun">/</span><span class="pln">asteroid1</span><span class="pun">.</span><span class="lit">698d75e9</span><span class="pun">.</span><span class="pln">svg       </span><span class="lit">2.9</span><span class="pln"> KB    </span><span class="lit">153ms</span><span class="pln">
dist</span><span class="pun">/</span><span class="pln">src</span><span class="pun">.</span><span class="lit">84f2edd1.css</span><span class="pun">.</span><span class="pln">map        </span><span class="lit">2.57</span><span class="pln"> KB      </span><span class="lit">3ms</span><span class="pln">
dist</span><span class="pun">/</span><span class="pln">src</span><span class="pun">.</span><span class="lit">84f2edd1.css</span><span class="pln">            </span><span class="lit">1.25</span><span class="pln"> KB    </span><span class="lit">1.53s</span><span class="pln">
dist</span><span class="pun">/</span><span class="pln">bg</span><span class="pun">.</span><span class="lit">084d3fd3.svg</span><span class="pln">               </span><span class="lit">795</span><span class="pln"> B    </span><span class="lit">147ms</span><span class="pln">
dist</span><span class="pun">/</span><span class="pln">index</span><span class="pun">.</span><span class="pln">html                    </span><span class="lit">354</span><span class="pln"> B    </span><span class="lit">944ms</span></pre>

<p>
	يجب استضافة شيفرة المشروع في مستودع git الخاص بك لتتمكّن من إنشاء نسخة منه. خطوتنا التالية هي رفع المشروع على GitHub.
</p>

<h2>
	تنفيذ التغييرات على GitHub
</h2>

<p>
	سيساعدك هذا القسم على تجاوز حدود تخزين شيفرتك في مستودع git، ولكنك لن تتعلّم <a href="https://academy.hsoub.com/programming/workflow/git/%d9%85%d8%af%d8%ae%d9%84-%d8%a5%d9%84%d9%89-%d9%86%d8%b8%d8%a7%d9%85-%d8%a7%d9%84%d8%aa%d8%ad%d9%83%d9%85-%d9%81%d9%8a-%d8%a7%d9%84%d9%86%d8%b3%d8%ae-git-r240/" rel="">git</a> بالتفصيل.
</p>

<p>
	هيّأنا دليل العمل بوصفه دليل عمل git سابقًا، وهناك طريقة سريعة للتحقق من ذلك وهي تشغيل الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3548_21" style="">
<span class="pln">git status</span></pre>

<p>
	يجب أن تحصل على تقرير بحالة الملفات المُتتبَّعة والملفات المُنظَّمة وما إلى ذلك، وتُعَد هذه المصطلحات جزءًا من قواعد git. إذا حصلتَ على الخطأ <code>fatal: not a git repository</code>، فهذا يدل على أن دليل العمل ليس دليل عمل <a href="https://academy.hsoub.com/programming/workflow/git/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-git-r658/" rel="">git</a>، وبالتالي يجب تهيئة git باستخدام الأمر <code>git init</code>.
</p>

<p>
	أمامنا الآن ثلاث مهام وهي:
</p>

<ul>
<li>
		إضافة التغييرات التي أجريناها إلى مكان يدعَى stage، وهو اسم خاص بالمكان الذي يودع git الملفات فيه.
	</li>
	<li>
		تنفيذ التغييرات على المستودع.
	</li>
	<li>
		رفع التغييرات على GitHub.
	</li>
</ul>
<p>
	أولًا، يمكنك إضافة التغييرات من خلال تشغيل الأمر التالي:
</p>

<pre class="ipsCode">
git add .
</pre>

<p>
	لاحظ النقطة في النهاية التي تعني "كل شيء في هذا الدليل". يشبه الأمر <code>git add .‎</code> إلى حدٍ ما نهج المطرقة، إذ سيضيف جميع التغييرات المحلية التي عملت عليها دفعة واحدة. إن أردت تحكمًا أفضل فيما تضيفه، فاستخدم الأمر <code>git add -p</code> للعمليات التفاعلية، أو أضف ملفات باستخدام الأمر <code>git add path/to/file</code>.
</p>

<p>
	ثانيًا، أصبحت الآن الشيفرة منظمة، ويمكننا تثبيت التغيير من خلال تشغيل الأمر التالي:
</p>

<pre class="ipsCode">
git commit -m ’committing initial code’
</pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظة</strong>: بالرغم من أنك تتمتع بحرية كتابة ما تريد في رسالة تثبيت التغيير، إلا أن هناك بعض النصائح المفيدة في الويب حول رسائل الالتزام الجيدة. اجعلها قصيرة وموجزة ووصفية، لتصف بوضوح ما يفعله التغيير المحدَّد.
		</p>
	</div>
</blockquote>

<p>
	ثالثًا، أخيرًا، يجب رفع الشيفرة على مستودع GitHub المستضاف. يمكنك زيارة الصفحة <a href="https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fnew" rel="external nofollow">new</a> في موقع github لإنشاء مستودعك لاستضافة هذه الشيفرة.
</p>

<p>
	رابعًا، امنح مستودعك اسمًا قصيرًا يسهل تذكره بدون مسافات (استخدم الشرطات لفصل الكلمات)، واكتب وصفًا مناسبًا، ثم انقر على زر إنشاء مستودع Create Repository في أسفل الصفحة. يجب أن يكون لديك الآن عنوان URL بعيد يؤشّر إلى مستودع GitHub الجديد الخاص بك.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91769" href="https://academy.hsoub.com/uploads/monthly_2022_02/01_github-quick-setup.png.82bc8ebaea4b5a1149f3eb504c8b5cf3.png" rel=""><img alt="01_github-quick-setup.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91769" data-unique="a1fm3lg93" src="https://academy.hsoub.com/uploads/monthly_2022_02/01_github-quick-setup.thumb.png.9247df2d809d19b86ec72bebc153959a.png"></a>
</p>

<p>
	خامسًا، يجب إضافة هذا الموقع البعيد إلى مستودع git المحلي قبل أن نتمكن من رفعه هناك، وإلا فلن يتمكن من العثور عليه. يجب تشغيل أمر له البنية التالية (استخدم خيار HTTPS المُقدَّم حاليًا وليس خيار <a href="https://academy.hsoub.com/devops/security/ssh/%D8%A3%D9%86%D9%81%D8%A7%D9%82-ssh%D8%8C-%D9%85%D8%A7%D9%87%D9%8A%D8%AA%D9%87%D8%A7-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D8%B9%D8%AF%D8%A7%D8%AF%D9%87%D8%A7-r76/" rel=""><abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr></a>، خاصة إذا كنت جديدًا على GitHub):
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3548_23" style="">
<span class="pln">git remote add github https</span><span class="pun">:</span><span class="com">//github.com/yourname/repo-name.git</span></pre>

<p>
	لذلك إذا كان <a href="https://academy.hsoub.com/programming/general/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%B9%D9%86%D9%88%D8%A7%D9%86-url-%D9%88%D8%A3%D9%86%D9%88%D8%A7%D8%B9%D9%87-r1435/" rel="">عنوان URL</a> البعيد الخاص بك هو <a href="https://github.com/remy/super-website.git" ipsnoembed="false" rel="external nofollow">https://github.com/remy/super-website.git</a> -كما في لقطة الشاشة أعلاه- فسيكون الأمر كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3548_25" style="">
<span class="pln">git remote add github https</span><span class="pun">:</span><span class="com">//github.com/remy/super-website.git</span></pre>

<p>
	غيّر عنوان URL إلى المستودع الخاص بك، وشغّل الأمر.
</p>

<p>
	سادسًا، أصبحنا الآن جاهزين لرفع شيفرتنا على GitHub، ويمكنك الآن تشغيل الأمر التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3548_27" style="">
<span class="pln">git push github main</span></pre>

<p>
	سيُطلب منك الآن إدخال اسم مستخدم وكلمة مرور قبل أن يسمح Git بإرسال الرفع، لأننا استخدمنا خيار HTTPS بدلًا من خيار <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr> كما رأينا سابقًا. لذلك تحتاج إلى اسم مستخدم Github الخاص بك وكلمة مرور -إن لم تكن المصادقة الثنائية Two-Factor Authentication -أو 2FA اختصارًا- مفعّلة فإننا نشجعك دائمًا على تفعيلها، ولكن ضع في بالك أنك إذا فعلتها فستحتاج لاستخدام رمز وصول شخصي بجانب كلمة السر الخاصة بالحساب. تحتوي <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token" rel="external nofollow">صفحات المساعدة على Github</a> على إرشادات بسيطة وممتازة تغطي كيفية الحصول على هذا الرمز.
</p>

<p>
	<strong>ملاحظة</strong>: إذا كنت مهتمًا باستخدام خيار <abbr title="Secure Shell | القشرة (أو الصَدَفة) الآمنة">SSH</abbr>، وبالتالي تجنب الحاجة إلى إدخال اسم المستخدم وكلمة المرور في كل مرة ترفع فيها شيفرة على GitHub، فيمكنك الاطلاع على فيديو <a href="https://academy.hsoub.com/programming/workflow/git/%D9%81%D9%8A%D8%AF%D9%8A%D9%88-%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D8%A8%D8%AE%D8%AF%D9%85%D8%A9-github-%D8%AF%D9%88%D9%86-%D9%83%D9%84%D9%85%D8%A9-%D8%B3%D8%B1-r661/" rel="">الاتصال بخدمة GitHub دون كلمة سر</a>.
</p>

<p>
	يوجّه الأمر السابق git لرفع الشيفرة -أو ما يسمى بالنشر- على الموقع البعيد الذي أطلقنا عليه اسم <code>github</code> -وهو المستودع المستضاف على github.com ويمكننا أن نطلق عليه أي اسم نريده- باستخدام الفرع <code>main</code>. لم ننشئ أي فروع إضافية على هذا المشروع الإطلاق، ولكن الفرع <code>main</code> هو الفرع الافتراضي لعملنا وهو ما يؤسسه git بصورة افتراضية، وهو أيضًا الفرع الافتراضي الذي سيبحث عنه Netlify.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<div class="ipsQuote_contents ipsClearfix">
		<p>
			<strong>ملاحظة</strong>: كان الفرع الافتراضي على GitHub هو الفرع <code>master</code> حتى أكتوبر من عام 2020، ثم بُدِّل إلى <code>main</code> لأسباب مختلفة. يجب أن تدرك أن هذا الفرع الافتراضي القديم يمكن أن يظهر في العديد من المشاريع، لكننا نقترح استخدام <code>main</code> في مشاريعك.
		</p>
	</div>
</blockquote>

<p>
	الخطوة التالية في سلسلة الأدوات هي توصيل GitHub مع Netlify لنشر مشروعنا مباشرة على الويب.
</p>

<h2>
	استخدام Netlify للنشر
</h2>

<p>
	يُعَد النشر من GitHub إلى Netlify أمرًا بسيطًا بمجرد معرفة الخطوات، خاصة مع مواقع الويب الثابتة Static Websites مثل مشروعنا.
</p>

<ol>
<li>
		انتقل إلى <a href="https://app.netlify.com/start" rel="external nofollow">صفحة البداية في Netlify</a>.
	</li>
	<li>
		اضغط على زر Github أسفل عنوان النشر المستمر Continuous Deployment الذي يعني أنه كلما تغير مستودع الشيفرة، فسيحاول Netlify نشرها، وبالتالي فهي مستمرة.
	</li>
</ol>
<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91770" href="https://academy.hsoub.com/uploads/monthly_2022_02/02_netlify-deploy.png.5c8db1555a62ddba0b8795a02903be8d.png" rel=""><img alt="02_netlify-deploy.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91770" data-unique="xg7mudto9" src="https://academy.hsoub.com/uploads/monthly_2022_02/02_netlify-deploy.thumb.png.4880d5d04359c927b154d338e49c450e.png" style="width: 600px; height: auto;"></a>
</p>

<ol start="3">
<li>
		يمكن أن تحتاج إلى ترخيص Netlify مع GitHub اعتمادًا على ما إذا أعطيت Netlify ترخيصًا من قبل، واختيار الحساب الذي تريد إعطاءه ترخيصًا، إذا كان لديك عدة حسابات أو مؤسسات على GitHub. اختر الحساب الذي رفعت مشروعك عليه.
	</li>
	<li>
		سيطالبك Netlify بقائمة من مستودعات GitHub التي يمكنك العثور عليها. حدد مستودع مشروعك وانتقل إلى الخطوة التالية.
	</li>
	<li>
		بما أننا ربطنا Netlify بحساب Github وأعطيناه إذن الوصول لنشر مستودع المشروع، فسيسأل Netlify عن كيفية إعداد المشروع للنشر وما الذي يجب نشره. يجب إدخال الأمر <code>npm run build</code> وتحديد الدليل <code>dist</code> لدليل النشر الذي يحتوي على الشيفرة التي نريد جعلها عامة.
	</li>
	<li>
		انقر على نشر الموقع Deploy site في النهاية.
	</li>
</ol>
<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="91771" href="https://academy.hsoub.com/uploads/monthly_2022_02/03_netlify-dist.png.1311b3e02cfc8511c396cca588f5fd60.png" rel=""><img alt="03_netlify-dist.png" class="ipsImage ipsImage_thumbnailed" data-fileid="91771" data-unique="yjwn4lyr3" src="https://academy.hsoub.com/uploads/monthly_2022_02/03_netlify-dist.thumb.png.862915e28a4b4fb9673de62334a3a209.png"></a>
</p>

<ol start="7">
<li>
		يجب أن تحصل بعد انتظار قصير لحدوث النشر على عنوان URL يمكنك الانتقال إليه لرؤية موقعك المنشور.
	</li>
	<li>
		إذا أجريت تغييرًا ورفعت التغيير إلى مستودع git البعيد على <a href="https://academy.hsoub.com/programming/workflow/git/%d9%83%d9%8a%d9%81-%d8%aa%d8%b3%d8%a7%d9%87%d9%85-%d9%81%d9%8a-%d9%85%d8%b4%d8%a7%d8%b1%d9%8a%d8%b9-%d9%85%d9%81%d8%aa%d9%88%d8%ad%d8%a9-%d8%a7%d9%84%d9%85%d8%b5%d8%af%d8%b1-%d8%b9%d9%84%d9%89-github-r265/" rel="">GitHub</a>، فسيؤدي ذلك إلى إرسال إشعار إلى Netlify الذي سيشغّل مهمة البناء ثم ينشر دليل <code>dist</code> الناتج على موقعنا المنشور. جرّب إجراء تغيير بسيط على تطبيقك، ثم ارفعه إلى GitHub باستخدام الأوامر التالية:
	</li>
</ol>
<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_3548_31" style="">
<span class="pln">git add .
git commit -m ‘simple netlify test’
git push github main</span></pre>

<p>
	يجب أن ترى تحديث موقعك المنشور بالتغيير. يستغرق ذلك بضع دقائق للنشر، لذا تحلى بالصبر.
</p>

<p>
	يمكننا اختياريًا تغيير اسم مشروع Netlify أو تحديد استخدام اسم نطاقنا الذي يقدّم Netlify بعض الوثائق الممتازة عنه.
</p>

<h2>
	الاختبار
</h2>

<p>
	يُعَد الاختبار بحد ذاته موضوعًا واسعًا حتى في مجال تطوير الواجهة الأمامية. سنوضح كيفية إضافة اختبار أولي إلى مشروعك وكيفية استخدام الاختبار للسماح بنشر المشروع أو منعه في حال وجود مشاكل.
</p>

<p>
	هناك طرق متعددة للتعامل مع مشاكل الاختبارات هي:
</p>

<ul>
<li>
		الاختبار الشامل End-to-end Testing: يتضمن نقر الزائر على شيء ما مع حدوث بعض الأمور الأخرى.
	</li>
	<li>
		اختبار التكامل Integration Testing: يتضمن هذا الاختبار السؤال: "هل ستستمر بالعمل إحدى كتل الشيفرة بطريقة صحيحة عند اتصالها بكتلة أخرى؟"
	</li>
	<li>
		اختبار الوحدة Unit Testing: تُختبَر أجزاء صغيرة ومحددة من الوظائف لمعرفة ما إذا كانت تفعل ما يفترض منها فعله.
	</li>
</ul>
<p>
	تذكّر أيضًا أن الاختبارات لا تقتصر على شيفرة جافاسكربت، إذ يمكن تشغيل الاختبارات على <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-dom-r644/" rel="">DOM</a> المُصيَّر، وتفاعلات المستخدم، <a href="https://academy.hsoub.com/programming/css/%d8%aa%d8%b9%d8%b1%d9%91%d9%81-%d8%b9%d9%84%d9%89-%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a7%d8%aa-css-r70/" rel="">وCSS</a>، وحتى على مظهر الصفحة.
</p>

<p>
	سننشئ اختبارًا صغيرًا في مشروعنا يتحقق من بيانات وكالة ناسا التابعة لجهة خارجية والتأكد من أنها في التنسيق الصحيح. إذا لم يكن الأمر كذلك، فسيفشل الاختبار وسيمنع المشروع من العمل. الاختبار موضوع ضخم يتطلب مقالات منفصلة خاصة به، ولكننا نأمل أن يجعلك هذا القسم على الأقل مدركًا لأهمية الاختبار.
</p>

<p>
	لا يتضمن اختبار هذا المشروع إطارًا اختباريًا، إلا أن هناك عددًا كبيرًا من خيارات إطار العمل. ليس الاختبار مهمًا بحد ذاته، فالمهم هو كيفية التعامل مع فشل أو نجاح الاختبار. ستتضمن بعض منصات النشر طريقة محددة للاختبار كجزء من سير العمل الخاصة بها. تدعم جميع المنتجات مثل GitHub وGitLab إجراء الاختبارات على التنفيذات.
</p>

<p>
	بما أننا ننشر مشروعنا على Netlify الذي لا يسأل إلا عن أمر البناء، فسيتعين علينا جعل الاختبارات جزءًا من عملية البناء. إذا فشل الاختبار، فسيفشل البناء، ولن ينشر Netlify.
</p>

<p>
	أولًا، انتقل إلى الملف <code>package.json</code> وافتحه.
</p>

<p>
	ثانيًا، ابحث عن الخاصية <code>scripts</code> وحدّثها لتحتوي على أوامر البناء والاختبار التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3548_33" style="">
<span class="str">"scripts"</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">…</span><span class="pln">
  </span><span class="str">"test"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"node tests/*.js"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"build"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"npm run test &amp;&amp; parcel build src/index.html"</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ثالثًا، يجب الآن إضافة الاختبار إلى قاعدة شيفرتنا. أنشئ دليلًا جديدًا في الدليل الجذر الخاص بك وسمِّه tests:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3548_35" style="">
<span class="pln">mkdir tests</span></pre>

<p>
	رابعًا، أنشئ ملف اختبار ضمن الدليل الجديد:
</p>

<pre class="ipsCode">
cd tests
touch nasa-feed.test.js
</pre>

<p>
	خامسًا، افتح هذا الملف وأضف محتويات الملف <a href="https://raw.githubusercontent.com/remy/mdn-will-it-miss/master/tests/nasa-feed.test.js" rel="external nofollow">nasa-feed.test.js</a> إليه. سادسًا، يستخدم هذا الاختبار حزمة axios لجلب البيانات التي نريد اختبارها. شغّل الأمر التالي لتثبيت هذه الاعتمادية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_3548_37" style="">
<span class="pln">npm install </span><span class="pun">--</span><span class="pln">save</span><span class="pun">-</span><span class="pln">dev axios</span></pre>

<p>
	يجب تثبيت axios يدويًا لأن Parcel لن تساعدنا فيها. تقع اختباراتنا خارج نطاق رؤية Parcel في نظامنا، نظرًا لأن Parcel لا ترى أو تدير أيًا من شيفرة الاختبار، لذلك يجب تثبيت الاعتمادية بأنفسنا.
</p>

<p>
	سابعًا، يمكننا تشغيل الأمر التالي في سطر الأوامر لإجراء الاختبار يدويًا:
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_3548_39" style="">
<span class="pln">npm run test</span></pre>

<p>
	إذا نجحت عملية الاختبار، فالنتيجة هي لا شيء، وهذا يُعَد نجاحًا بحد ذاته. كما جرى الخروج من الاختبار بإشارة خاصة تخبر سطر الأوامر بأن الاختبار ناجح، وتكون قيمة إشارة الخروج 0، وإذا كان هناك فشل، فسيفشل الاختبار مع رمز الخروج 1، وهي قيمة على مستوى النظام تدل على حدوث فشل شيء ما. يستخدم الأمر <code>npm run test</code> <a href="https://wiki.hsoub.com/Node.js" rel="external">لغة Node.Js</a> لتشغيل جميع الملفات الموجودة في دليل الاختبارات التي تنتهي بالامتداد <code>‎.js</code>. يُستدعَى الأمر <code>npm run test</code> في سكربت البناء، ثم سترى السلسلة <code>&amp;&amp;</code> التي تعني أنه "إذا نجح الشيء الموجود على اليسار (الخرج صفر)، فافعل الشيء الموجود على اليمين"، أي إذا نجحت الاختبارات، فطبّق بناء الشيفرة.
</p>

<p>
	ثامنًا، يجب رفع الشيفرة الجديدة إلى GitHub باستخدام أوامر التالية المماثلة لما استخدمته سابقًا:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_3548_41" style="">
<span class="pln">git add .
git commit -m ‘adding test’
git push github main</span></pre>

<p>
	يمكن أن ترغب في بعض الأحيان في اختبار نتيجة شيفرة البناء، لأنها ليست الشيفرة الأصلية التي كتبناها، لذلك يجب تشغيل الاختبار بعد أمر البناء.
</p>

<p>
	أخيرًا، سينشر Netlify تحديث المشروع بعد دقيقة أو نحو ذلك من الرفع، إذا اجتاز الاختبار فقط.
</p>

<h2>
	الخلاصة
</h2>

<p>
	لا يزال هناك طريق طويل لقطعه قبل أن تتمكن من عَدّ نفسك ممتازًا في استخدام الأدوات من طرف العميل، لكن نأمل أن تكون هذه السلسلة من المقالات منحتك أول خطوة مهمة نحو فهم هذه الأدوات.
</p>

<p>
	لنلخص جميع أجزاء سلسلة الأدوات:
</p>

<ol>
<li>
		تُطبَّق جودة الشيفرة وصيانتها بواسطة الأداتين Eslint وPrettier، وتُضاف هذه الأدوات بوصفها اعتماديات تطوير devDependencies إلى المشروع عبر الأمر <code>npm install --dev eslint prettier eslint-plugin-react</code>. كما يجب استخدام إضافة Eslint لأن المشروع يستخدم <a href="https://academy.hsoub.com/programming/javascript/react/" rel="">React</a>.
	</li>
	<li>
		هناك نوعان من ملفات الإعداد التي تقرأها أدوات جودة الشيفرة هما: <code>‎.eslintrc</code> و<code>‎.prettierrc</code>.
	</li>
	<li>
		نستخدم أداة Parcel أثناء التطوير للتعامل مع الاعتماديات. يعمل <code>parcel src/index.html</code> في الخلفية لمراقبة التغييرات وبناء الشيفرة المصدرية تلقائيًا.
	</li>
	<li>
		يُعالَج النشر عن طريق رفع التغييرات إلى Github في الفرع <code>main</code>، مما يؤدي إلى البناء والنشر على Netlify لنشر المشروع. يكون عنوان URL في مثالنا هو <a href="https://near-misses.netlify.com" rel="external nofollow">near-misses.netlify.com</a>، وسيكون لديك عنوان URL الفريد الخاص بك.
	</li>
	<li>
		كما يوجد اختبار بسيط يمنع بناء ونشر الموقع إذا لم تعطنا NASA <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> تنسيق البيانات الصحيح.
	</li>
</ol>
<p>
	هذا المقال جزء من سلسلة مقالات بعنوان <a href="https://academy.hsoub.com/search/?tags=%D8%AA%D8%B9%D9%84%D9%85%20%D8%AA%D8%B7%D9%88%D9%8A%D8%B1%20%D8%A7%D9%84%D9%88%D9%8A%D8%A8&amp;sortby=newest&amp;page=1" rel="">تعلم تطوير الويب</a> والتي تشرح كامل <a href="https://academy.hsoub.com/programming/general/%D8%AA%D8%B9%D9%84%D9%85-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8/" rel="">عملية تطوير الويب</a> من واجهات أمامية وخلفية بالكامل.
</p>

<p>
	ترجمة -وبتصرُّف- للمقال <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment" rel="external nofollow">Deploying our app</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/workflow/%D8%A8%D9%86%D8%A7%D8%A1-%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D9%83%D8%A7%D9%85%D9%84-%D9%84%D8%B3%D9%84%D8%B3%D9%84%D8%A9-%D8%A3%D8%AF%D9%88%D8%A7%D8%AA-%D8%AA%D8%B7%D9%88%D9%8A%D8%B1-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%85%D9%86-%D8%B7%D8%B1%D9%81-%D8%A7%D9%84%D8%B9%D9%85%D9%8A%D9%84-r1473/" rel="">بناء نموذج كامل لسلسلة أدوات تطوير الويب من طرف العميل</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/react/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A8%D9%86%D8%A7%D8%A1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r1071/" rel="">أساسيات بناء تطبيقات الويب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/ruby/rails/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D9%86%D8%B4%D8%B1-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82-rails-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-azk-r611/" rel="">كيفية نشر تطبيق Rails باستخدام AZK</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">589</guid><pubDate>Fri, 25 Feb 2022 16:04:00 +0000</pubDate></item></channel></rss>
