Эмхэтгэсэн тархсан системийн тохиргоо

Тархсан системийн тохиргоотой ажиллах нэг сонирхолтой механизмыг би танд хэлэхийг хүсч байна. Тохиргоог шууд хөрвүүлсэн хэлээр (Scala) аюулгүй төрлөөр дүрсэлсэн. Энэхүү нийтлэл нь ийм тохиргооны жишээг өгч, эмхэтгэсэн тохиргоог ерөнхий хөгжлийн процесст хэрэгжүүлэх янз бүрийн талуудын талаар ярилцав.

Эмхэтгэсэн тархсан системийн тохиргоо

(англи хэл)

Танилцуулга

Найдвартай тархсан системийг бий болгох нь бүх зангилаа нь бусад зангилаатай синхрончлогдсон зөв тохиргоог ашигладаг гэсэн үг юм. DevOps технологиудыг (terraform, ansible эсвэл үүнтэй төстэй зүйл) ихэвчлэн тохиргооны файлуудыг автоматаар үүсгэхэд ашигладаг (ихэвчлэн зангилаа тус бүрт тусгайлан зориулсан). Бид бүх харилцах зангилаа ижил протоколуудыг (ижил хувилбарыг оруулаад) ашиглаж байгаа гэдэгт итгэлтэй байхыг хүсч байна. Үгүй бол бидний тархсан системд үл нийцэх байдал бий болно. JVM ертөнцөд энэ шаардлагын нэг үр дагавар нь протоколын мессежийг агуулсан номын сангийн ижил хувилбарыг хаа сайгүй ашиглах ёстой.

Түгээмэл системийг турших талаар юу хэлэх вэ? Мэдээжийн хэрэг, бид интеграцийн туршилт руу шилжихээс өмнө бүх бүрэлдэхүүн хэсгүүдийг нэгжийн тесттэй гэж үздэг. (Бид туршилтын үр дүнг ажлын цагтай харьцуулахын тулд туршилтын үе шатанд болон ажиллах үед ижил сангуудын багцыг өгөх ёстой.)

Интеграцийн тестүүдтэй ажиллахдаа бүх зангилааны хаа сайгүй ижил ангиллын замыг ашиглах нь ихэвчлэн хялбар байдаг. Бидний хийх ёстой зүйл бол ажиллах үед ижил ангийн замыг ашиглах явдал юм. (Хэдийгээр өөр өөр ангиллын зам бүхий өөр өөр зангилаа ажиллуулах бүрэн боломжтой ч энэ нь ерөнхий тохиргоонд төвөгтэй байдал, байршуулалт, нэгтгэх тестийн хүндрэлийг нэмэгдүүлдэг.) Энэ нийтлэлийн зорилгын үүднээс бид бүх зангилаа нэг ангиллын замыг ашиглана гэж үзэж байна.

Тохиргоо нь программыг дагаж өөрчлөгддөг. Бид програмын хувьслын янз бүрийн үе шатуудыг тодорхойлохын тулд хувилбаруудыг ашигладаг. Тохиргооны янз бүрийн хувилбаруудыг тодорхойлох нь логик юм. Мөн тохиргоог өөрөө хувилбарын хяналтын системд байрлуул. Хэрэв үйлдвэрлэлд зөвхөн нэг тохиргоо байгаа бол бид зүгээр л хувилбарын дугаарыг ашиглаж болно. Хэрэв бид олон үйлдвэрлэлийн жишээ ашигладаг бол бидэнд хэд хэдэн хэрэгтэй болно
тохиргооны салбарууд болон хувилбараас гадна нэмэлт шошго (жишээлбэл, салбарын нэр). Ингэснээр бид яг тохиргоог тодорхой тодорхойлж чадна. Тохиргооны тодорхойлогч бүр нь хуваарилагдсан зангилаа, порт, гадаад нөөц, номын сангийн хувилбаруудын тодорхой хослолтой нийцдэг. Энэ нийтлэлийн зорилгын үүднээс бид зөвхөн нэг салбар байна гэж үзэх бөгөөд бид цэгээр тусгаарлагдсан гурван тоог ашиглан тохиргоог ердийн аргаар тодорхойлж болно (1.2.3).

Орчин үеийн орчинд тохиргооны файлуудыг гараар үүсгэх нь ховор байдаг. Ихэнхдээ тэдгээр нь байршуулах явцад үүсдэг бөгөөд цаашид хөндөгдөөгүй (ингэснээр юу ч бүү эвд). Байгалийн асуулт гарч ирнэ: яагаад бид тохиргоог хадгалахын тулд текст форматыг ашигладаг хэвээр байна вэ? Боломжит хувилбар бол тохиргоонд зориулж ердийн кодыг ашиглах, эмхэтгэх цагийг шалгах чадвар юм.

Энэ нийтлэлд бид эмхэтгэсэн олдвор доторх тохиргоог төлөөлөх санааг судлах болно.

Эмхэтгэсэн тохиргоо

