Konfigurasi boleh susun sistem teragih

Dalam siaran ini kami ingin berkongsi cara yang menarik untuk menangani konfigurasi sistem teragih.
Konfigurasi diwakili secara langsung dalam bahasa Scala dengan cara selamat jenis. Contoh pelaksanaan diterangkan secara terperinci. Pelbagai aspek cadangan dibincangkan, termasuk pengaruh ke atas keseluruhan proses pembangunan.

Konfigurasi boleh susun sistem teragih

(dalam bahasa Rusia)

Pengenalan

Membina sistem teragih yang teguh memerlukan penggunaan konfigurasi yang betul dan koheren pada semua nod. Penyelesaian biasa ialah menggunakan penerangan penggunaan teks (terraform, ansible atau sesuatu yang serupa) dan fail konfigurasi yang dijana secara automatik (selalunya β€” khusus untuk setiap nod/peranan). Kami juga ingin menggunakan protokol yang sama bagi versi yang sama pada setiap nod yang berkomunikasi (jika tidak, kami akan mengalami isu ketidakserasian). Dalam dunia JVM ini bermakna bahawa sekurang-kurangnya perpustakaan pemesejan hendaklah daripada versi yang sama pada semua nod berkomunikasi.

Bagaimana pula dengan menguji sistem? Sudah tentu, kita harus mempunyai ujian unit untuk semua komponen sebelum datang ke ujian penyepaduan. Untuk dapat mengekstrapolasi keputusan ujian pada masa jalan, kita harus memastikan bahawa versi semua pustaka dikekalkan sama dalam kedua-dua masa jalan dan persekitaran ujian.

Apabila menjalankan ujian penyepaduan, selalunya lebih mudah untuk mempunyai laluan kelas yang sama pada semua nod. Kami hanya perlu memastikan bahawa laluan kelas yang sama digunakan pada penggunaan. (Adalah mungkin untuk menggunakan laluan kelas yang berbeza pada nod yang berbeza, tetapi lebih sukar untuk mewakili konfigurasi ini dan menggunakan konfigurasi ini dengan betul.) Jadi untuk memastikan perkara mudah, kami hanya akan mempertimbangkan laluan kelas yang sama pada semua nod.

Konfigurasi cenderung untuk berkembang bersama-sama dengan perisian. Kami biasanya menggunakan versi untuk mengenal pasti pelbagai
peringkat evolusi perisian. Nampaknya munasabah untuk merangkumi konfigurasi di bawah pengurusan versi dan mengenal pasti konfigurasi yang berbeza dengan beberapa label. Jika terdapat hanya satu konfigurasi dalam pengeluaran, kami boleh menggunakan versi tunggal sebagai pengecam. Kadangkala kita mungkin mempunyai berbilang persekitaran pengeluaran. Dan untuk setiap persekitaran kita mungkin memerlukan cabang konfigurasi yang berasingan. Jadi konfigurasi mungkin dilabelkan dengan cawangan dan versi untuk mengenal pasti konfigurasi berbeza secara unik. Setiap label dan versi cawangan sepadan dengan gabungan tunggal nod yang diedarkan, port, sumber luaran, versi perpustakaan classpath pada setiap nod. Di sini kita hanya akan merangkumi cawangan tunggal dan mengenal pasti konfigurasi dengan versi perpuluhan tiga komponen (1.2.3), dengan cara yang sama seperti artifak lain.

Dalam persekitaran moden, fail konfigurasi tidak diubah suai secara manual lagi. Biasanya kita menjana
fail konfigurasi pada masa penggunaan dan jangan sekali-kali menyentuh mereka selepas itu. Jadi seseorang boleh bertanya mengapa kita masih menggunakan format teks untuk fail konfigurasi? Pilihan yang berdaya maju ialah meletakkan konfigurasi di dalam unit kompilasi dan mendapat manfaat daripada pengesahan konfigurasi masa kompilasi.

Dalam siaran ini kita akan mengkaji idea untuk mengekalkan konfigurasi dalam artifak yang disusun.

Konfigurasi boleh susun

Dalam bahagian ini kita akan membincangkan contoh konfigurasi statik. Dua perkhidmatan mudah - perkhidmatan gema dan pelanggan perkhidmatan gema sedang dikonfigurasikan dan dilaksanakan. Kemudian dua sistem teragih yang berbeza dengan kedua-dua perkhidmatan dijadikan instantiated. Satu adalah untuk konfigurasi nod tunggal dan satu lagi untuk konfigurasi dua nod.

Sistem teragih biasa terdiri daripada beberapa nod. Nod boleh dikenal pasti menggunakan beberapa jenis:

sealed trait NodeId
case object Backend extends NodeId
case object Frontend extends NodeId

