Menjalankan Keycloak dalam mode HA di Kubernetes

Menjalankan Keycloak dalam mode HA di Kubernetes

TL; DR: akan ada penjelasan tentang Keycloak, sistem kontrol akses open source, analisis struktur internal, detail konfigurasi.

Pendahuluan dan Ide Utama

Pada artikel ini, kita akan melihat ide dasar yang perlu diingat saat menerapkan cluster Keycloak di atas Kubernetes.

Jika Anda ingin tahu lebih banyak tentang Keycloak, lihat link di akhir artikel. Agar lebih mendalami latihan, Anda bisa belajar gudang kami dengan modul yang mengimplementasikan gagasan utama artikel ini (panduan peluncuran ada di sana, artikel ini akan memberikan ikhtisar perangkat dan pengaturan, kira-kira Penerjemah).

Keycloak adalah sistem komprehensif yang ditulis dalam Java dan dibangun di atas server aplikasi lalat liar. Singkatnya, ini adalah kerangka kerja otorisasi yang memberikan kemampuan federasi dan SSO (sistem masuk tunggal) kepada pengguna aplikasi.

Kami mengundang Anda untuk membaca resminya situs web ΠΈΠ»ΠΈ Wikipedia untuk pemahaman rinci.

Meluncurkan Keycloak

Keycloak memerlukan dua sumber data persisten untuk dijalankan:

  • Basis data yang digunakan untuk menyimpan data yang sudah ada, seperti informasi pengguna
  • Cache datagrid, yang digunakan untuk menyimpan data dari database, serta untuk menyimpan beberapa metadata yang berumur pendek dan sering berubah, seperti sesi pengguna. Diimplementasikan tak terbatas, yang biasanya jauh lebih cepat daripada database. Namun bagaimanapun juga, data yang disimpan di Infinispan bersifat sementara - dan tidak perlu disimpan di mana pun saat cluster dimulai ulang.

Keycloak bekerja dalam empat mode berbeda:

  • Normal - satu dan hanya satu proses, dikonfigurasi melalui file mandiri.xml
  • Klaster biasa (opsi ketersediaan tinggi) - semua proses harus menggunakan konfigurasi yang sama, yang harus disinkronkan secara manual. Pengaturan disimpan dalam file mandiri-ha.xml, selain itu Anda perlu membuat akses bersama ke database dan penyeimbang beban.
  • Kluster domain β€” memulai sebuah cluster dalam mode normal dengan cepat menjadi tugas rutin dan membosankan seiring pertumbuhan cluster, karena setiap kali konfigurasi berubah, semua perubahan harus dilakukan pada setiap node cluster. Mode operasi domain memecahkan masalah ini dengan menyiapkan beberapa lokasi penyimpanan bersama dan memublikasikan konfigurasinya. Pengaturan ini disimpan dalam file domain.xml
  • Replikasi antar pusat data β€” jika Anda ingin menjalankan Keycloak di cluster yang terdiri dari beberapa pusat data, paling sering di lokasi geografis berbeda. Dalam opsi ini, setiap pusat data akan memiliki cluster server Keycloak sendiri.

Pada artikel ini kami akan mempertimbangkan secara rinci opsi kedua, yaitu klaster reguler, dan kami juga akan sedikit membahas topik replikasi antar pusat data, karena masuk akal untuk menjalankan kedua opsi ini di Kubernetes. Untungnya, di Kubernetes tidak ada masalah dengan sinkronisasi pengaturan beberapa pod (Keycloak node), jadi cluster domain Ini tidak akan terlalu sulit untuk dilakukan.

Harap perhatikan juga kata itu gugus karena artikel selanjutnya hanya akan berlaku untuk sekelompok node Keycloak yang bekerja bersama, tidak perlu mengacu pada cluster Kubernetes.

Kluster Keycloak biasa

Untuk menjalankan Keycloak dalam mode ini, Anda memerlukan:

  • konfigurasikan database bersama eksternal
  • memasang penyeimbang beban
  • memiliki jaringan internal dengan dukungan IP multicast

Kami tidak akan membahas pengaturan database eksternal, karena ini bukan tujuan artikel ini. Mari kita asumsikan ada database yang berfungsi di suatu tempat - dan kita memiliki titik koneksi ke sana. Kami hanya akan menambahkan data ini ke variabel lingkungan.

Untuk lebih memahami cara kerja Keycloak di kluster failover (HA), penting untuk mengetahui seberapa besar ketergantungannya pada kemampuan pengelompokan Wildfly.