Энэ хэсэгт статик хөрвүүлсэн тохиргооны жишээг үзүүлэв. Хоёр энгийн үйлчилгээ хэрэгждэг - цуурай үйлчилгээ болон цуурай үйлчилгээний үйлчлүүлэгч. Эдгээр хоёр үйлчилгээн дээр үндэслэн системийн хоёр сонголтыг угсардаг. Нэг хувилбарт хоёр үйлчилгээ нь нэг зангилаа дээр, өөр сонголтоор өөр өөр зангилаа дээр байрладаг.

Ерөнхийдөө тархсан систем нь хэд хэдэн зангилаа агуулдаг. Та зарим төрлийн утгыг ашиглан зангилааг тодорхойлж болно NodeId:

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

буюу

case class NodeId(hostName: String)

эсвэл бүр

object Singleton
type NodeId = Singleton.type

Зангилаанууд нь янз бүрийн үүрэг гүйцэтгэдэг, үйлчилгээ эрхэлдэг бөгөөд тэдгээрийн хооронд TCP/HTTP холболтыг байгуулж болно.

TCP холболтыг тайлбарлахын тулд бидэнд дор хаяж портын дугаар хэрэгтэй. Үйлчлүүлэгч болон сервер хоёулаа ижил протокол ашиглаж байгаа эсэхийг баталгаажуулахын тулд бид тухайн порт дээр дэмжигдсэн протоколыг тусгамаар байна. Бид дараах классыг ашиглан холболтыг тайлбарлах болно.

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

хаана Port - зүгээр л бүхэл тоо Int Зөвшөөрөгдөх утгуудын хүрээг заана:

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

Сайжруулсан төрлүүд

Номын санг үзнэ үү цэвэршүүлсэн и миний тайлан. Товчхондоо, номын сан нь хөрвүүлэх үед шалгагдсан төрлүүдэд хязгаарлалт нэмэх боломжийг олгодог. Энэ тохиолдолд портын дугаарын хүчинтэй утга нь 16 битийн бүхэл тоо юм. Эмхэтгэсэн тохиргооны хувьд боловсронгуй номын санг ашиглах нь заавал байх албагүй ч хөрвүүлэгчийн тохиргоог шалгах чадварыг сайжруулдаг.

HTTP (REST) ​​протоколуудын хувьд портын дугаараас гадна үйлчилгээний зам хэрэгтэй болно:

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

Phantom төрлүүд

Эмхэтгэх үед протоколыг тодорхойлохын тулд бид ангид ашиглагддаггүй төрлийн параметрийг ашигладаг. Энэ шийдвэр нь бид ажиллах үед протоколын жишээ ашигладаггүйтэй холбоотой боловч хөрвүүлэгчээс протоколын нийцтэй байдлыг шалгахыг хүсч байна. Протоколыг зааж өгснөөр бид зохисгүй үйлчилгээг хараат байдлаар дамжуулах боломжгүй болно.

Нийтлэг протоколуудын нэг нь Json цувралжуулалттай REST API юм:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

хаана RequestMessage - хүсэлтийн төрөл, ResponseMessage - хариултын төрөл.
Мэдээжийн хэрэг, бид шаардлагатай тодорхойлолтын нарийвчлалыг хангах бусад протоколын тайлбарыг ашиглаж болно.

Энэ нийтлэлийн зорилгоор бид протоколын хялбаршуулсан хувилбарыг ашиглах болно:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Энд хүсэлт нь url-д хавсаргасан тэмдэгт мөр бөгөөд хариу нь HTTP хариултын үндсэн хэсэгт буцаж ирсэн мөр юм.

Үйлчилгээний тохиргоог үйлчилгээний нэр, портууд болон хамаарлаар тайлбарладаг. Эдгээр элементүүдийг Скала дээр хэд хэдэн аргаар төлөөлж болно (жишээлбэл, HList-s, алгебрийн өгөгдлийн төрлүүд). Энэ нийтлэлийн зорилгын үүднээс бид бялууны загварыг ашиглаж, модулиудыг ашиглана trait'ov. (Бялууны загвар нь энэ аргын зайлшгүй элемент биш юм. Энэ нь зүгээр л нэг боломжит хэрэгжилт юм.)

Үйлчилгээ хоорондын хамаарлыг портуудыг буцаах аргуудаар илэрхийлж болно EndPointбусад зангилааны 's:

  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)
  }

Цуурай үйлчилгээг бий болгохын тулд портын дугаар болон порт цуурай протоколыг дэмждэг болохыг харуулахад л хангалттай. Бид тодорхой порт зааж өгөхгүй байж магадгүй, учир нь... шинж чанарууд нь хэрэгжүүлэхгүйгээр аргуудыг тунхаглах боломжийг олгодог (хийсвэр аргууд). Энэ тохиолдолд тодорхой тохиргоог үүсгэх үед хөрвүүлэгч биднээс хийсвэр аргын хэрэгжилтийг хангах, портын дугаар өгөхийг шаардах болно. Бид энэ аргыг хэрэгжүүлсэн тул тодорхой тохиргоог хийхдээ өөр порт зааж өгөхгүй байж магадгүй юм. Анхдагч утгыг ашиглана.