atau hanya

case class NodeId(hostName: String)

atau

object Singleton
type NodeId = Singleton.type

Nod ini melaksanakan pelbagai peranan, menjalankan beberapa perkhidmatan dan harus boleh berkomunikasi dengan nod lain melalui sambungan TCP/HTTP.

Untuk sambungan TCP sekurang-kurangnya nombor port diperlukan. Kami juga ingin memastikan bahawa klien dan pelayan bercakap protokol yang sama. Untuk memodelkan sambungan antara nod mari kita isytiharkan kelas berikut:

case class TcpEndPoint[Protocol](node: NodeId, port: Port[Protocol])

di mana Port hanyalah sebuah Int dalam julat yang dibenarkan:

type PortNumber = Refined[Int, Closed[_0, W.`65535`.T]]

Jenis halus

Lihat ditapis perpustakaan. Ringkasnya, ia membolehkan untuk menambah kekangan masa penyusunan kepada jenis lain. Dalam kes ini Int hanya dibenarkan mempunyai nilai 16-bit yang boleh mewakili nombor port. Tiada keperluan untuk menggunakan perpustakaan ini untuk pendekatan konfigurasi ini. Ia kelihatan sangat sesuai.

Untuk HTTP (REST) ​​​​kami mungkin juga memerlukan laluan perkhidmatan:

type UrlPathPrefix = Refined[String, MatchesRegex[W.`"[a-zA-Z_0-9/]*"`.T]]
case class PortWithPrefix[Protocol](portNumber: PortNumber, pathPrefix: UrlPathPrefix)

Jenis hantu

Untuk mengenal pasti protokol semasa penyusunan kami menggunakan ciri Scala untuk mengisytiharkan hujah jenis Protocol yang tidak digunakan dalam kelas. Ia adalah apa yang dipanggil jenis hantu. Pada masa jalan kami jarang memerlukan contoh pengecam protokol, itulah sebabnya kami tidak menyimpannya. Semasa penyusunan jenis hantu ini memberikan keselamatan jenis tambahan. Kami tidak boleh melepasi port dengan protokol yang salah.

Salah satu protokol yang paling banyak digunakan ialah REST API dengan siri Json:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

di mana RequestMessage ialah jenis asas mesej yang pelanggan boleh hantar ke pelayan dan ResponseMessage ialah mesej respons daripada pelayan. Sudah tentu, kami boleh mencipta perihalan protokol lain yang menentukan protokol komunikasi dengan ketepatan yang dikehendaki.

Untuk tujuan siaran ini, kami akan menggunakan versi protokol yang lebih mudah:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Dalam protokol ini, mesej permintaan dilampirkan pada url dan mesej respons dikembalikan sebagai rentetan biasa.

Konfigurasi perkhidmatan boleh diterangkan dengan nama perkhidmatan, koleksi port dan beberapa kebergantungan. Terdapat beberapa cara yang mungkin untuk mewakili semua elemen ini dalam Scala (contohnya, HList, jenis data algebra). Untuk tujuan siaran ini, kami akan menggunakan Corak Kek dan mewakili kepingan boleh digabungkan (modul) sebagai ciri. (Corak Kek bukan keperluan untuk pendekatan konfigurasi yang boleh dikompilasi ini. Ia hanyalah satu kemungkinan pelaksanaan idea itu.)

Ketergantungan boleh diwakili menggunakan Corak Kek sebagai titik akhir nod lain:

  type EchoProtocol[A] = SimpleHttpGetRest[A, A]

  trait EchoConfig[A] extends ServiceConfig {
    def portNumber: PortNumber = 8081
    def echoPort: PortWithPrefix[EchoProtocol[A]] = PortWithPrefix[EchoProtocol[A]](portNumber, "echo")
    def echoService: HttpSimpleGetEndPoint[NodeId, EchoProtocol[A]] = providedSimpleService(echoPort)
  }

Perkhidmatan gema hanya memerlukan port yang dikonfigurasikan. Dan kami mengisytiharkan bahawa port ini menyokong protokol gema. Ambil perhatian bahawa kita tidak perlu menentukan port tertentu pada masa ini, kerana sifat membenarkan pengisytiharan kaedah abstrak. Jika kita menggunakan kaedah abstrak, pengkompil akan memerlukan pelaksanaan dalam contoh konfigurasi. Di sini kami telah menyediakan pelaksanaan (8081) dan ia akan digunakan sebagai nilai lalai jika kita melangkaunya dalam konfigurasi konkrit.

Kami boleh mengisytiharkan pergantungan dalam konfigurasi pelanggan perkhidmatan gema:

  trait EchoClientConfig[A] {
    def testMessage: String = "test"
    def pollInterval: FiniteDuration
    def echoServiceDependency: HttpSimpleGetEndPoint[_, EchoProtocol[A]]
  }

