{"id":"l-aws-4-1","courseId":"aws-cloud-foundations","lang":"ar","kind":"lesson","summary":"انشر الواجهة الأمامية لـ Lantern من bucket خاص في S3 عبر Amazon CloudFront مع origin access control، وHTTPS على نطاق مخصّص، وترويسات cache مدروسة، وعمليات إبطال (invalidations).","html":"<p>أتذكر مهرجان طعام الشارع الذي أسقط Lantern؟ معظم تلك الطلبات كانت لبضعة ملفات هي نفسها: الصفحة الرئيسية، وملف أنماط، وJavaScript، وملصق واحد. الواجهة الأمامية لـ Lantern تطبيق Vite يُبنى إلى ملفات ثابتة، فلا يحتاج إلى خادم أصلًا. إنه يحتاج إلى مكان يحفظ فيه الملفات، <strong>Amazon S3</strong>، وإلى cache عالمي أمامه، <strong>Amazon CloudFront</strong>.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الصورة العامة</h2>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 230\" role=\"img\" aria-labelledby=\"l-aws-4-1-t1\"><defs><marker id=\"arrow-l-aws-4-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-aws-4-1-t1\">يطلب المتصفّح lantern.example عبر HTTPS من موقع حافة في CloudFront. إذا وُجد الملف في الـ cache يجيب موقع الحافة مباشرة. وإذا لم يوجد، يوقّع CloudFront طلبًا عبر origin access control إلى الـ bucket الخاص في S3، الذي لا يسمح إلا لذلك الـ distribution.</title>\n  <rect class=\"d-box\" x=\"20\" y=\"90\" width=\"120\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"80\" y=\"123\" text-anchor=\"middle\">المتصفّح</text>\n  <rect class=\"d-box-accent\" x=\"240\" y=\"80\" width=\"170\" height=\"76\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"325\" y=\"110\" text-anchor=\"middle\">CloudFront edge</text>\n  <text class=\"d-label-muted\" x=\"325\" y=\"132\" text-anchor=\"middle\">cache</text>\n  <rect class=\"d-box-primary\" x=\"510\" y=\"80\" width=\"150\" height=\"76\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"585\" y=\"110\" text-anchor=\"middle\">S3 bucket</text>\n  <text class=\"d-label-muted\" x=\"585\" y=\"132\" text-anchor=\"middle\">خاص، BPA مفعّل</text>\n  <path class=\"d-arrow\" d=\"M140 108 L236 108\" marker-end=\"url(#arrow-l-aws-4-1-1)\"/>\n  <text class=\"d-label\" x=\"188\" y=\"98\" text-anchor=\"middle\">HTTPS</text>\n  <path class=\"d-arrow d-dashed\" d=\"M238 132 L144 132\" marker-end=\"url(#arrow-l-aws-4-1-1)\"/>\n  <text class=\"d-label-muted\" x=\"190\" y=\"152\" text-anchor=\"middle\">cache hit</text>\n  <path class=\"d-arrow\" d=\"M410 108 L506 108\" marker-end=\"url(#arrow-l-aws-4-1-1)\"/>\n  <text class=\"d-label\" x=\"458\" y=\"98\" text-anchor=\"middle\">miss: طلب موقَّع</text>\n  <text class=\"d-label-muted\" x=\"325\" y=\"200\" text-anchor=\"middle\">سياسة الـ bucket: السماح بـ s3:GetObject لهذا الـ distribution فقط</text>\n</svg><figcaption>يصل الزوّار إلى موقع حافة قريب؛ ولا يبلغ الـ bucket الخاص إلا ما لم يوجد في الـ cache</figcaption></figure>\n<p>معظم الطلبات <strong>cache hits</strong>، يجيب عنها موقع حافة قريب من الزائر دون لمس S3. وعند <strong>الـ miss</strong> (عدم وجود الملف في الـ cache)، يجلب CloudFront الملف من الـ bucket، موقّعًا الطلب عبر <strong>origin access control (OAC)</strong>. يبقى الـ bucket خاصًّا، مع تفعيل Block Public Access، وتسمح سياسته بالقراءة من ذلك الـ distribution وحده.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">خطوة بخطوة</h2>\n<h3 id=\"sec-3\" tabindex=\"-1\">1. ابنِ التطبيق وأنشئ bucket خاصًّا</h3>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> run</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> build</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">                      # Vite writes dist/</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> s3</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> mb</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> s3://lantern-site-prod</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --region</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> eu-west-1</span></span></code></pre></div>\n<p>الـ bucket الجديد خاص افتراضيًا. <strong>لا</strong> تفعّل استضافة المواقع الثابتة: يحتاج OAC إلى نقطة النهاية العادية للـ bucket، أما نقطة نهاية الموقع فلا تتحدّث إلا HTTP وتتطلّب وصولًا عامًّا.</p>\n<h3 id=\"sec-4\" tabindex=\"-1\">2. ارفع الملفات بترويسات الـ cache الصحيحة</h3>\n<p>يضع Vite قيمة hash للمحتوى في أسماء ملفات الأصول (<code dir=\"ltr\">assets/index-4f8a1c.js</code>)، فيحصل الملف المتغيّر دائمًا على اسم جديد. يمكن تخزين هذه الملفات مؤقتًا لمدة سنة. أما <code dir=\"ltr\">index.html</code> فيحتفظ باسمه، لذا يجب أن تتحقّق المتصفّحات وCloudFront دائمًا من وجود نسخة أحدث منه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> s3</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> sync</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> dist/</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> s3://lantern-site-prod/</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --delete</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --exclude</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"index.html\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --cache-control</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"public,max-age=31536000,immutable\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> s3</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> cp</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> dist/index.html</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> s3://lantern-site-prod/index.html</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --cache-control</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"no-cache\"</span></span></code></pre></div>\n<p>يزيل <code dir=\"ltr\">--delete</code> من الـ bucket الملفات التي لم تعد موجودة في <code dir=\"ltr\">dist/</code>، كي لا تتراكم الحزم القديمة.</p>\n<h3 id=\"sec-5\" tabindex=\"-1\">3. احصل على شهادة في us-east-1</h3>\n<p>لتفعيل HTTPS على <code dir=\"ltr\">lantern.example</code>، اطلب شهادة عامة من <strong>AWS Certificate Manager</strong> في <strong><code dir=\"ltr\">us-east-1</code></strong>. لا يستخدم CloudFront إلا الشهادات من تلك الـ Region، أيًّا كانت الـ Region التي فيها الـ bucket الخاص بك. تحقّق منها بسجل DNS الذي يعطيك إياه ACM.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> acm</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> request-certificate</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --region</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> us-east-1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --domain-name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> lantern.example</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --validation-method</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> DNS</span></span></code></pre></div>\n<h3 id=\"sec-6\" tabindex=\"-1\">4. أنشئ الـ distribution</h3>\n<p>في console الـ CloudFront، أنشئ distribution بالإعدادات التالية:</p>\n<ul>\n<li><strong>Origin</strong>: نقطة النهاية العادية للـ bucket، مع <strong>origin access control</strong> (توقيع الطلبات).</li>\n<li><strong>Default root object</strong>: <code dir=\"ltr\">index.html</code>.</li>\n<li><strong>Viewer protocol policy</strong>: إعادة توجيه HTTP إلى HTTPS.</li>\n<li><strong>Alternate domain name</strong> هو <code dir=\"ltr\">lantern.example</code>، مع شهادة us-east-1.</li>\n<li><strong>Cache policy</strong> هي <code dir=\"ltr\">CachingOptimized</code>، وسياسة ترويسات الاستجابة المُدارة <strong>SecurityHeadersPolicy</strong>، التي تضيف HSTS والترويسات المرتبطة به.</li>\n</ul>\n<p>إذا كانت الواجهة الأمامية تستخدم مسارات من جهة العميل مثل <code dir=\"ltr\">/events/street-food</code>، فهذه المسارات لا توجد كملفات، فيجيب S3 عبر OAC بالرمز 403. أضف استجابة خطأ مخصّصة تقدّم <code dir=\"ltr\">/index.html</code> بالرمز 200 لأخطاء 403، أو CloudFront Function صغيرة تعيد كتابة تلك المسارات، كي تفتح الروابط العميقة المتداولة على وسائل التواصل الصفحة الصحيحة بدلًا من صفحة خطأ.</p>\n<h3 id=\"sec-7\" tabindex=\"-1\">5. اسمح لذلك الـ distribution وحده بقراءة الـ bucket</h3>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">site-bucket-policy.json</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Version\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"2012-10-17\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"Statement\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Sid\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"AllowCloudFrontServicePrincipalReadOnly\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Effect\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Allow\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Principal\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: { </span><span style=\"color:#116329;--shiki-dark:#7EE787\">\"Service\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"cloudfront.amazonaws.com\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Action\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"s3:GetObject\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Resource\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"arn:aws:s3:::lantern-site-prod/*\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">      \"Condition\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">        \"StringEquals\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">          \"AWS:SourceArn\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"arn:aws:cloudfront::111122223333:distribution/E2QWRUHEXAMPLE\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>الشرط هو ما يجعل هذا آمنًا: من دونه، يستطيع أي distribution في CloudFront في أي حساب قراءة الـ bucket.</p>\n<h3 id=\"sec-8\" tabindex=\"-1\">6. وجّه DNS واختبر</h3>\n<p>أنشئ سجلات alias من نوع <code dir=\"ltr\">A</code> و<code dir=\"ltr\">AAAA</code> لـ <code dir=\"ltr\">lantern.example</code> تشير إلى الـ distribution (في Amazon Route 53، أو كسجل CNAME لدى مزوّد DNS آخر إن كان نطاقًا فرعيًا). ثم تحقّق من أن <code dir=\"ltr\">https://lantern.example</code> يُحمَّل، وأن رابط الـ bucket نفسه يعيد <strong>403 Access Denied</strong>: هذا الـ 403 يثبت أن الـ bucket خاص.</p>\n<h3 id=\"sec-9\" tabindex=\"-1\">7. في كل عملية نشر</h3>\n<p>كرّر الخطوة 2، ثم أبطل الملف الوحيد الذي يحتفظ باسمه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> cloudfront</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create-invalidation</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --distribution-id</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> E2QWRUHEXAMPLE</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --paths</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"/index.html\"</span></span></code></pre></div>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>إبطال كل شيء في كل عملية نشر</span></p>\n<p>يعمل <code dir=\"ltr\">--paths &quot;/*&quot;</code>، لكنه يرمي الـ cache كله، فتفوت طلبات الزوّار التالين كلها الـ cache وتنهال على S3 دفعة واحدة، وهو بالضبط التدافع الذي وُجدت شبكة الـ CDN لمنعه. مع أسماء ملفات تحتوي على hash و<code dir=\"ltr\">index.html</code> بترويسة no-cache، يكفي مسار واحد.</p>\n</aside>\n<h2 id=\"sec-10\" tabindex=\"-1\">التكلفة والأمان في لمحة</h2>\n<p>تدفع لـ S3 مقابل التخزين والطلبات، ومع وجود CloudFront في الأمام، لا يرى S3 إلا حالات الـ miss. النقل من S3 إلى CloudFront لا يُحتسب؛ ويحتسب CloudFront الطلبات والبيانات المقدَّمة للزوّار بنظام الدفع حسب الاستخدام، أو يمكنك اختيار إحدى خططه ذات السعر الثابت التي تجمع الـ CDN مع AWS WAF وDNS وأرصدة S3 بسعر شهري ثابت دون رسوم تجاوز. وفي الحالتين، تهبط ذروة المهرجان الآن على مواقع حافة بُنيت لتحمّلها.</p>\n<p>من ناحية الأمان: bucket خاص واحد، وسياسة bucket واحدة بشرط <code dir=\"ltr\">SourceArn</code>، وHTTPS فقط، وترويسات الأمان مفعّلة. في الدرس التالي تضيف الجزء الديناميكي: الـ API الخاص بتأكيد الحضور.</p>\n","toc":[{"id":"sec-1","text":"الصورة العامة","level":2},{"id":"sec-2","text":"خطوة بخطوة","level":2},{"id":"sec-3","text":"1. ابنِ التطبيق وأنشئ bucket خاصًّا","level":3},{"id":"sec-4","text":"2. ارفع الملفات بترويسات الـ cache الصحيحة","level":3},{"id":"sec-5","text":"3. احصل على شهادة في us-east-1","level":3},{"id":"sec-6","text":"4. أنشئ الـ distribution","level":3},{"id":"sec-7","text":"5. اسمح لذلك الـ distribution وحده بقراءة الـ bucket","level":3},{"id":"sec-8","text":"6. وجّه DNS واختبر","level":3},{"id":"sec-9","text":"7. في كل عملية نشر","level":3},{"id":"sec-10","text":"التكلفة والأمان في لمحة","level":2}],"takeaways":["يبقى الـ bucket خاصًّا؛ يقرؤه CloudFront عبر origin access control، وتسمح سياسة الـ bucket لذلك الـ distribution وحده.","يعمل origin access control مع نقطة النهاية العادية للـ bucket، لا مع نقطة النهاية الخاصة باستضافة المواقع الثابتة في S3.","شهادة النطاق المخصّص في CloudFront يجب أن تكون في AWS Certificate Manager في us-east-1، مطلوبةً هناك أو مستوردةً إليها.","امنح ملفات الأصول ذات الأسماء المحتوية على hash عمر cache طويلًا، وامنح index.html عمرًا قصيرًا، كي تظهر عمليات النشر دون إبطال جماعي.","التقديم من مواقع الحافة يمتصّ ذروات الزيارات ويقلّل نقل البيانات من الـ origin."],"further":[{"title":"Restrict access to an Amazon S3 origin","url":"https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html"},{"title":"Get started with a CloudFront standard distribution","url":"https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.SimpleDistribution.html"},{"title":"Requirements for using SSL/TLS certificates with CloudFront","url":"https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-and-https-requirements.html"},{"title":"Invalidate files to remove content","url":"https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html"}],"quiz":[{"q":"<p>لماذا يُبقي Lantern الـ bucket الخاص بالموقع خاصًّا بدلًا من تفعيل استضافة المواقع الثابتة في S3 وجعله عامًّا؟</p>","options":[{"text":"الـ buckets العامة لا تستطيع تقديم ملفات HTML.","why":"الـ bucket العام مع استضافة المواقع يقدّم HTML دون مشكلة. الأسباب هي الأمان والتحكّم، لا القدرة."},{"text":"يرفض CloudFront تخزين المحتوى من الـ buckets العامة مؤقتًا.","why":"يستطيع CloudFront استخدام نقطة نهاية موقع عامة كـ origin مخصّص. يتجنّبها Lantern لأنها تترك الـ bucket مفتوحًا للوصول المباشر."},{"text":"استضافة المواقع الثابتة في S3 لم تعد متاحة.","why":"نقاط نهاية المواقع ما زالت موجودة. لكنها لا تقدّم إلا HTTP، وتتطلّب وصول قراءة عامًّا."},{"text":"مع origin access control لا يستطيع الزوّار الوصول إلى الملفات إلا عبر CloudFront، وعبر HTTPS، ولا يحتاج الـ bucket أبدًا إلى وصول عام.","why":"صحيح. سياسة bucket واحدة تمنح القراءة لـ distribution واحد، ويبقى Block Public Access مفعّلًا."}],"answer":3},{"q":"<p>طلبت شهادة لـ <code dir=\"ltr\">lantern.example</code> من AWS Certificate Manager في <code dir=\"ltr\">eu-west-1</code>، لكن CloudFront لا يعرضها. لماذا؟</p>","options":[{"text":"لا يستخدم CloudFront إلا الشهادات الصادرة في Region شرق الولايات المتحدة (شمال فيرجينيا)، <code dir=\"ltr\">us-east-1</code>.","why":"صحيح. CloudFront خدمة عامّة يجب أن تُطلب شهاداتها أو تُستورد في us-east-1."},{"text":"تحتاج الشهادة إلى بضع ساعات لتُنسخ إلى كل الـ Regions.","why":"شهادات ACM لا تُنسخ أبدًا بين الـ Regions. الشهادة توجد فقط في الـ Region التي أنشأتها فيها."},{"text":"يتطلّب CloudFront شهادات من جهة إصدار خارجية.","why":"شهادات ACM العامة تعمل مع CloudFront، دون أي رسوم على الشهادة نفسها."}],"answer":0},{"q":"<p>بعد عملية نشر، ما زال الزوّار يرون النسخة القديمة من الصفحة الرئيسية لساعات، بينما تُحمَّل ملفات JavaScript الجديدة بشكل سليم. ما السبب الأرجح؟</p>","options":[{"text":"لم يرفع S3 sync ملف <code dir=\"ltr\">index.html</code> الجديد.","why":"هذا ممكن، لكن ملفات JS الجديدة ذات الـ hash وصلت، فعملية الرفع نفسها جرت. العَرَض يشير إلى الـ cache."},{"text":"يقدّم CloudFront نسخة مخزّنة مؤقتًا من <code dir=\"ltr\">index.html</code> لأنه رُفع بعمر cache طويل.","why":"صحيح. امنح <code dir=\"ltr\">index.html</code> ترويسة قصيرة أو no-cache، وأبطل <code dir=\"ltr\">/index.html</code> بعد كل عملية نشر."},{"text":"المتصفّحات لا تخزّن HTML مؤقتًا أبدًا، فلا بد أنها مشكلة في DNS.","why":"المتصفّحات وشبكات CDN كلاهما يخزّن HTML مؤقتًا وفق ترويسة Cache-Control. ولا يحدّد DNS أيّ نسخة من الملف تُقدَّم."}],"answer":1}],"exercise":{"kind":"order","items":["build","bucket","upload","cert","distribution","policy","dns","verify"],"title":"انشر الموقع الثابت","prompt":"<p>رتّب خطوات أول عملية نشر للواجهة الأمامية لـ Lantern في الإنتاج ترتيبًا يعمل. يبقى الـ bucket خاصًّا، ولا يُقدَّم الموقع إلا عبر CloudFront على <code dir=\"ltr\">https://lantern.example</code>. أنهِ أعمال البناء وS3 أولًا، ثم انتقل إلى الشهادة وCloudFront وDNS.</p>\n","hints":["لا يمكنك رفع ملفات لم تبنِها، ولا الرفع إلى bucket غير موجود.","يحتاج الـ distribution إلى الشهادة عندما تربط به النطاق المخصّص، وتحتاج سياسة الـ bucket إلى ARN الـ distribution.","يأتي DNS أخيرًا قبل الاختبار، كي لا يصل الزوّار أبدًا إلى distribution نصف مُعَدّ."],"explanation":"<p>ابنِ أولًا كي يوجد ما ترفعه، ثم أنشئ الـ bucket الخاص وزامن الملفات مع ترويسات الـ cache الخاصة بها. يجب أن توجد الشهادة (وأن يُتحقَّق منها) في us-east-1 قبل أن تستطيع ربط <code dir=\"ltr\">lantern.example</code> بالـ distribution. ويجب أن يوجد الـ distribution قبل سياسة الـ bucket، لأن الشرط <code dir=\"ltr\">AWS:SourceArn</code> في السياسة يسمّي ARN الـ distribution. ولا توجّه DNS إليه إلا حين يستطيع CloudFront قراءة الـ bucket، والفحص الأخير يثبت النصفين: الموقع يُحمَّل عبر CloudFront، والـ bucket يرفض الطلبات المباشرة بالرمز 403.</p>\n","checkLabels":{},"itemText":{"build":"شغّل <code dir=\"ltr\">npm run build</code> لإنتاج المجلّد <code dir=\"ltr\">dist/</code>.","bucket":"أنشئ الـ bucket الخاص <code dir=\"ltr\">lantern-site-prod</code> في الـ Region الخاصة بـ Lantern، مع إبقاء Block Public Access مفعّلًا.","upload":"ارفع <code dir=\"ltr\">dist/</code> باستخدام <code dir=\"ltr\">aws s3 sync</code>، مع ترويسات cache طويلة للأصول ذات الـ hash و<code dir=\"ltr\">no-cache</code> لـ <code dir=\"ltr\">index.html</code>.","cert":"اطلب شهادة ACM لـ <code dir=\"ltr\">lantern.example</code> في <code dir=\"ltr\">us-east-1</code> وتحقّق منها عبر DNS.","distribution":"أنشئ الـ distribution في CloudFront مع origin access control، و<code dir=\"ltr\">index.html</code> ككائن جذر افتراضي، والشهادة مرفقة.","policy":"أضف سياسة الـ bucket التي تسمح بـ <code dir=\"ltr\">s3:GetObject</code> لـ <code dir=\"ltr\">cloudfront.amazonaws.com</code> مع شرط <code dir=\"ltr\">AWS:SourceArn</code> لهذا الـ distribution.","dns":"أنشئ سجلات alias من نوع A وAAAA لـ <code dir=\"ltr\">lantern.example</code> تشير إلى الـ distribution.","verify":"تحقّق من أن <code dir=\"ltr\">https://lantern.example</code> يُحمَّل وأن الرابط المباشر للـ bucket يعيد 403."}},"words":645,"readMinutes":4}