Үйлчлүүлэгчийн тохиргоонд бид цуурай үйлчилгээнээс хамааралтай болохыг зарладаг:

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

Хараат байдал нь экспортолсон үйлчилгээтэй ижил төрлийнх юм echoService. Ялангуяа echo клиент дээр бид ижил протоколыг шаарддаг. Тиймээс, хоёр үйлчилгээг холбохдоо бүх зүйл зөв ажиллах болно гэдэгт итгэлтэй байж болно.

Үйлчилгээний хэрэгжилт

Үйлчилгээг эхлүүлэх, зогсооход функц шаардлагатай. (Үйлчилгээг зогсоох чадвар нь туршилт хийхэд маш чухал юм.) Дахин хэлэхэд ийм функцийг хэрэгжүүлэх хэд хэдэн сонголт байдаг (жишээлбэл, бид тохиргооны төрлөөс хамааран төрлийн ангиллыг ашиглаж болно). Энэ нийтлэлийн зорилгоор бид бялууны загварыг ашиглах болно. Бид анги ашиглан үйлчилгээг төлөөлөх болно cats.Resource, учир нь Энэ анги нь асуудал гарсан тохиолдолд нөөцийг аюулгүйгээр чөлөөлөх боломжийг олгодог. Нөөц авахын тулд бид тохиргоо болон бэлэн ажиллах цагийн контекстийг өгөх хэрэгтэй. Үйлчилгээг эхлүүлэх функц нь дараах байдлаар харагдаж болно.

  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]
  }

хаана

  • Config — энэ үйлчилгээний тохиргооны төрөл
  • AddressResolver - бусад зангилааны хаягийг олох боломжийг олгодог ажлын цагийн объект (доороос үзнэ үү)

номын сангийн бусад төрлүүд cats:

  • F[_] - нөлөөний төрөл (хамгийн энгийн тохиолдолд F[A] зүгээр л функц байж болно () => A. Энэ нийтлэлд бид ашиглах болно cats.IO.)
  • Reader[A,B] - функцтэй их бага ижил утгатай A => B
  • cats.Resource - олж авах, гаргах боломжтой нөөц
  • Timer — таймер (хэсэг хугацаанд унтаж, цагийн интервалыг хэмжих боломжийг танд олгоно)
  • ContextShift - аналог ExecutionContext
  • Applicative - бие даасан эффектүүдийг хослуулах боломжийг олгодог эффектийн төрлийн анги (бараг монад). Илүү төвөгтэй програмуудад үүнийг ашиглах нь илүү дээр юм шиг санагддаг Monad/ConcurrentEffect.

Энэ функцийн гарын үсгийг ашиглан бид хэд хэдэн үйлчилгээг хэрэгжүүлж чадна. Жишээлбэл, юу ч хийдэггүй үйлчилгээ:

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

(см. эх сурвалж, бусад үйлчилгээнүүд хэрэгждэг - цуурай үйлчилгээ, echo үйлчлүүлэгч
и насан туршийн хянагч.)

Зангилаа нь хэд хэдэн үйлчилгээг эхлүүлэх боломжтой объект юм (нөөцийн сүлжээг эхлүүлэх нь бялууны загвараар хангагдсан байдаг):

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

Бид энэ зангилаанд шаардлагатай тохиргооны төрлийг яг таг зааж байгааг анхаарна уу. Хэрэв бид тодорхой үйлчилгээнд шаардлагатай тохиргооны төрлүүдийн аль нэгийг зааж өгөхөө мартвал эмхэтгэлд алдаа гарна. Мөн бид зохих төрлийн объектыг шаардлагатай бүх мэдээллээр хангахгүй бол зангилаа эхлүүлэх боломжгүй болно.

Хост нэрийн шийдэл

Алсын хосттой холбогдохын тулд бидэнд жинхэнэ IP хаяг хэрэгтэй. Энэ хаяг нь бусад тохиргооноос хожуу тодорхой болох магадлалтай. Тиймээс бидэнд зангилааны ID-г хаягаар буулгах функц хэрэгтэй байна:

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

Энэ функцийг хэрэгжүүлэх хэд хэдэн арга байдаг:

  1. Хэрэв хаягууд нь байршуулахаас өмнө бидэнд мэдэгдвэл бид Скала кодыг үүсгэж болно
    хаягуудыг оруулаад дараа нь угсралтыг ажиллуул. Энэ нь тестүүдийг эмхэтгэж ажиллуулна.
    Энэ тохиолдолд функц нь статик байдлаар мэдэгдэх бөгөөд кодонд зураглал хэлбэрээр дүрслэгдэх боломжтой Map[NodeId, NodeAddress].
  2. Зарим тохиолдолд жинхэнэ хаяг нь зангилаа эхэлсний дараа л мэдэгддэг.
    Энэ тохиолдолд бид бусад зангилааны өмнө ажилладаг "нээлт хийх үйлчилгээ"-г хэрэгжүүлж болох бөгөөд бүх зангилаа энэ үйлчилгээнд бүртгүүлж, бусад зангилааны хаягийг хүсэх болно.
  3. Хэрэв бид өөрчилж чадвал /etc/hosts, дараа нь та урьдчилан тодорхойлсон хост нэрийг ашиглаж болно (жишээ нь my-project-main-node и echo-backend) мөн эдгээр нэрийг зүгээр л холбоно уу
    байршуулах явцад IP хаягуудтай.

