podهایی که یک OpenVPN sidecar را اجرا می‌کنند، در تفکیک نام با الگویی قابل‌تشخیص شکست می‌خورند: نام‌های خوشه معلق می‌شوند، نام‌های راه‌دور تا زمانی که pod دوباره راه‌اندازی شود پاسخ می‌دهند، و فایل resolver ویرایش‌شده‌ی شما به حالت قبل برمی‌گردد. هیچ‌کدام از این‌ها خطای تونل نیست. یک pod یک فایل resolver و دو دنیای نام دارد، و فایل می‌تواند سرورها را به ترتیب فهرست کند اما نمی‌تواند ناحیه‌ها را بیان کند. راه‌حل پایدار یک resolver است که درون pod اجرا می‌شود و مالک تقسیم است، بنابراین فایل ساده می‌ماند و هیچ سیاستی دلیلی برای بازنویسی آن ندارد.

چرا ویرایشی که کردید مدام ناپدید می‌شود

فایل متعلق به kubelet است، نه شما. زیر dnsPolicy: ClusterFirst، kubelet آن را برای اشاره به DNS خوشه تولید می‌کند و هر بار که یک container شروع می‌شود آن را دوباره تولید می‌کند. هر چیزی که از طریق kubectl exec تغییر دهید در لایه‌ی نوشتنی container زندگی می‌کند، پس راه‌اندازی مجدد، eviction یا rollout آن را دور می‌ریزد. spec پاد همچنان همان سیاست را دارد، به همین دلیل همان فایل برمی‌گردد.

تنظیم dnsPolicy: None بازتولید را متوقف می‌کند و تفکیک نام خوشه را هم حذف می‌کند. فایلی به دست می‌آورید که کنترل می‌کنید و نام‌هایی را از دست می‌دهید که لازم داشتید. این مبادله تمام مشکل است: resolv.conf مفهومی از ناحیه‌ها ندارد، فقط سرورهایی برای امتحان به ترتیب، پس نمی‌تواند cluster.local را به یک آدرس و یک ناحیه‌ی خصوصی را به آدرس دیگر بفرستد.

یک resolver در pod، روی loopback اجرا کنید

pod را به یک nameserver واحد روی loopback اشاره دهید، سپس بگذارید یک container CoreDNS که فضای نام شبکه‌ی pod را به اشتراک می‌گذارد روی پورت 53 پاسخ دهد. سیاست را روی None تنظیم کنید، آن یک آدرس را به pod بدهید و container DNS را کنار VPN و برنامه اضافه کنید:

spec:
  dnsPolicy: None
  dnsConfig:
    nameservers:
      - 127.0.0.1
    options:
      - name: ndots
        value: "5"
  containers:
    - name: dns
      image: coredns/coredns
      args: ["-conf", "/etc/coredns/Corefile"]
      volumeMounts:
        - {name: corefile, mountPath: /etc/coredns, readOnly: true}
    - name: vpn
      image: your-openvpn-client-image
    - name: app
      image: your-app-image
  volumes:
    - name: corefile
      configMap:
        name: split-dns-corefile

هر سه container یک فضای نام شبکه را به اشتراک می‌گذارند، بنابراین 127.0.0.1:53 از container برنامه بدون service، بدون port-forward و بدون دور زدن از طریق شبکه‌ی خوشه به CoreDNS می‌رسد. مانت کردن Corefile از یک ConfigMap آن را به ورودی نسخه‌دار تبدیل می‌کند، نه فایلی که درجا ویرایش شود. resolv.conf تولیدشده آنگاه یک nameserver و چیز دیگری ندارد، که وضعیتی است که می‌توانید درباره‌اش استدلال کنید.

دو جزئیات اینجا گاز می‌گیرند. ایمیج رسمی پورت 53 را به‌عنوان root می‌بندد، و برخی سیاست‌های خوشه شما را مجبور می‌کنند این را صریح بنویسید. هر container در pod اکنون به بالابودن همان یک container وابسته است، پس یک readiness probe روی یک نام بی‌خطر اضافه کنید و بگذارید برنامه خطاهای شروع را تحمل کند.

به هر ناحیه‌ی خصوصی upstream خودش را بدهید

یک Corefile فهرستی از ناحیه‌هاست، هر کدام سروری را نام می‌برد که می‌تواند به آن پاسخ دهد. یک بلوک ناحیه‌ی تونل را پوشش می‌دهد، و بلوک catch-all آخر می‌آید و هر چیزی را که هیچ بلوک صریحی ادعا نکرده است می‌گیرد. بلوک catch-all باید به یک upstream واقعی اشاره کند، هرگز به pod برنگردد: .:53 { forward . 10.100.0.10 } از resolver خوشه دوباره استفاده می‌کند چون آن resolver از قبل نام‌های عمومی را به resolverهای خود node هدایت می‌کند. هیچ چیز دیگری در pod لازم نیست بداند یک نام به کدام شبکه تعلق دارد.

.:53 {
  errors
  cache 30
  forward . 10.100.0.10
}