Ketergantungan mempunyai jenis yang sama seperti echoService. Khususnya, ia menuntut protokol yang sama. Oleh itu, kita boleh yakin bahawa jika kita menyambungkan kedua-dua kebergantungan ini, ia akan berfungsi dengan betul.

Pelaksanaan perkhidmatan

Perkhidmatan memerlukan fungsi untuk dimulakan dan ditutup dengan anggun. (Keupayaan untuk menutup perkhidmatan adalah penting untuk ujian.) Sekali lagi terdapat beberapa pilihan untuk menentukan fungsi sedemikian untuk konfigurasi tertentu (contohnya, kita boleh menggunakan kelas jenis). Untuk siaran ini kami akan menggunakan Corak Kek sekali lagi. Kami boleh mewakili perkhidmatan menggunakan cats.Resource yang telah menyediakan pendakapan dan keluaran sumber. Untuk memperoleh sumber, kami harus menyediakan konfigurasi dan beberapa konteks masa jalan. Jadi fungsi permulaan perkhidmatan mungkin kelihatan seperti:

  type ResourceReader[F[_], Config, A] = Reader[Config, Resource[F, A]]

  trait ServiceImpl[F[_]] {
    type Config
    def resource(
      implicit
      resolver: AddressResolver[F],
      timer: Timer[F],
      contextShift: ContextShift[F],
      ec: ExecutionContext,
      applicative: Applicative[F]
    ): ResourceReader[F, Config, Unit]
  }

di mana

  • Config β€” jenis konfigurasi yang diperlukan oleh pemula perkhidmatan ini
  • AddressResolver β€” objek masa jalan yang mempunyai keupayaan untuk mendapatkan alamat sebenar nod lain (teruskan membaca untuk butiran).

jenis lain datang dari cats:

  • F[_] β€” jenis kesan (Dalam kes paling mudah F[A] boleh jadi adil () => A. Dalam siaran ini kami akan gunakan cats.IO.)
  • Reader[A,B] β€” adalah lebih kurang sinonim untuk fungsi A => B
  • cats.Resource β€” mempunyai cara untuk memperoleh dan melepaskan
  • Timer β€” membolehkan untuk tidur/mengukur masa
  • ContextShift - analog daripada ExecutionContext
  • Applicative β€” pembungkus fungsi yang berkuat kuasa (hampir monad) (kita mungkin akan menggantikannya dengan sesuatu yang lain)

Menggunakan antara muka ini kami boleh melaksanakan beberapa perkhidmatan. Sebagai contoh, perkhidmatan yang tidak melakukan apa-apa:

  trait ZeroServiceImpl[F[_]] extends ServiceImpl[F] {
    type Config <: Any
    def resource(...): ResourceReader[F, Config, Unit] =
      Reader(_ => Resource.pure[F, Unit](()))
  }

(Lihat Kod sumber untuk pelaksanaan perkhidmatan lain - perkhidmatan gema,
pelanggan gema and pengawal seumur hidup.)

Nod ialah objek tunggal yang menjalankan beberapa perkhidmatan (memulakan rantaian sumber didayakan oleh Corak Kek):

object SingleNodeImpl extends ZeroServiceImpl[IO]
  with EchoServiceService
  with EchoClientService
  with FiniteDurationLifecycleServiceImpl
{
  type Config = EchoConfig[String] with EchoClientConfig[String] with FiniteDurationLifecycleConfig
}

Ambil perhatian bahawa dalam nod kami menentukan jenis konfigurasi yang tepat yang diperlukan oleh nod ini. Pengkompil tidak akan membenarkan kami membina objek (Kek) dengan jenis yang tidak mencukupi, kerana setiap ciri perkhidmatan mengisytiharkan kekangan pada Config menaip. Kami juga tidak akan dapat memulakan nod tanpa menyediakan konfigurasi lengkap.

Resolusi alamat nod

Untuk mewujudkan sambungan, kami memerlukan alamat hos sebenar untuk setiap nod. Ia mungkin diketahui kemudian daripada bahagian lain konfigurasi. Oleh itu, kami memerlukan cara untuk membekalkan pemetaan antara id nod dan alamat sebenar. Pemetaan ini adalah fungsi:

case class NodeAddress[NodeId](host: Uri.Host)
trait AddressResolver[F[_]] {
  def resolve[NodeId](nodeId: NodeId): F[NodeAddress[NodeId]]
}