Энэ нийтлэлд бид эдгээр тохиолдлуудыг илүү нарийвчлан авч үзэхгүй. Бидний хувьд
тоглоомын жишээнд бүх зангилаа ижил IP хаягтай байх болно - 127.0.0.1.

Дараа нь бид тархсан системийн хоёр сонголтыг авч үзье.

  1. Бүх үйлчилгээг нэг цэг дээр байрлуулах.
  2. Мөн цуурай үйлчилгээ болон echo үйлчлүүлэгчийг өөр өөр зангилаанууд дээр байршуулах.

Тохиргоо нэг зангилаа:

Нэг зангилааны тохиргоо

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.
}

Объект нь үйлчлүүлэгч болон серверийн тохиргоог хэрэгжүүлдэг. Амьдрах цаг тохиргоог мөн ашигладаг бөгөөд ингэснээр интервалын дараа lifetime програмыг дуусгах. (Ctrl-C нь мөн бүх нөөцийг зөв ажиллуулж, чөлөөлдөг.)

-ээс бүрдсэн системийг бий болгохын тулд ижил төрлийн тохиргоо, хэрэгжүүлэх шинж чанаруудыг ашиглаж болно хоёр тусдаа зангилаа:

Хоёр зангилааны тохиргоо

  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"
  }

Чухал! Үйлчилгээнүүд хэрхэн холбогдож байгааг анхаарч үзээрэй. Бид нэг зангилаагаар хэрэгжүүлсэн үйлчилгээг өөр зангилааны хамаарлын аргын хэрэгжилт гэж тодорхойлдог. Хамаарлын төрлийг хөрвүүлэгч шалгадаг, учир нь протоколын төрлийг агуулдаг. Ажиллуулах үед хамаарал нь зөв зорилтот зангилааны ID-г агуулна. Энэ схемийн ачаар бид портын дугаарыг яг нэг удаа зааж өгсөн бөгөөд үргэлж зөв порт руу хандах баталгаатай байдаг.

Хоёр системийн зангилааны хэрэгжилт

Энэ тохиргооны хувьд бид ижил үйлчилгээний хэрэгжилтийг өөрчлөхгүйгээр ашигладаг. Цорын ганц ялгаа нь бид одоо өөр өөр үйлчилгээний багцыг хэрэгжүүлдэг хоёр объекттой болсон явдал юм.

  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
  }

Эхний зангилаа нь серверийг хэрэгжүүлдэг бөгөөд зөвхөн серверийн тохиргоо хэрэгтэй. Хоёр дахь зангилаа нь үйлчлүүлэгчийг хэрэгжүүлж, тохиргооны өөр хэсгийг ашигладаг. Мөн хоёр зангилаанд насан туршийн менежмент хэрэгтэй. Серверийн зангилаа зогсох хүртэл тодорхойгүй хугацаагаар ажиллана SIGTERM'om, мөн үйлчлүүлэгчийн зангилаа хэсэг хугацааны дараа дуусна. см. эхлүүлэгч програм.

Хөгжлийн ерөнхий үйл явц

Энэхүү тохиргооны арга нь хөгжлийн ерөнхий үйл явцад хэрхэн нөлөөлж байгааг харцгаая.

Тохиргоог бусад кодын хамт эмхэтгэх ба олдвор (.jar) үүсгэгдэнэ. Тохиргоог тусдаа олдворт оруулах нь утга учиртай юм шиг санагддаг. Учир нь бид нэг код дээр суурилсан олон тохиргоотой байж болно. Дахин хэлэхэд, янз бүрийн тохиргооны салбаруудад тохирох олдворуудыг үүсгэх боломжтой. Номын сангийн тодорхой хувилбаруудын хамаарлыг тохиргооны хамт хадгалдаг бөгөөд бид тохиргооны тэр хувилбарыг ашиглахаар шийдсэн үед эдгээр хувилбарууд үүрд хадгалагдана.

Аливаа тохиргооны өөрчлөлт нь кодын өөрчлөлт болж хувирдаг. Тиймээс, тус бүр
өөрчлөлтийг чанарын хэвийн баталгаажуулалтын процесст хамруулна:

Алдаа мөрдөгч доторх тасалбар -> PR -> тойм -> холбогдох салбаруудтай нэгтгэх ->
нэгтгэх -> байршуулалт