Wildfly menggunakan beberapa subsistem, beberapa di antaranya digunakan sebagai penyeimbang beban, beberapa untuk toleransi kesalahan. Penyeimbang beban memastikan ketersediaan aplikasi ketika node cluster kelebihan beban, dan toleransi kesalahan memastikan ketersediaan aplikasi bahkan jika beberapa node cluster gagal. Beberapa subsistem ini:

  • mod_cluster: Bekerja bersama dengan Apache sebagai penyeimbang beban HTTP, bergantung pada multicast TCP untuk menemukan host secara default. Dapat diganti dengan penyeimbang eksternal.

  • infinispan: Cache terdistribusi menggunakan saluran JGroups sebagai lapisan transport. Selain itu, dapat menggunakan protokol HotRod untuk berkomunikasi dengan cluster Infinispan eksternal untuk menyinkronkan konten cache.

  • jgroups: Memberikan dukungan komunikasi grup untuk layanan yang tersedia berdasarkan saluran JGroups. Pipa bernama memungkinkan contoh aplikasi dalam sebuah cluster untuk dihubungkan ke dalam kelompok sehingga komunikasi memiliki properti seperti keandalan, keteraturan, dan kepekaan terhadap kegagalan.

Penyeimbang Beban

Saat memasang penyeimbang sebagai pengontrol ingress di cluster Kubernetes, penting untuk mengingat hal-hal berikut:

Keycloak berasumsi bahwa alamat jarak jauh klien yang terhubung melalui HTTP ke server otentikasi adalah alamat IP sebenarnya dari komputer klien. Pengaturan penyeimbang dan masuknya harus mengatur header HTTP dengan benar X-Forwarded-For ΠΈ X-Forwarded-Proto, dan simpan juga judul aslinya HOST. Versi terbaru ingress-nginx (>0.22.0) menonaktifkan ini secara default

Mengaktifkan bendera proxy-address-forwarding dengan mengatur variabel lingkungan PROXY_ADDRESS_FORWARDING Π² true memberi Keycloak pemahaman bahwa ia bekerja di belakang proxy.

Anda juga perlu mengaktifkannya sesi lengket dalam masuknya. Keycloak menggunakan cache Infinispan terdistribusi untuk menyimpan data yang terkait dengan sesi otentikasi dan sesi pengguna saat ini. Cache beroperasi dengan satu pemilik secara default, dengan kata lain, sesi tertentu disimpan pada beberapa node di cluster, dan node lain harus menanyakannya dari jarak jauh jika mereka memerlukan akses ke sesi tersebut.

Secara khusus, bertentangan dengan dokumentasi, melampirkan sesi dengan nama cookie tidak berhasil bagi kami AUTH_SESSION_ID. Keycloak memiliki loop pengalihan, jadi sebaiknya pilih nama cookie yang berbeda untuk sesi melekat.

Keycloak juga melampirkan nama node yang direspon pertama kali AUTH_SESSION_ID, dan karena setiap node dalam versi yang sangat tersedia menggunakan database yang sama, masing-masing node harus punya pengidentifikasi node yang terpisah dan unik untuk mengelola transaksi. Disarankan untuk dimasukkan ke dalam JAVA_OPTS parameter jboss.node.name ΠΈ jboss.tx.node.id unik untuk setiap node - Anda dapat, misalnya, memasukkan nama pod. Jika Anda memasukkan nama pod, jangan lupa tentang batas 23 karakter untuk variabel jboss, jadi lebih baik menggunakan StatefulSet daripada Deployment.

Penggaruk lainnya - jika pod dihapus atau dimulai ulang, cache-nya akan hilang. Dengan mempertimbangkan hal ini, sebaiknya atur jumlah pemilik cache untuk semua cache menjadi setidaknya dua, sehingga salinan cache akan tetap ada. Solusinya adalah dengan berlari skrip untuk Wildfly saat memulai pod, letakkan di direktori /opt/jboss/startup-scripts dalam wadah:

Isi Naskah

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo * Setting CACHE_OWNERS to "${env.CACHE_OWNERS}" in all cache-containers

/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})

run-batch
stop-embedded-server

lalu atur nilai variabel lingkungan CACHE_OWNERS untuk yang diperlukan.

Jaringan pribadi dengan dukungan IP multicast

Jika Anda menggunakan Weavenet sebagai CNI, multicast akan langsung berfungsi - dan node Keycloak Anda akan saling bertemu segera setelah diluncurkan.