Terdapat beberapa cara yang mungkin untuk melaksanakan fungsi sedemikian.

  1. Jika kita mengetahui alamat sebenar sebelum penggunaan, semasa instantiasi hos nod, maka kita boleh menjana kod Scala dengan alamat sebenar dan menjalankan binaan selepas itu (yang melakukan semakan masa penyusunan dan kemudian menjalankan suite ujian penyepaduan). Dalam kes ini fungsi pemetaan kami diketahui secara statik dan boleh dipermudahkan kepada sesuatu seperti a Map[NodeId, NodeAddress].
  2. Kadangkala kami memperoleh alamat sebenar hanya pada satu masa kemudian apabila nod sebenarnya dimulakan, atau kami tidak mempunyai alamat nod yang belum dimulakan lagi. Dalam kes ini, kami mungkin mempunyai perkhidmatan penemuan yang dimulakan sebelum semua nod lain dan setiap nod mungkin mengiklankan alamatnya dalam perkhidmatan tersebut dan melanggan kebergantungan.
  3. Jika kita boleh mengubah suai /etc/hosts, kita boleh menggunakan nama hos yang telah ditetapkan (seperti my-project-main-node and echo-backend) dan hanya kaitkan nama ini dengan alamat ip pada masa penggunaan.

Dalam siaran ini kami tidak membincangkan kes ini dengan lebih terperinci. Malah dalam contoh mainan kami semua nod akan mempunyai alamat IP yang sama - 127.0.0.1.

Dalam siaran ini, kami akan mempertimbangkan dua susun atur sistem teragih:

  1. Susun atur nod tunggal, di mana semua perkhidmatan diletakkan pada nod tunggal.
  2. Dua susun atur nod, di mana perkhidmatan dan klien berada pada nod yang berbeza.

Konfigurasi untuk a nod tunggal susun atur adalah seperti berikut:

Konfigurasi nod tunggal

object SingleNodeConfig extends EchoConfig[String] 
  with EchoClientConfig[String] with FiniteDurationLifecycleConfig
{
  case object Singleton // identifier of the single node 
  // configuration of server
  type NodeId = Singleton.type
  def nodeId = Singleton

  /** Type safe service port specification. */
  override def portNumber: PortNumber = 8088

  // configuration of client

  /** We'll use the service provided by the same host. */
  def echoServiceDependency = echoService

  override def testMessage: UrlPathElement = "hello"

  def pollInterval: FiniteDuration = 1.second

  // lifecycle controller configuration
  def lifetime: FiniteDuration = 10500.milliseconds // additional 0.5 seconds so that there are 10 requests, not 9.
}

Di sini kami mencipta konfigurasi tunggal yang memanjangkan konfigurasi pelayan dan klien. Kami juga mengkonfigurasi pengawal kitaran hayat yang biasanya akan menamatkan klien dan pelayan selepas itu lifetime selang berlalu.

Set pelaksanaan dan konfigurasi perkhidmatan yang sama boleh digunakan untuk mencipta reka letak sistem dengan dua nod yang berasingan. Kita hanya perlu mencipta dua konfigurasi nod yang berasingan dengan perkhidmatan yang sesuai:

Konfigurasi dua nod

  object NodeServerConfig extends EchoConfig[String] with SigTermLifecycleConfig
  {
    type NodeId = NodeIdImpl

    def nodeId = NodeServer

    override def portNumber: PortNumber = 8080
  }

  object NodeClientConfig extends EchoClientConfig[String] with FiniteDurationLifecycleConfig
  {
    // NB! dependency specification
    def echoServiceDependency = NodeServerConfig.echoService

    def pollInterval: FiniteDuration = 1.second

    def lifetime: FiniteDuration = 10500.milliseconds // additional 0.5 seconds so that there are 10 request, not 9.

    def testMessage: String = "dolly"
  }

Lihat cara kami menentukan kebergantungan. Kami menyebut perkhidmatan yang disediakan nod lain sebagai pergantungan nod semasa. Jenis pergantungan disemak kerana ia mengandungi jenis hantu yang menerangkan protokol. Dan pada masa jalan kita akan mempunyai id nod yang betul. Ini adalah salah satu aspek penting dalam pendekatan konfigurasi yang dicadangkan. Ia memberikan kami keupayaan untuk menetapkan port sekali sahaja dan pastikan kami merujuk port yang betul.

Pelaksanaan dua nod

Untuk konfigurasi ini kami menggunakan pelaksanaan perkhidmatan yang sama. Tiada perubahan langsung. Walau bagaimanapun, kami mencipta dua pelaksanaan nod berbeza yang mengandungi set perkhidmatan yang berbeza:

  object TwoJvmNodeServerImpl extends ZeroServiceImpl[IO] with EchoServiceService with SigIntLifecycleServiceImpl {
    type Config = EchoConfig[String] with SigTermLifecycleConfig
  }

  object TwoJvmNodeClientImpl extends ZeroServiceImpl[IO] with EchoClientService with FiniteDurationLifecycleServiceImpl {
    type Config = EchoClientConfig[String] with FiniteDurationLifecycleConfig
  }