Эмхэтгэсэн тохиргоог хэрэгжүүлэх үндсэн үр дагавар нь:

  1. Тохиргоо нь тархсан системийн бүх зангилаанд нийцсэн байх болно. Бүх зангилаа нь нэг эх сурвалжаас ижил тохиргоог хүлээн авдаг тул.

  2. Зөвхөн нэг зангилааны тохиргоог өөрчлөх нь асуудалтай байдаг. Тиймээс "тохиргооны шилжилт" магадлал багатай.

  3. Тохиргоонд бага зэрэг өөрчлөлт оруулах нь илүү хэцүү болдог.

  4. Ихэнх тохиргооны өөрчлөлтүүд нь ерөнхий боловсруулалтын үйл явцын нэг хэсэг болох бөгөөд хянан үзэх болно.

Үйлдвэрлэлийн тохиргоог хадгалахын тулд надад тусдаа репозитор хэрэгтэй юу? Энэ тохиргоо нь бидний хандалтыг хязгаарлахыг хүссэн нууц үг болон бусад нууц мэдээллийг агуулж болзошгүй. Үүний үндсэн дээр эцсийн тохиргоог тусдаа агуулахад хадгалах нь утга учиртай юм шиг санагддаг. Та тохиргоог хоёр хэсэгт хувааж болно: нэг нь нийтэд нээлттэй тохиргооны тохиргоог агуулсан, нөгөө нь хязгаарлагдмал тохиргоог агуулсан. Энэ нь ихэнх хөгжүүлэгчид нийтлэг тохиргоонд хандах боломжийг олгоно. Анхдагч утгыг агуулсан завсрын шинж чанаруудыг ашиглан энэ тусгаарлалтыг хийхэд хялбар байдаг.

Боломжит хувилбарууд

Эмхэтгэсэн тохиргоог зарим нийтлэг хувилбаруудтай харьцуулахыг хичээцгээе:

  1. Зорилтот машин дээрх текст файл.
  2. Түлхүүр үнэ цэнийн төвлөрсөн дэлгүүр (etcd/zookeeper).
  3. Процессыг дахин эхлүүлэхгүйгээр дахин тохируулах/дахин эхлүүлэх боломжтой процессын бүрэлдэхүүн хэсгүүд.
  4. Олдвор болон хувилбарын хяналтаас гадуур тохиргоог хадгалах.

Текст файлууд нь жижиг өөрчлөлтүүдийн хувьд ихээхэн уян хатан байдлыг хангадаг. Системийн администратор нь алсын зангилаа руу нэвтэрч, тохирох файлд өөрчлөлт оруулж, үйлчилгээг дахин эхлүүлэх боломжтой. Харин том системүүдийн хувьд ийм уян хатан байдал нь хүсээгүй байж болно. Хийсэн өөрчлөлтүүд нь бусад системд ул мөр үлдээдэггүй. Өөрчлөлтийг хэн ч хянадаггүй. Чухам хэн, ямар шалтгаанаар өөрчлөлт оруулсныг тогтооход хэцүү. Өөрчлөлтүүдийг шалгадаггүй. Хэрэв систем тархсан бол администратор бусад зангилаанууд дээр холбогдох өөрчлөлтийг хийхээ мартаж магадгүй юм.

(Мөн эмхэтгэсэн тохиргоог ашиглах нь ирээдүйд текст файлуудыг ашиглах боломжийг хаадаггүй гэдгийг тэмдэглэх нь зүйтэй. Гаралттай ижил төрлийг гаргадаг задлан шинжлэгч болон баталгаажуулагчийг нэмэхэд хангалттай. Config, мөн та текст файлуудыг ашиглаж болно. Эндээс харахад эмхэтгэсэн тохиргоотой системийн нарийн төвөгтэй байдал нь текст файлуудыг ашигладаг системийн нарийн төвөгтэй байдлаас арай бага байдаг, учир нь текст файлд нэмэлт код шаардлагатай.)

Түлхүүр үнэ цэнийн төвлөрсөн дэлгүүр нь тархсан програмын мета параметрүүдийг түгээх сайн механизм юм. Бид тохиргооны параметрүүд гэж юу болох, зөвхөн өгөгдөл гэж юу болохыг шийдэх хэрэгтэй. Бидэнд функцтэй байцгаая C => A => B, болон параметрүүд C ховор өөрчлөгддөг, өгөгдөл A - ихэвчлэн. Энэ тохиолдолд бид үүнийг хэлж чадна C - тохиргооны параметрүүд, ба A - өгөгдөл. Тохиргооны параметрүүд нь өгөгдлөөс ерөнхийдөө өгөгдлөөс бага өөрчлөгддөгөөрөө ялгаатай юм шиг харагдаж байна. Мөн өгөгдөл нь ихэвчлэн нэг эх сурвалжаас (хэрэглэгчээс), тохиргооны параметрүүдийг өөр эх сурвалжаас (системийн администратороос) авдаг.

Хэрэв програмыг дахин эхлүүлэхгүйгээр ховор өөрчлөгддөг параметрүүдийг шинэчлэх шаардлагатай бол энэ нь ихэвчлэн програмын хүндрэлд хүргэдэг, учир нь бид ямар нэгэн байдлаар параметрүүдийг хүргэх, хадгалах, задлан шинжлэх, шалгах, буруу утгыг боловсруулах шаардлагатай болно. Тиймээс, програмын нарийн төвөгтэй байдлыг багасгах үүднээс програмын үйл ажиллагааны явцад өөрчлөгдөж болох параметрүүдийн тоог багасгах нь зүйтэй (эсвэл ийм параметрүүдийг огт дэмждэггүй).