Jika Anda tidak memiliki dukungan ip multicast di cluster Kubernetes, Anda dapat mengonfigurasi JGroup agar bekerja dengan protokol lain untuk menemukan node.

Opsi pertama adalah menggunakan KUBE_DNSyang menggunakan headless service untuk menemukan node Keycloak, Anda cukup memberikan JGroups nama layanan yang akan digunakan untuk menemukan node tersebut.

Pilihan lainnya adalah menggunakan metode ini KUBE_PING, yang berfungsi dengan API untuk mencari node (Anda perlu mengonfigurasi serviceAccount dengan hak list ΠΈ get, lalu konfigurasikan pod agar berfungsi dengan ini serviceAccount).

Cara JGroups menemukan node dikonfigurasi dengan mengatur variabel lingkungan JGROUPS_DISCOVERY_PROTOCOL ΠΈ JGROUPS_DISCOVERY_PROPERTIES. Untuk KUBE_PING Anda perlu memilih pod dengan bertanya namespace ΠΈ labels.

️ Jika Anda menggunakan multicast dan menjalankan dua atau lebih cluster Keycloak dalam satu cluster Kubernetes (misalkan satu di namespace production, kedua - staging) - node dari satu cluster Keycloak dapat bergabung dengan cluster lain. Pastikan untuk menggunakan alamat multicast unik untuk setiap cluster dengan mengatur variabeljboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Replikasi antar pusat data

Menjalankan Keycloak dalam mode HA di Kubernetes

Link

Keycloak menggunakan beberapa cluster cache Infinispan terpisah untuk setiap pusat data tempat cluster Keycloack yang terdiri dari node Keycloak berada. Namun tidak ada perbedaan antara node Keycloak di pusat data yang berbeda.

Node Keycloak menggunakan Java Data Grid eksternal (server Infinispan) untuk komunikasi antar pusat data. Komunikasi bekerja sesuai dengan protokol HotRod Infinispan.

Cache Infinispan harus dikonfigurasi dengan atribut remoteStore, sehingga data dapat disimpan secara jarak jauh (di pusat data lain, kira-kira Penerjemah) cache. Ada cluster infinispan terpisah di antara server JDG, sehingga data disimpan di JDG1 di situs site1 akan direplikasi ke JDG2 di situs site2.

Dan terakhir, server JDG penerima memberi tahu server Keycloak tentang clusternya melalui koneksi klien, yang merupakan fitur protokol HotRod. Node keycloak aktif site2 perbarui cache Infinispan mereka dan sesi pengguna tertentu juga tersedia di node Keycloak site2.

Untuk beberapa cache, dimungkinkan juga untuk tidak membuat cadangan dan menghindari penulisan data sepenuhnya melalui server Infinispan. Untuk melakukan ini, Anda perlu menghapus pengaturan tersebut remote-store cache Infinispan tertentu (dalam file mandiri-ha.xml), setelah itu beberapa hal spesifik replicated-cache juga tidak diperlukan lagi di sisi server Infinispan.

Menyiapkan cache

Ada dua jenis cache di Keycloak:

  • Lokal. Letaknya di sebelah database dan berfungsi untuk mengurangi beban pada database, serta mengurangi latensi respons. Jenis cache ini menyimpan ranah, klien, peran, dan metadata pengguna. Jenis cache ini tidak direplikasi, meskipun cache tersebut merupakan bagian dari klaster Keycloak. Jika entri dalam cache berubah, pesan tentang perubahan tersebut dikirim ke server yang tersisa di cluster, setelah itu entri tersebut dikecualikan dari cache. Lihat deskripsi work Lihat di bawah untuk penjelasan lebih rinci tentang prosedurnya.

  • Direplikasi. Memproses sesi pengguna, token offline, dan juga memantau kesalahan login untuk mendeteksi upaya phishing kata sandi dan serangan lainnya. Data yang disimpan dalam cache ini bersifat sementara, hanya disimpan dalam RAM, namun dapat direplikasi ke seluruh cluster.

Cache Infinispan

Sesi - sebuah konsep di Keycloak, cache terpisah disebut authenticationSessions, digunakan untuk menyimpan data pengguna tertentu. Permintaan dari cache ini biasanya dibutuhkan oleh browser dan server Keycloak, bukan oleh aplikasi. Di sinilah ketergantungan pada sesi lengket berperan, dan cache tersebut sendiri tidak perlu direplikasi, bahkan dalam kasus mode Aktif-Aktif.