Nod pertama melaksanakan pelayan dan ia hanya memerlukan konfigurasi sisi pelayan. Nod kedua melaksanakan klien dan memerlukan bahagian konfigurasi yang lain. Kedua-dua nod memerlukan beberapa spesifikasi seumur hidup. Untuk tujuan nod perkhidmatan pos ini akan mempunyai seumur hidup yang tidak terhingga yang boleh ditamatkan penggunaannya SIGTERM, manakala klien gema akan ditamatkan selepas tempoh terhingga yang dikonfigurasikan. Lihat aplikasi permulaan untuk maklumat lanjut.

Proses pembangunan keseluruhan

Mari lihat bagaimana pendekatan ini mengubah cara kita bekerja dengan konfigurasi.

Konfigurasi sebagai kod akan disusun dan menghasilkan artifak. Nampaknya munasabah untuk memisahkan artifak konfigurasi daripada artifak kod lain. Selalunya kita boleh mempunyai banyak konfigurasi pada asas kod yang sama. Dan sudah tentu, kita boleh mempunyai berbilang versi pelbagai cabang konfigurasi. Dalam konfigurasi, kami boleh memilih versi perpustakaan tertentu dan ini akan kekal malar apabila kami menggunakan konfigurasi ini.

Perubahan konfigurasi menjadi perubahan kod. Oleh itu, ia harus dilindungi oleh proses jaminan kualiti yang sama:

Tiket -> PR -> semakan -> gabungan -> penyepaduan berterusan -> penggunaan berterusan

Terdapat akibat berikut dari pendekatan:

  1. Konfigurasi adalah koheren untuk contoh sistem tertentu. Nampaknya tidak ada cara untuk mempunyai sambungan yang salah antara nod.
  2. Bukan mudah untuk menukar konfigurasi hanya dalam satu nod. Nampaknya tidak munasabah untuk log masuk dan menukar beberapa fail teks. Jadi hanyut konfigurasi menjadi kurang mungkin.
  3. Perubahan konfigurasi kecil tidak mudah dibuat.
  4. Kebanyakan perubahan konfigurasi akan mengikuti proses pembangunan yang sama, dan ia akan lulus beberapa semakan.

Adakah kita memerlukan repositori berasingan untuk konfigurasi pengeluaran? Konfigurasi pengeluaran mungkin mengandungi maklumat sensitif yang kami ingin jauhkan daripada jangkauan ramai orang. Jadi mungkin berbaloi menyimpan repositori berasingan dengan akses terhad yang akan mengandungi konfigurasi pengeluaran. Kami mungkin membahagikan konfigurasi kepada dua bahagian - satu yang mengandungi parameter pengeluaran yang paling terbuka dan satu yang mengandungi bahagian rahsia konfigurasi. Ini akan membolehkan akses kepada kebanyakan pembangun kepada sebahagian besar parameter sambil mengehadkan akses kepada perkara yang sangat sensitif. Sangat mudah untuk mencapai ini menggunakan ciri perantaraan dengan nilai parameter lalai.

Variasi

Mari lihat kebaikan dan keburukan pendekatan yang dicadangkan berbanding dengan teknik pengurusan konfigurasi yang lain.

Pertama sekali, kami akan menyenaraikan beberapa alternatif kepada pelbagai aspek cara yang dicadangkan untuk menangani konfigurasi:

  1. Fail teks pada mesin sasaran.
  2. Storan nilai kunci terpusat (seperti etcd/zookeeper).
  3. Komponen subproses yang boleh dikonfigurasikan semula/dimulakan semula tanpa memulakan semula proses.
  4. Konfigurasi di luar artifak dan kawalan versi.

Fail teks memberikan sedikit fleksibiliti dari segi pembetulan ad-hoc. Pentadbir sistem boleh log masuk ke nod sasaran, membuat perubahan dan hanya memulakan semula perkhidmatan. Ini mungkin tidak begitu baik untuk sistem yang lebih besar. Tiada kesan tertinggal di sebalik perubahan itu. Perubahan itu tidak disemak oleh sepasang mata lain. Mungkin sukar untuk mengetahui apa yang menyebabkan perubahan itu. Ia belum diuji. Dari perspektif sistem yang diedarkan, seorang pentadbir boleh lupa untuk mengemas kini konfigurasi dalam salah satu nod lain.