Энэ нийтлэлийн зорилгын үүднээс бид статик болон динамик параметрүүдийг ялгах болно. Хэрэв үйлчилгээний логик нь програмыг ажиллуулах явцад параметрүүдийг өөрчлөх шаардлагатай бол бид ийм параметрүүдийг динамик гэж нэрлэнэ. Үгүй бол сонголтууд нь статик бөгөөд эмхэтгэсэн тохиргоог ашиглан тохируулж болно. Динамик дахин тохируулахын тулд үйлдлийн системийн процессуудыг хэрхэн дахин эхлүүлдэгтэй адил шинэ параметрүүдээр програмын хэсгүүдийг дахин эхлүүлэх механизм хэрэгтэй байж магадгүй. (Бидний бодлоор энэ нь системийн нарийн төвөгтэй байдлыг нэмэгдүүлдэг тул бодит цагийн тохиргооноос зайлсхийхийг зөвлөж байна. Боломжтой бол үйл явцыг дахин эхлүүлэхийн тулд үйлдлийн системийн стандарт боломжуудыг ашиглах нь дээр.)

Хүмүүсийг динамик дахин тохируулах талаар бодоход хүргэдэг статик тохиргоог ашиглах нэг чухал тал бол тохиргооны шинэчлэлт хийсний дараа системийг дахин ачаалахад шаардагдах хугацаа юм (сул зогсолт). Үнэн хэрэгтээ, хэрэв бид статик тохиргоонд өөрчлөлт оруулах шаардлагатай бол шинэ утгууд хүчин төгөлдөр болохын тулд системийг дахин эхлүүлэх шаардлагатай болно. Сул зогсолтын асуудал нь янз бүрийн системүүдийн хувьд өөр өөр байдаг. Зарим тохиолдолд ачаалал бага байх үед та дахин ачаалах хуваарь гаргаж болно. Тасралтгүй үйлчилгээ үзүүлэх шаардлагатай бол хэрэгжүүлж болно AWS ELB холболтын ус зайлуулах. Үүний зэрэгцээ, системийг дахин ачаалах шаардлагатай үед бид энэ системийн зэрэгцээ жишээг ажиллуулж, тэнцвэржүүлэгчийг түүн рүү шилжүүлж, хуучин холболтууд дуусахыг хүлээнэ. Бүх хуучин холболтууд дууссаны дараа бид системийн хуучин хувилбарыг хаадаг.

Одоо олдвор дотор эсвэл гадна талд тохиргоог хадгалах асуудлыг авч үзье. Хэрэв бид тохиргоог олдвор дотор хадгалдаг бол ядаж л олдворыг угсрах явцад тохиргооны зөв эсэхийг шалгах боломж олдсон. Хэрэв тохиргоо нь хяналттай олдворын гадна байгаа бол энэ файлд хэн, яагаад өөрчлөлт оруулсныг хянахад хэцүү байдаг. Энэ нь хэр чухал вэ? Бидний бодлоор олон үйлдвэрлэлийн системүүдийн хувьд тогтвортой, өндөр чанартай тохиргоотой байх нь чухал юм.

Олдворын хувилбар нь түүнийг хэзээ үүсгэсэн, ямар утгыг агуулж байгаа, ямар функц идэвхжсэн/идэвхгүй, тохиргооны өөрчлөлтийг хэн хариуцах зэргийг тодорхойлох боломжийг олгодог. Мэдээжийн хэрэг, олдвор дотор тохиргоог хадгалах нь тодорхой хүчин чармайлт шаарддаг тул та мэдээлэлтэй шийдвэр гаргах хэрэгтэй.

Сайн болон сул талууд

Санал болгож буй технологийн давуу болон сул талуудын талаар ярихыг хүсч байна.

Ашиг тус