svc.acme.internal:53 {
  errors
  cache 30
  forward . 192.168.240.1
}

آدرس خوشه را از سرویس kube-dns در kube-system بخوانید نه اینکه یکی از مقاله کپی کنید، چون CIDRهای سرویس فرق می‌کنند. آدرس تونل از مقدار dhcp-option DNS می‌آید که سرور OpenVPN می‌فرستد، یا از سرور معتبر برای آن ناحیه. اگر resolver خوشه‌ی شما به نام‌های عمومی پاسخ نمی‌دهد، آدرس resolver node را در بلوک catch-all بگذارید، که روی node خوانده می‌شود نه درون pod.

بلوک catch-all را به /etc/resolv.conf اشاره ندهید، که تک‌خط وسوسه‌انگیز و تله در همین پیکربندی است. زیر dnsPolicy: None فایل خود pod تنها nameserver 127.0.0.1 را دارد، پس آن هدف خود CoreDNS است و هر پرس‌وجوی بی‌تطابق تا وقتی مهلت تمام شود بازگشتی می‌ماند. بلوکی به نام svc.acme.internal با آن نام و هر نام زیر آن مطابق است، که db.svc.acme.internal را پوشش می‌دهد و svc.acme.internal.example.net را کنار می‌گذارد. نقطه‌ی پایانی ظاهری است، چون نام ناحیه‌ها به شکل کاملاً واجد شرایط نرمال می‌شوند؛ مرز پسوند است که تعیین می‌کند یک پرس‌وجو کجا فرود می‌آید.

تصمیم بگیرید آیا هدایت باید در resolver خوشه باشد

اگر هر workload در namespace به ناحیه‌ی تونل نیاز دارد، یک بلوک هدایت در CoreDNS ConfigMap زیر kube-system ارزان‌تر از یک sidecar برای هر pod است. این تغییر در مقیاس خوشه است: به حقوق مدیر و راه‌اندازی مجدد یک deployment نیاز دارد که همه به آن وابسته‌اند. آن را به همان یک ناحیه محدود کنید، هرگز به ریشه. آدرس تونل یک workload واحد آنگاه resolver هر pod برای آن ناحیه می‌شود و روزی که تونل قطع شود کل خوشه نام را از دست می‌دهد.

sidecar شعاع انفجار را درون یک pod نگه می‌دارد، که پیش‌فرض بهتری است وقتی فقط چند سرویس به شبکه‌ی راه دور نیاز دارند. هر کدام را که انتخاب کنید، بنویسید کدام resolver مالک ناحیه است، چون نفر بعدی که یک timeout را عیب‌یابی می‌کند خواهد پرسید.

نام‌هایی که تفکیک می‌شوند در حالی که اتصال‌ها هنوز شکست می‌خورند

تفکیک نام یکی از دو چیزی است که تونل باید حمل کند؛ دیگری مسیر بازگشت است، و شبکه‌ی pod آن را خوب پنهان می‌کند. اگر خود سرور OpenVPN به‌صورت pod اجرا شود، قواعدی که درون آن container اضافه می‌کنید ترافیکی را که از node خارج می‌شود اداره نمی‌کنند. این شکست باریک است: فقط podهای روی node که خود سرور را هم اجرا می‌کرد پاسخ می‌گرفتند و بقیه مهلت تمام می‌کردند.

اجرای سرور با hostNetwork: true آن را به فضای نام node برمی‌گرداند، جایی که قاعده‌ی FORWARD و ترجمه‌ی مبدأ اثر می‌کنند. pool کلاینت را در خروج ترجمه کنید، وگرنه هیچ node راه دوری مسیری به یک آدرس تونل مانند 10.8.0.6 ندارد. هر دو جهت را قبل از دست زدن دوباره به Corefile بررسی کنید. تونلی که پرس‌وجو را حمل می‌کند و پاسخ را نه، دقیقاً همان timeoutی را تولید می‌کند که resolverی که هرگز پرس‌وجو را دریافت نکرد.

از یک pod جدید بررسی کنید، نه از نشستی که تعمیر کردید

هر دو ناحیه را صریح بپرسید، سپس پرس‌وجو را پس از جایگزینی pod تکرار کنید. پیکربندی‌ای که فقط در یک container در حال اجرا وجود دارد پیکربندی نیست:

kubectl exec -it deploy/app -- dig @127.0.0.1 kubernetes.default.svc.cluster.local +short
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +short
kubectl delete pod -l app=your-app
kubectl wait --for=condition=Ready pod -l app=your-app --timeout=90s
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +short

اگر جفت اول پاسخ دهد و جفت بعد از جایگزینی نه، Corefile درست است و spec پاد نه. اگر هر دو پاسخ دهند و برنامه هنوز نتواند متصل شود، عیب‌یابی DNS را متوقف کنید و مسیر TCP را با nc -vz db.svc.acme.internal 5432 از درون container آزمایش کنید. خروجی dig را در تیکت نگه دارید، چون همان چیزی است که مشکل نام‌گذاری را از مشکل مسیریابی جدا می‌کند.

مقالات مرتبط