(Btw, jika akhirnya terdapat keperluan untuk mula menggunakan fail konfigurasi teks, kami hanya perlu menambah parser + validator yang boleh menghasilkan yang sama Config taip dan itu sudah cukup untuk mula menggunakan konfigurasi teks. Ini juga menunjukkan bahawa kerumitan konfigurasi masa kompilasi adalah lebih kecil sedikit daripada kerumitan konfigurasi berasaskan teks, kerana dalam versi berasaskan teks kita memerlukan beberapa kod tambahan.)

Storan nilai kunci terpusat ialah mekanisme yang baik untuk mengedarkan parameter meta aplikasi. Di sini kita perlu memikirkan apa yang kita anggap sebagai nilai konfigurasi dan apa itu hanya data. Diberi fungsi C => A => B kita biasanya memanggil nilai yang jarang berubah C "konfigurasi", sementara data sering ditukar A - hanya masukkan data. Konfigurasi harus diberikan kepada fungsi lebih awal daripada data A. Memandangkan idea ini, kita boleh mengatakan bahawa kekerapan perubahan yang dijangkakan yang boleh digunakan untuk membezakan data konfigurasi daripada data sahaja. Juga data biasanya datang daripada satu sumber (pengguna) dan konfigurasi datang daripada sumber yang berbeza (admin). Berurusan dengan parameter yang boleh diubah selepas proses permulaan membawa kepada peningkatan kerumitan aplikasi. Untuk parameter sedemikian, kita perlu mengendalikan mekanisme penghantaran, penghuraian dan pengesahannya, mengendalikan nilai yang salah. Oleh itu, untuk mengurangkan kerumitan program, lebih baik kita mengurangkan bilangan parameter yang boleh berubah pada masa jalan (atau malah menghapuskannya sama sekali).

Dari perspektif siaran ini, kita harus membuat perbezaan antara parameter statik dan dinamik. Jika logik perkhidmatan memerlukan perubahan jarang bagi beberapa parameter pada masa jalan, maka kami boleh memanggilnya parameter dinamik. Jika tidak, ia adalah statik dan boleh dikonfigurasikan menggunakan pendekatan yang dicadangkan. Untuk konfigurasi semula dinamik pendekatan lain mungkin diperlukan. Sebagai contoh, bahagian sistem mungkin dimulakan semula dengan parameter konfigurasi baharu dengan cara yang sama seperti memulakan semula proses berasingan bagi sistem teragih.
(Pendapat saya yang rendah hati adalah untuk mengelakkan konfigurasi semula masa jalan kerana ia meningkatkan kerumitan sistem.
Mungkin lebih mudah untuk hanya bergantung pada sokongan OS untuk memulakan semula proses. Walaupun, ia mungkin tidak selalu mungkin.)

Satu aspek penting dalam menggunakan konfigurasi statik yang kadangkala membuatkan orang menganggap konfigurasi dinamik (tanpa sebab lain) ialah masa henti perkhidmatan semasa kemas kini konfigurasi. Sesungguhnya, jika kita perlu membuat perubahan pada konfigurasi statik, kita perlu memulakan semula sistem supaya nilai baharu menjadi berkesan. Keperluan untuk masa henti berbeza-beza untuk sistem yang berbeza, jadi ia mungkin tidak begitu kritikal. Jika ia kritikal, maka kita perlu merancang lebih awal untuk sebarang sistem dimulakan semula. Sebagai contoh, kita boleh melaksanakan Sambungan AWS ELB terputus. Dalam senario ini apabila kita perlu memulakan semula sistem, kita memulakan contoh baharu sistem secara selari, kemudian menukar ELB kepadanya, sambil membenarkan sistem lama menyelesaikan perkhidmatan sambungan sedia ada.

Bagaimana pula dengan mengekalkan konfigurasi di dalam artifak versi atau di luar? Menyimpan konfigurasi di dalam artifak bermakna dalam kebanyakan kes konfigurasi ini telah melepasi proses jaminan kualiti yang sama seperti artifak lain. Jadi seseorang mungkin pasti bahawa konfigurasi adalah berkualiti dan boleh dipercayai. Sebaliknya konfigurasi dalam fail berasingan bermakna tiada kesan siapa dan mengapa membuat perubahan pada fail tersebut. Adakah ini penting? Kami percaya bahawa untuk kebanyakan sistem pengeluaran adalah lebih baik untuk mempunyai konfigurasi yang stabil dan berkualiti tinggi.

Versi artifak membolehkan untuk mengetahui bila ia dicipta, apakah nilai yang terkandung di dalamnya, apakah ciri yang didayakan/dilumpuhkan, siapa yang bertanggungjawab untuk membuat setiap perubahan dalam konfigurasi. Ia mungkin memerlukan sedikit usaha untuk mengekalkan konfigurasi di dalam artifak dan ia merupakan pilihan reka bentuk untuk dibuat.

Kebaikan dan keburukan

Di sini kami ingin menyerlahkan beberapa kelebihan dan membincangkan beberapa kelemahan pendekatan yang dicadangkan.

kelebihan

Ciri-ciri konfigurasi boleh susun sistem teragih lengkap:

  1. Semakan statik konfigurasi. Ini memberikan tahap keyakinan yang tinggi, bahawa konfigurasi adalah betul memandangkan kekangan jenis.
  2. Bahasa konfigurasi yang kaya. Biasanya pendekatan konfigurasi lain dihadkan kepada kebanyakan penggantian berubah-ubah.
    Menggunakan Scala seseorang boleh menggunakan pelbagai ciri bahasa untuk menjadikan konfigurasi lebih baik. Sebagai contoh, kita boleh menggunakan ciri untuk memberikan nilai lalai, objek untuk menetapkan skop yang berbeza, kita boleh merujuk kepada vals ditakrifkan sekali sahaja dalam skop luar (KERING). Ia mungkin untuk menggunakan urutan literal, atau contoh kelas tertentu (Seq, Map, dan lain-lain.).
  3. DSL. Scala mempunyai sokongan yang baik untuk penulis DSL. Seseorang boleh menggunakan ciri ini untuk mewujudkan bahasa konfigurasi yang lebih mudah dan mesra pengguna akhir, supaya konfigurasi akhir sekurang-kurangnya boleh dibaca oleh pengguna domain.
  4. Integriti dan koheren merentas nod. Salah satu faedah mempunyai konfigurasi untuk keseluruhan sistem yang diedarkan di satu tempat ialah semua nilai ditakrifkan sekali sahaja dan kemudian digunakan semula di semua tempat di mana kita memerlukannya. Taipkan juga pengisytiharan port selamat memastikan bahawa dalam semua konfigurasi yang mungkin betul, nod sistem akan bercakap bahasa yang sama. Terdapat kebergantungan yang jelas antara nod yang menjadikannya sukar untuk dilupakan untuk menyediakan beberapa perkhidmatan.
  5. Kualiti perubahan yang tinggi. Pendekatan keseluruhan untuk meneruskan perubahan konfigurasi melalui proses PR biasa mewujudkan standard kualiti yang tinggi juga dalam konfigurasi.
  6. Perubahan konfigurasi serentak. Setiap kali kami membuat sebarang perubahan dalam penggunaan automatik konfigurasi memastikan semua nod sedang dikemas kini.
  7. Memudahkan permohonan. Aplikasi tidak perlu menghuraikan dan mengesahkan konfigurasi serta mengendalikan nilai konfigurasi yang salah. Ini memudahkan aplikasi keseluruhan. (Sesetengah peningkatan kerumitan adalah dalam konfigurasi itu sendiri, tetapi ia merupakan pertukaran sedar ke arah keselamatan.) Ia agak mudah untuk kembali kepada konfigurasi biasa β€” cuma tambah bahagian yang hilang. Lebih mudah untuk bermula dengan konfigurasi yang disusun dan menangguhkan pelaksanaan bahagian tambahan untuk beberapa waktu kemudian.
  8. Konfigurasi versi. Oleh kerana perubahan konfigurasi mengikut proses pembangunan yang sama, hasilnya kami mendapat artifak dengan versi unik. Ia membolehkan kami menukar konfigurasi semula jika perlu. Kita juga boleh menggunakan konfigurasi yang telah digunakan setahun yang lalu dan ia akan berfungsi dengan cara yang sama. Konfigurasi stabil meningkatkan kebolehramalan dan kebolehpercayaan sistem yang diedarkan. Konfigurasi ditetapkan pada masa penyusunan dan tidak boleh diganggu dengan mudah pada sistem pengeluaran.
  9. Modulariti. Rangka kerja yang dicadangkan adalah modular dan modul boleh digabungkan dalam pelbagai cara untuk
    menyokong konfigurasi yang berbeza (persediaan/susun atur). Khususnya, ada kemungkinan untuk mempunyai susun atur nod tunggal berskala kecil dan tetapan berbilang nod skala besar. Adalah munasabah untuk mempunyai berbilang susun atur pengeluaran.
  10. Menguji. Untuk tujuan ujian seseorang mungkin melaksanakan perkhidmatan olok-olok dan menggunakannya sebagai pergantungan dengan cara selamat jenis. Beberapa reka letak ujian yang berbeza dengan pelbagai bahagian digantikan dengan olok-olok boleh dikekalkan serentak.
  11. Ujian integrasi. Kadangkala dalam sistem teragih sukar untuk menjalankan ujian penyepaduan. Menggunakan pendekatan yang diterangkan untuk menaip konfigurasi selamat bagi sistem teragih yang lengkap, kami boleh menjalankan semua bahagian yang diedarkan pada pelayan tunggal dengan cara yang boleh dikawal. Mudah untuk mencontohi keadaan
    apabila salah satu perkhidmatan menjadi tidak tersedia.