Эмхэтгэсэн тархсан системийн тохиргооны үндсэн шинж чанаруудын жагсаалтыг доор харуулав.

  1. Статик тохиргоог шалгах. Үүнд итгэлтэй байх боломжийг танд олгоно
    тохиргоо зөв байна.
  2. Баян тохиргооны хэл. Ихэвчлэн бусад тохиргооны аргууд нь дээд тал нь мөрийн хувьсагчийг орлуулахаар хязгаарлагддаг. Scala-г ашиглах үед таны тохиргоог сайжруулахын тулд олон төрлийн хэлний боломжуудыг ашиглах боломжтой. Жишээлбэл, бид ашиглаж болно
    өгөгдмөл утгуудын шинж чанарууд, параметрүүдийг бүлэглэхийн тулд объектыг ашиглан бид хавсаргасан хамрах хүрээнд зөвхөн нэг удаа (ХУУРАЙ) зарласан vals-д хандаж болно. Та тохиргоон дотроос дурын ангийг шууд үүсгэж болно (Seq, Map, захиалгат ангиуд).
  3. DSL. Scala нь хэд хэдэн хэлний онцлогтой бөгөөд DSL үүсгэхэд хялбар болгодог. Эдгээр боломжуудын давуу талыг ашиглаж, зорилтот бүлгийн хэрэглэгчдэд илүү тохиромжтой тохиргооны хэлийг хэрэгжүүлэх боломжтой бөгөөд ингэснээр тохиргоог дор хаяж домэйны мэргэжилтнүүд унших боломжтой болно. Жишээлбэл, мэргэжилтнүүд тохиргоог шалгах үйл явцад оролцож болно.
  4. Зангилаа хоорондын бүрэн бүтэн байдал ба синхрончлол. Бүхэл бүтэн тархсан системийн тохиргоог нэг цэгт хадгалахын нэг давуу тал нь бүх утгыг яг нэг удаа зарлаж, дараа нь шаардлагатай газар дахин ашиглах явдал юм. Портуудыг зарлахдаа хийсвэр төрлүүдийг ашиглах нь зангилаанууд системийн бүх зөв тохиргоонд нийцтэй протоколуудыг ашиглаж байгаа эсэхийг баталгаажуулдаг. Зангилааны хооронд тодорхой хамааралтай байх нь бүх үйлчилгээ холбогдсон байх баталгаа болдог.
  5. Өндөр чанартай өөрчлөлтүүд. Түгээмэл боловсруулах процессыг ашиглан тохиргоонд өөрчлөлт оруулах нь тохиргооны чанарын өндөр стандартад хүрэх боломжийг олгодог.
  6. Тохиргоог нэгэн зэрэг шинэчлэх. Тохиргоог өөрчилсний дараа системийг автоматаар байршуулснаар бүх зангилаа шинэчлэгдэх болно.
  7. Програмыг хялбарчлах. Аппликешн нь задлан шинжлэх, тохиргоог шалгах, буруу утгыг боловсруулах шаардлагагүй. Энэ нь хэрэглээний нарийн төвөгтэй байдлыг бууруулдаг. (Бидний жишээн дээр ажиглагдсан зарим тохиргооны нарийн төвөгтэй байдал нь эмхэтгэсэн тохиргооны шинж чанар биш, харин зөвхөн илүү сайн төрлийн аюулгүй байдлыг хангах хүсэл эрмэлзлээс үүдэлтэй ухамсартай шийдвэр юм.) Ердийн тохиргоо руу буцах нь маш амархан - дутуу байгаа зүйлийг л хэрэгжүүлэхэд л хангалттай. хэсгүүд. Тиймээс, жишээлбэл, шаардлагагүй хэсгүүдийн хэрэгжилтийг үнэхээр шаардлагатай болтол хойшлуулж, эмхэтгэсэн тохиргооноос эхэлж болно.
  8. Баталгаажсан тохиргоо. Тохиргооны өөрчлөлтүүд нь бусад өөрчлөлтүүдийн ердийн хувь заяаг дагадаг тул бидний олж авсан гаралт нь өвөрмөц хувилбартай олдвор юм. Энэ нь жишээлбэл, шаардлагатай бол тохиргооны өмнөх хувилбар руу буцах боломжийг бидэнд олгодог. Бид бүр жилийн өмнөх тохиргоог ашиглаж болох бөгөөд систем яг адилхан ажиллах болно. Тогтвортой тохиргоо нь тархсан системийн урьдчилан таамаглах, найдвартай байдлыг сайжруулдаг. Тохиргоо нь эмхэтгэлийн үе шатанд тогтоогдсон тул үйлдвэрлэлд хуурамчаар үйлдэх нь нэлээд хэцүү байдаг.
  9. Модульчлал. Санал болгож буй хүрээ нь модульчлагдсан бөгөөд модулиудыг янз бүрийн аргаар нэгтгэж, өөр өөр системийг бий болгож болно. Ялангуяа та системийг нэг хувилбарт нэг зангилаа, нөгөө хувилбарт олон зангилаа дээр ажиллуулахаар тохируулж болно. Та системийн үйлдвэрлэлийн жишээнүүдийн хэд хэдэн тохиргоог үүсгэж болно.
  10. Туршилт хийх. Бие даасан үйлчилгээг хуурамч объектоор сольсноор та тест хийхэд тохиромжтой системийн хэд хэдэн хувилбарыг авах боломжтой.
  11. Интеграцийн туршилт. Бүхэл бүтэн тархсан системийн нэг тохиргоотой байх нь интеграцийн туршилтын нэг хэсэг болгон бүх бүрэлдэхүүн хэсгүүдийг хяналттай орчинд ажиллуулах боломжтой болгодог. Жишээ нь, зарим зангилаа хандах боломжтой болсон нөхцөл байдлыг дуурайхад хялбар байдаг.

Сул тал ба хязгаарлалт