Token Tindakan. Konsep lain, biasanya digunakan untuk berbagai skenario ketika, misalnya, pengguna harus melakukan sesuatu secara asinkron melalui email. Misalnya saat prosedur forget password cache actionTokens digunakan untuk melacak metadata token terkait - misalnya, token telah digunakan dan tidak dapat diaktifkan kembali. Jenis cache ini biasanya perlu direplikasi antar pusat data.

Caching dan penuaan data yang disimpan berfungsi untuk meringankan beban pada database. Caching semacam ini meningkatkan kinerja, namun menambah masalah yang jelas. Jika salah satu server Keycloak memperbarui data, server lain harus diberi tahu agar mereka dapat memperbarui data di cache mereka. Keycloak menggunakan cache lokal realms, users ΠΈ authorization untuk menyimpan data dari database.

Ada juga cache terpisah work, yang direplikasi di semua pusat data. Itu sendiri tidak menyimpan data apa pun dari database, tetapi berfungsi untuk mengirim pesan tentang penuaan data ke node cluster antar pusat data. Dengan kata lain, segera setelah data diperbarui, node Keycloak mengirimkan pesan ke node lain di pusat datanya, serta node di pusat data lainnya. Setelah menerima pesan seperti itu, setiap node menghapus data terkait di cache lokalnya.

Sesi pengguna. Cache dengan nama sessions, clientSessions, offlineSessions ΠΈ offlineClientSessions, biasanya direplikasi antar pusat data dan berfungsi untuk menyimpan data tentang sesi pengguna yang aktif saat pengguna aktif di browser. Cache ini berfungsi dengan aplikasi yang memproses permintaan HTTP dari pengguna akhir, sehingga cache tersebut dikaitkan dengan sesi melekat dan harus direplikasi antar pusat data.

Perlindungan kekerasan. Cache loginFailures Digunakan untuk melacak data kesalahan login, seperti berapa kali pengguna memasukkan kata sandi yang salah. Replikasi cache ini adalah tanggung jawab administrator. Namun untuk perhitungan yang akurat, ada baiknya mengaktifkan replikasi antar pusat data. Namun di sisi lain, jika Anda tidak mereplikasi data ini, Anda akan meningkatkan performa, dan jika masalah ini muncul, replikasi mungkin tidak diaktifkan.

Saat meluncurkan cluster Infinispan, Anda perlu menambahkan definisi cache ke file pengaturan:

<replicated-cache-configuration name="keycloak-sessions" mode="ASYNC" start="EAGER" batching="false">
</replicated-cache-configuration>

<replicated-cache name="work" configuration="keycloak-sessions" />
<replicated-cache name="sessions" configuration="keycloak-sessions" />
<replicated-cache name="offlineSessions" configuration="keycloak-sessions" />
<replicated-cache name="actionTokens" configuration="keycloak-sessions" />
<replicated-cache name="loginFailures" configuration="keycloak-sessions" />
<replicated-cache name="clientSessions" configuration="keycloak-sessions" />
<replicated-cache name="offlineClientSessions" configuration="keycloak-sessions" />

Anda harus mengonfigurasi dan memulai klaster Infinispan sebelum memulai klaster Keycloak

Maka Anda perlu mengkonfigurasi remoteStore untuk cache Keycloak. Untuk melakukan ini, cukup skrip, yang dilakukan mirip dengan yang sebelumnya, yang digunakan untuk mengatur variabel CACHE_OWNERS, Anda perlu menyimpannya ke file dan meletakkannya di direktori /opt/jboss/startup-scripts:

Isi Naskah

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo *** Update infinispan subsystem ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)

echo ** Add remote socket binding to infinispan server **
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})

echo ** Update replicated-cache work element **
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=work, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)

/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache sessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=sessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache offlineSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=offlineSessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache clientSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=clientSessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache offlineClientSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=offlineClientSessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache loginFailures element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=loginFailures, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache actionTokens element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    cache=actionTokens, 
    remote-servers=["remote-cache"], 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=statistics-enabled,value=true)

echo ** Update distributed-cache authenticationSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)

echo *** Update undertow subsystem ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)

run-batch
stop-embedded-server

Jangan lupa untuk menginstal JAVA_OPTS agar node Keycloak menjalankan HotRod: remote.cache.host, remote.cache.port dan nama layanan jboss.site.name.

Tautan dan dokumentasi tambahan

Artikel tersebut diterjemahkan dan disiapkan untuk Habr oleh para karyawan Pusat pelatihan lumpur β€” kursus intensif, kursus video, dan pelatihan perusahaan dari spesialis praktik (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Sumber: www.habr.com

Tambah komentar