Kekurangan

Pendekatan konfigurasi yang disusun adalah berbeza daripada konfigurasi "biasa" dan ia mungkin tidak sesuai dengan semua keperluan. Berikut adalah beberapa kelemahan konfigurasi yang disusun:

  1. Konfigurasi statik. Ia mungkin tidak sesuai untuk semua aplikasi. Dalam sesetengah kes, terdapat keperluan untuk membetulkan konfigurasi dengan cepat dalam pengeluaran memintas semua langkah keselamatan. Pendekatan ini menjadikannya lebih sukar. Penyusunan dan penempatan semula diperlukan selepas membuat sebarang perubahan dalam konfigurasi. Ini adalah ciri dan bebannya.
  2. Penjanaan konfigurasi. Apabila konfigurasi dijana oleh beberapa alat automasi pendekatan ini memerlukan kompilasi berikutnya (yang mungkin gagal). Ia mungkin memerlukan usaha tambahan untuk menyepadukan langkah tambahan ini ke dalam sistem binaan.
  3. Instrumen. Terdapat banyak alat yang digunakan hari ini yang bergantung pada konfigurasi berasaskan teks. Sebahagian daripada mereka
    tidak akan terpakai apabila konfigurasi disusun.
  4. Anjakan minda diperlukan. Pembangun dan DevOps biasa dengan fail konfigurasi teks. Idea menyusun konfigurasi mungkin kelihatan pelik kepada mereka.
  5. Sebelum memperkenalkan konfigurasi boleh dikompilasi, proses pembangunan perisian berkualiti tinggi diperlukan.

Terdapat beberapa batasan contoh yang dilaksanakan:

  1. Jika kami menyediakan konfigurasi tambahan yang tidak dituntut oleh pelaksanaan nod, pengkompil tidak akan membantu kami untuk mengesan pelaksanaan yang tidak hadir. Ini boleh diatasi dengan menggunakan HList atau ADT (kelas kes) untuk konfigurasi nod dan bukannya ciri dan Corak Kek.
  2. Kami perlu menyediakan beberapa boilerplate dalam fail konfigurasi: (package, import, object pengisytiharan;
    override def's untuk parameter yang mempunyai nilai lalai). Ini mungkin sebahagiannya ditangani menggunakan DSL.
  3. Dalam siaran ini kami tidak meliputi konfigurasi semula dinamik kelompok nod yang serupa.

Kesimpulan

Dalam siaran ini, kami telah membincangkan idea untuk mewakili konfigurasi secara langsung dalam kod sumber dengan cara yang selamat. Pendekatan ini boleh digunakan dalam banyak aplikasi sebagai pengganti kepada xml- dan konfigurasi berasaskan teks lain. Walaupun contoh kami telah dilaksanakan dalam Scala, ia juga boleh diterjemahkan ke bahasa lain yang boleh dikompilasi (seperti Kotlin, C#, Swift, dll.). Seseorang boleh mencuba pendekatan ini dalam projek baharu dan, sekiranya ia tidak sesuai, tukar kepada cara lama.

Sudah tentu, konfigurasi yang boleh dikompilasi memerlukan proses pembangunan berkualiti tinggi. Sebagai balasannya, ia berjanji untuk menyediakan konfigurasi teguh berkualiti tinggi yang sama.

Pendekatan ini boleh diperluaskan dalam pelbagai cara:

  1. Seseorang boleh menggunakan makro untuk melaksanakan pengesahan konfigurasi dan gagal pada masa penyusunan sekiranya berlaku sebarang kegagalan kekangan logik perniagaan.
  2. DSL boleh dilaksanakan untuk mewakili konfigurasi dalam cara yang mesra pengguna domain.
  3. Pengurusan sumber dinamik dengan pelarasan konfigurasi automatik. Sebagai contoh, apabila kita melaraskan bilangan nod kluster kita mungkin mahu (1) nod memperoleh konfigurasi yang diubah suai sedikit; (2) pengurus kluster untuk menerima maklumat nod baharu.

Terima kasih

Saya ingin mengucapkan terima kasih kepada Andrey Saksonov, Pavel Popov, Anton Nehaev kerana memberi maklum balas inspirasi pada draf siaran ini yang membantu saya menjadikannya lebih jelas.

Sumber: www.habr.com