Эмхэтгэсэн тохиргоо нь бусад тохиргооны аргуудаас ялгаатай бөгөөд зарим програмуудад тохиромжгүй байж болно. Зарим сул талуудыг доор харуулав.

  1. Статик тохиргоо. Заримдаа та бүх хамгаалалтын механизмыг алгасаж, үйлдвэрлэлийн тохиргоог хурдан засах хэрэгтэй. Энэ аргын хувьд илүү хэцүү байж болно. Наад зах нь эмхэтгэл, автомат байршуулалт шаардлагатай хэвээр байх болно. Энэ нь аргын ашигтай шинж чанар, зарим тохиолдолд сул тал юм.
  2. Тохиргоо үүсгэх. Хэрэв тохиргооны файлыг автомат хэрэгслээр үүсгэсэн бол бүтээх скриптийг нэгтгэхийн тулд нэмэлт хүчин чармайлт шаардагдана.
  3. Багаж хэрэгсэл. Одоогоор тохиргоотой ажиллахад зориулагдсан хэрэгслүүд, техникүүд нь текст файлууд дээр суурилдаг. Ийм бүх хэрэгсэл/техникийг эмхэтгэсэн тохиргоонд ашиглах боломжгүй.
  4. Хандлагаа өөрчлөх шаардлагатай. Хөгжүүлэгчид болон DevOps нь текст файлд дассан байдаг. Тохиргоог эмхэтгэх санаа нь зарим талаараа гэнэтийн, ер бусын байж магадгүй бөгөөд татгалзах шалтгаан болдог.
  5. Өндөр чанартай хөгжүүлэлтийн процесс шаардлагатай. Эмхэтгэсэн тохиргоог ая тухтай ашиглахын тулд програмыг (CI/CD) бүтээх, байрлуулах үйл явцыг бүрэн автоматжуулах шаардлагатай. Үгүй бол энэ нь нэлээд эвгүй байх болно.

Мөн эмхэтгэсэн тохиргооны санаатай холбоогүй авч үзсэн жишээний хэд хэдэн хязгаарлалтыг авч үзье.

  1. Хэрэв бид зангилаанд ашиглагдаагүй тохиргооны мэдээллийг өгвөл хөрвүүлэгч нь дутуу хэрэгжүүлэлтийг илрүүлэхэд тус болохгүй. Бялууны загвараас татгалзаж, илүү хатуу төрлийг ашиглах замаар энэ асуудлыг шийдэж болно, жишээлбэл, HList эсвэл тохиргоог илэрхийлэх алгебрийн өгөгдлийн төрлүүд (кейс ангиуд).
  2. Тохиргооны файлд тохиргоотой холбоогүй мөрүүд байна: (package, import,объектуудын мэдүүлэг; override def's нь анхдагч утгатай параметрүүдийн хувьд). Хэрэв та өөрийн DSL-г хэрэгжүүлбэл үүнээс хэсэгчлэн зайлсхийх боломжтой. Үүнээс гадна бусад төрлийн тохиргоо (жишээлбэл, XML) нь файлын бүтцэд тодорхой хязгаарлалт тавьдаг.
  3. Энэ нийтлэлийн зорилгын үүднээс бид ижил төстэй зангилааны кластерын динамик тохиргоог авч үзэхгүй.

дүгнэлт

Энэ нийтлэлд бид Scala төрлийн системийн дэвшилтэт чадамжийг ашиглан эх код дахь тохиргоог төлөөлөх санааг судалсан. Энэ аргыг xml эсвэл текст файл дээр суурилсан уламжлалт тохиргооны аргуудыг орлуулах болгон янз бүрийн програмуудад ашиглаж болно. Бидний жишээг Скала дээр хэрэгжүүлсэн ч гэсэн ижил санааг бусад хөрвүүлсэн хэл рүү (Kotlin, C#, Swift, ... гэх мэт) шилжүүлж болно. Та энэ аргыг дараах төслүүдийн аль нэгэнд туршиж үзэх боломжтой бөгөөд хэрэв энэ нь ажиллахгүй бол текст файл руу шилжиж, дутуу хэсгүүдийг нэмж оруулаарай.

Мэдээжийн хэрэг, эмхэтгэсэн тохиргоо нь өндөр чанартай боловсруулах үйл явцыг шаарддаг. Үүний хариуд тохиргооны өндөр чанар, найдвартай байдлыг хангана.

Үзэж буй хандлагыг өргөжүүлж болно:

  1. Та эмхэтгэх цагийн шалгалтыг хийхийн тулд макро ашиглаж болно.
  2. Та эцсийн хэрэглэгчдэд хүртээмжтэй байдлаар тохиргоог үзүүлэхийн тулд DSL-г хэрэгжүүлж болно.
  3. Та тохиргооны автомат тохируулгатай динамик нөөцийн удирдлагыг хэрэгжүүлэх боломжтой. Жишээлбэл, кластер дахь зангилааны тоог өөрчлөх нь (1) зангилаа бүр арай өөр тохиргоог хүлээн авах; (2) кластер менежер шинэ зангилааны талаар мэдээлэл хүлээн авсан.

Талархал

Андрей Саксонов, Павел Попов, Антон Нехаев нарт төслийн нийтлэлийг бүтээлчээр шүүмжилсэнд талархал илэрхийлье.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх