Bu gönderi, Rust'ta hızlı bir şekilde yüksek performanslı ve güvenli uygulamalar yazmakla ilgilenen herkes içindir. Okuyucuya, bir HTTP Tüneli tasarlama ve uygulama ve temel, dilden bağımsız, sağlam, ölçeklenebilir, gözlemlenebilir ve geliştirilebilir ağ uygulamaları oluşturma ilkeleri boyunca yol gösterir.
Yaklaşık bir yıl önce Rust öğrenmeye başladım. İlk iki hafta oldukça sancılı geçti. Hiçbir şey derlenmedi, temel işlemleri nasıl yapacağımı bilmiyordum, basit bir programı çalıştıramadım. Ama adım adım derleyicinin ne istediğini anlamaya başladım. Dahası, doğru düşünceyi ve doğru davranışı zorladığını fark ettim.
Evet, bazen görünüşte fazlalık yapılar yazmanız gerekir. Ancak, doğru bir programı derlememek, yanlış bir programı derlemekten daha iyidir. Bu, hata yapmayı daha zor hale getirir.
Her neyse, kısa süre sonra az çok üretken oldum ve sonunda istediğimi yapabildim. Çoğu zaman.
Son zamanlarda meraktan biraz daha karmaşık bir mücadeleye girişmeye karar verdim: Rust'ta bir HTTP Tüneli uygulamak . Yapması şaşırtıcı derecede kolay çıktı ve yaklaşık bir gün sürdü, bu oldukça etkileyici. Temel olarak, tokio , clap , serde ve diğer birçok kullanışlı sandığı birbirine diktim. Tamam, bu kadar giriş yeter. Bu heyecan verici meydan okuma sırasında edindiğim bilgileri paylaşmama ve uygulamayı neden bu şekilde düzenlediğimi açıklamama izin verin. Umarım beğenirsiniz.
Basitçe ifade etmek gerekirse, tarayıcınızla kurabileceğiniz hafif bir VPN'dir, böylece İnternet sağlayıcınız etkinliğinizi engelleyemez veya izleyemez ve web sunucuları IP adresinizi görmez.
İsterseniz, tarayıcınızda yerel olarak, örneğin Firefox ile test edebilirsiniz (aksi takdirde şimdilik bu bölümü atlayın).
$ cargo install http-tunnel
$ http-tunnel --bind 0.0.0.0:8080 http
ve sonra belirtin
HTTP Proxyve ayrıca kontrol edin
HTTPS:
Birkaç web sayfasını ziyaret edebilir ve
./logs/application.logdosyayı kontrol edebilirsiniz - tüm trafiğiniz tünelden geçiyordu. Örneğin:
Tamam, süreci baştan inceleyelim.
Her uygulama tasarımla başlar, bu da aşağıdakileri tanımlamamız gerektiği anlamına gelir:
Burada özetlenen spesifikasyona uymamız gerekiyor: https://en.wikipedia.org/wiki/HTTP_tunnel :
Hedefi bir
HTTP CONNECTtaleple görüşün. Örneğin, müşteri www.wikipedia.org'a bir tünel oluşturmak isterse , istek şöyle görünecektir:
CONNECT www.wikipedia.org:443 HTTP/1.1 ...
HTTP/1.1 200 OK
HTTP Tüneli hem HTTP hem de HTTPS için çalışmalıdır.
Ayrıca, erişim / engelleme hedeflerini de yönetebilmeliyiz (örneğin, izleyicilerin engellenmesi).
Hizmet, kullanıcıları tanımlayan herhangi bir bilgiyi kaydetmemelidir.
Yüksek verim ve düşük gecikme süresine sahip olmalıdır (kullanıcılar tarafından farkedilmemeli ve çalıştırılması görece ucuz olmalıdır).
İdeal olarak, trafik artışlarına karşı dirençli olmasını, gürültülü komşu yalıtımı sağlamasını ve temel DDoS saldırılarına direnmesini istiyoruz.
Hata mesajları geliştirici dostu olmalıdır. Sistemin sorunu gidermek ve onu büyük ölçekte üretim aşamasında ayarlamak için gözlemlenebilir olmasını istiyoruz.
Bileşenleri tasarlarken, önce uygulamayı bir dizi sorumluluklara ayırmamız gerekir. İlk olarak, akış diyagramımızın nasıl göründüğüne bakalım:
Bunu uygulamak için aşağıdaki ana bileşenleri tanıtabiliriz:
Uygulamayı nasıl düzenleyeceğimizi kabaca bildiğimizde, hangi bağımlılıkları kullanmamız gerektiğine karar vermenin zamanı geldi. Rust için bildiğim en iyi I / O kütüphanesi tokio . In
tokioailesi dahil olmak üzere birçok kütüphaneler vardır
tokio-tlsşeyler çok daha basit hale getirir. Dolayısıyla, TCP alıcı kodu şöyle görünür:
Ve sonra tüm alıcı döngüsü + eşzamansız bağlantı işleyicileri başlatan:
Burada neler olduğunu inceleyelim . Bir bağlantıyı kabul ediyoruz. İşlem başarılı olduysa,
tokio::spawnbu bağlantıyı idare edecek yeni bir görev oluşturmak için kullanın . Bellek / iş parçacığı güvenliği yönetimi perde arkasında gerçekleşir. Vadeli işlemlerin işlenmesi
async/awaitsözdizimi şekeri ile gizlidir .
Ancak bir soru var.
TcpStreamve
TlsStreamfarklı nesnelerdir, ancak her ikisini de ele almak kesinlikle aynıdır. Aynı kodu tekrar kullanabilir miyiz? Rust'ta soyutlama,
Traitsçok kullanışlı olan aracılığıyla elde edilir :
Akış şunları uygulamalıdır:
AsyncRead /Write- böylece eşzamansız olarak okuyabilir / yazabiliriz
Send- mesaj dizileri arasında gönderebilmek için
Unpin- taşınabilir olmak (aksi takdirde yapamayacağız
async moveve
tokio::spawnbir
asyncgörev oluşturamayacağız )
'static—Uygulama kapanana kadar yaşayabileceğini ve başka herhangi bir nesnenin yok edilmesine bağlı olmadığını belirtmek için.
Bir saniye duralım ve daha yüksek bir seviyede düşünelim. Ya HTTP Tüneli'nden soyutlayabilirsek ve sadece genel bir tünel uygulamamız gerekirse?
Bu
tunnel_streamyöntemin zaten herhangi bir L4
Client<->Tunnel bağlantısıyla çalıştığını gördük .
Burada iki soyutlama belirtiyoruz:
TunnelTargetsadece bir
Addr- her neyse ona sahip bir şeydir .
TargetConnector- buna bağlanabilir
Addrve eşzamansız G / Ç'yi destekleyen bir akış döndürmesi gerekir.
Tünel görüşmesi yapıldıktan sonra verileri aktaran tek bir ana bileşenimiz kaldı.
tokiobir akışı ikiye bölmek için bir yöntem sağlar:
ReadHalfve
WriteHalf. Biz de müşteri ve hedef bağlantılarını ayırabilir ve röle iki yönde onları:
relay_data(…)Tanımın yukarıda bahsedilen soyutlamaları uygulamaktan başka bir şey gerektirmediği durumlarda . Yani, bir akışın herhangi iki yarısını birbirine bağlayabilir:
Ve son olarak, basit bir HTTP Tüneli yerine, herhangi bir taşıma ve proxy protokolü üzerinden herhangi bir tür tünel veya bir tünel zinciri (ör. Soğan yönlendirmesi için) inşa etmek için kullanılabilecek bir motorumuz var:
Uygulama temel durumlarda neredeyse önemsiz, ama biz sap başarısızlıklarına bizim app istiyorum, ve bu bir sonraki bölümün odak bu.
Mühendislerin arızalarla uğraştığı süre, bir sistemin ölçeği ile orantılıdır. Mutlu durum kodu yazmak kolaydır. Yine de, ilk hatada düzeltilemez bir duruma girerse, kullanımı acı verir. Bunun yanı sıra, uygulamanız diğer mühendisler tarafından kullanılacak ve şifreli / yanıltıcı hata mesajlarından daha rahatsız edici çok az şey var. Kodunuz büyük bir hizmetin parçası olarak çalışıyorsa, bazı kişilerin onu izlemesi ve desteklemesi gerekir (örneğin, SRE'ler veya DevOps) ve hizmetinizle ilgilenmek onlar için bir zevk olmalıdır.
Bir HTTP Tüneli ne tür hatalarla karşılaşabilir?
Uygulamanızın istemciye döndürdüğü tüm hata kodlarını numaralandırmak iyi bir fikirdir. Bu nedenle, işlem yeniden denenebilirse (veya yapılmaması gerekiyorsa), bir entegrasyon hatası veya yalnızca ağ gürültüsüyse, isteğin neden başarısız olduğu açıktır.
Gecikmelerle başa çıkmak, bir ağ uygulaması için çok önemlidir. İşlemlerinizde zaman aşımı yoksa, tüm iş parçacıklarınızın Godot'u Bekliyor olması veya uygulamanızın mevcut tüm kaynakları tüketmesi ve kullanılamaz hale gelmesi an meselesidir . Burada zaman aşımı tanımını şunlara devrediyoruz
RelayPolicy:
Röle politikası şu şekilde yapılandırılabilir:
relay_policy: idle_timeout: 10s min_rate_bpm: 1000 max_rate_bps: 10000 max_lifetime: 100s max_total_payload: 100mb
Her bir arıza modunun test edilmesi gerektiğini söylemeye gerek yok . Bunu genel olarak Rust'ta ve
tokio-testözellikle de aşağıdakilerle yapmak kolaydır :
Aynısı G / Ç hataları için de geçerlidir:
Sadece geliştiricilerinin beklediği şekillerde başarısız olan bir uygulama görmedim. Böyle bir uygulama yok demiyorum. Yine de, muhtemelen uygulamanız beklemediğiniz bir şeyle karşılaşacaktır: veri yarışları, belirli trafik modelleri, trafik patlamalarıyla başa çıkma, eski istemciler.
Ancak muhtemelen en yaygın arıza türlerinden biri, büyük projelerde kaçınılmaz olan, kötü kod veya konfigürasyon zorlama gibi insan hatalarıdır. Her neyse, önceden tahmin etmediğimiz bir şeyle başa çıkabilmeliyiz. Bu nedenle, hataları tespit etmemize ve sorun gidermemize izin verecek yeterli bilgi yayarız.
Bu nedenle, her hatayı ve önemli olayları anlamlı bilgiler ve ilgili bağlam ve istatistiklerle birlikte kaydetsek iyi olur.
tunnel_ctx: TunnelCtxMetrik kayıtları günlük mesajlarıyla ilişkilendirmek için kullanılabilecek alana lütfen dikkat edin :
error!( "{} failed to write {} bytes. Err = {:?}, CTX={}", self.name, n, e, self.tunnel_ctx );
Sonuncu ama bir o kadar önemli. Tünelimizi farklı parametrelerle farklı modlarda çalıştırabilmek istiyoruz. İşte burada
serdeve
clapkullanışlı olun.
Bence, komut satırı parametreleriyle uğraşmayı keyifli
claphale getiriyor . Olağanüstü ifade gücüne sahip ve bakımı kolay.
Yapılandırma dosyaları aşağıdakilerle kolayca işlenebilir
serde-yaml:
target_connection: dns_cache_ttl: 60s allowed_targets: "(?i)(wikipedia|rust-lang)\\.org:443$" connect_timeout: 10s relay_policy: idle_timeout: 10s min_rate_bpm: 1000 max_rate_bps: 10000
Okunabilir ve sürdürülebilir kılmak için herhangi bir ek yoruma ihtiyaç duymaz ve bu güzeldir.
Bu hızlı genel bakıştan görebileceğiniz gibi, Rust ekosistemi zaten birçok yapı taşı sağlıyor, böylece nasıl yapmaktan ziyade yapmanız gerekenlere odaklanabilirsiniz . Etkileyici performansa sahip herhangi bir bellek / kaynak yönetimi veya açık iş parçacığı güvenliği (genellikle eşzamanlılık pahasına gelir) görmediniz . Soyutlama mekanizmaları harika, bu nedenle kodunuz yüksek oranda yeniden kullanılabilir. Bu görev çok eğlenceliydi, bu yüzden bir sonraki mücadeleye geçmeye çalışacağım.
2017'de WebAssembly, tüm büyük tarayıcılar için ilan edildi ve o zamandan beri, bir web üzerinde yerele yakın hızda kod çalıştırabilmesi nedeniyle çok fazla gürültü üretiyor ve bu da geliştiricilere yoğun bilgi işlem uygulamaları yazma olasılıkları sunuyor. Ancak WebAssembly gerçekten çok mu harika? WebAssembly'a Genel Bakış WebAssembly (Wasm), yüksek performanslı hesaplamalar sağlamayı amaçlar.
Katıştırılmış yazılım geliştiricilerin projelerinde C'yi değiştirmeye çalıştıklarını görmek yaygındır. C [C'yi seviyorum] mükemmel bir dildir, basit, güçlü, kararlı, birleştirilmiş, hızlı vb.
Kubernetes'in yaygınlaştığını söylemek yetersiz kalır. Aslında, modern dağıtılmış sistemlerin nasıl tasarlandığını ve işletildiğini etkiledi.
Bu sadece kodlama ve teknik becerilerle ilgili değil Üç yıl önce, kurumsal dünyaya işlerin nasıl görüneceğine dair tonlarca varsayım ve beklentiyle ilk kez adım attım. Ne kadar yanıldığımı kanıtlamak üzere olduğumu çok az biliyordum.
Kubernetes Kabul Denetleyicisi nedir? Kubernetes kabul denetleyicileri, kimliği doğrulanmış istekleri Kubernetes API sunucusuna yakalayarak ve herhangi bir nesne verisi etcd veri deposunda saklanmadan önce bunları işleyerek ağ geçidi koruyucusu görevi gören eklentilerdir. Kabul denetleyicileri, bir istek nesnesini değiştirebilir / değiştirebilir (Değişen Kabul Denetleyicileri) veya bir isteğe izin verebilir / izin vermeyebilir (Kabul Denetleyicileri) ve bazen her ikisini de yapabilir.
Sprint günlerinde bir Hikaye, önceki 2 haftadaki başarılarımızı (veya başarısızlıklarımızı) yansıtıyoruz. Yakınlaştırmaya atlayıp düşüncelerimizi paylaşıp derinlemesine düşünmeden önce yaklaşık 20 dakika.