Құрастырылған үлестірілген жүйе конфигурациясы

Мен сізге бөлінген жүйенің конфигурациясымен жұмыс істеудің бір қызықты механизмін айтқым келеді. Конфигурация қауіпсіз түрлерді пайдаланып тікелей құрастырылған тілде (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)

Фантомдық түрлері

Протоколды компиляция уақытында анықтау үшін біз сынып ішінде пайдаланылмайтын тип параметрін қолданамыз. Бұл шешім орындалу уақытында протокол данасын пайдаланбауымызға байланысты, бірақ компилятордың протокол үйлесімділігін тексеруін қалаймыз. Протоколды көрсету арқылы біз сәйкес емес қызметті тәуелділік ретінде өткізе алмаймыз.

Жалпы протоколдардың бірі Json сериялануы бар REST API болып табылады:

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

қайда RequestMessage - сұраныс түрі, ResponseMessage - жауап түрі.
Әрине, біз талап ететін сипаттаманың дәлдігін қамтамасыз ететін басқа протоколдық сипаттамаларды пайдалана аламыз.

Осы жазбаның мақсаттары үшін біз хаттаманың жеңілдетілген нұсқасын қолданамыз:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Мұнда сұрау url мекенжайына қосылған жол және жауап HTTP жауабының мәтініндегі қайтарылған жол болып табылады.

Қызмет конфигурациясы қызмет атауы, порттар және тәуелділіктер арқылы сипатталады. Бұл элементтерді Scala-да бірнеше жолмен көрсетуге болады (мысалы, HList-s, алгебралық деректер түрлері). Осы посттың мақсаттары үшін біз торт үлгісін қолданамыз және модульдерді қолданамыз trait'ov. (Торт үлгісі бұл тәсілдің міндетті элементі емес. Бұл жай ғана мүмкін болатын іске асыру.)

Қызметтер арасындағы тәуелділік порттарды қайтаратын әдістер ретінде ұсынылуы мүмкін EndPointбасқа түйіндердің:

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

Эхо қызметін жасау үшін сізге тек порт нөмірі және порттың эхо протоколын қолдайтынының көрсеткіші қажет. Біз нақты портты көрсетпеуіміз мүмкін, себебі... белгілер іске асырусыз әдістерді жариялауға мүмкіндік береді (абстрактілі әдістер). Бұл жағдайда нақты конфигурацияны жасау кезінде компилятор абстрактілі әдісті жүзеге асыруды және порт нөмірін беруді талап етеді. Біз әдісті іске асырғандықтан, белгілі бір конфигурацияны жасау кезінде басқа портты көрсетпеуіміз мүмкін. Әдепкі мән пайдаланылады.

Клиент конфигурациясында біз echo қызметіне тәуелділікті жариялаймыз:

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

(См. қайнар көзі, онда басқа қызметтер жүзеге асырылады - эхо қызметі, жаңғырық клиенті
и өмірлік контроллерлер.)

Түйін - бұл бірнеше қызметтерді іске қоса алатын объект (ресурстар тізбегін іске қосу Cake Pattern арқылы қамтамасыз етіледі):

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

Осы түйін үшін қажет конфигурацияның нақты түрін көрсетіп жатқанымызды ескеріңіз. Белгілі бір қызметке қажет конфигурация түрлерінің бірін көрсетуді ұмытып қалсақ, компиляция қатесі болады. Сондай-ақ, біз барлық қажетті деректермен сәйкес типтегі кейбір нысанды бермейінше, түйінді іске қоса алмаймыз.

Хост атауының рұқсаты

Қашықтағы хостқа қосылу үшін бізге нақты IP мекенжайы қажет. Мекенжай конфигурацияның қалған бөлігінен кейін белгілі болуы мүмкін. Сондықтан бізге түйін идентификаторын мекенжайға салыстыратын функция қажет:

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

Бұл функцияны жүзеге асырудың бірнеше жолы бар:

  1. Егер мекенжайлар орналастыру алдында бізге белгілі болса, онда біз Scala кодын жасай аламыз
    мекенжайларды таңдап, құрастыруды іске қосыңыз. Бұл сынақтарды құрастырады және іске қосады.
    Бұл жағдайда функция статикалық түрде белгілі болады және кодта салыстыру ретінде ұсынылуы мүмкін Map[NodeId, NodeAddress].
  2. Кейбір жағдайларда нақты мекенжай түйін іске қосылғаннан кейін ғана белгілі болады.
    Бұл жағдайда біз басқа түйіндерден бұрын жұмыс істейтін «табу қызметін» жүзеге асыра аламыз және барлық түйіндер осы қызметке тіркеледі және басқа түйіндердің мекенжайларын сұрайды.
  3. Егер біз өзгерте алсақ /etc/hosts, содан кейін сіз алдын ала анықталған хост атауларын пайдалана аласыз (мысалы my-project-main-node и echo-backend) және жай ғана осы атауларды байланыстырыңыз
    орналастыру кезінде IP мекенжайларымен.

Бұл мақалада біз бұл жағдайларды толығырақ қарастырмаймыз. Біздің үшін
ойыншық мысалында барлық түйіндерде бірдей IP мекенжайы болады - 127.0.0.1.

Содан кейін біз бөлінген жүйенің екі нұсқасын қарастырамыз:

  1. Барлық қызметтерді бір түйінге орналастыру.
  2. Әртүрлі түйіндерде эхо қызметі мен жаңғырық клиентін орналастыру.

үшін конфигурация бір түйін:

Бір түйін конфигурациясы

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

Маңызды! Қызметтердің қалай байланыстырылғанына назар аударыңыз. Біз бір түйінмен жүзеге асырылатын қызметті басқа түйіннің тәуелділік әдісін іске асыру ретінде көрсетеміз. Тәуелділік түрін компилятор тексереді, себебі протокол түрін қамтиды. Іске қосылған кезде, тәуелділік дұрыс мақсатты түйін идентификаторын қамтиды. Осы схеманың арқасында біз порт нөмірін дәл бір рет көрсетеміз және әрқашан дұрыс портқа сілтеме жасауға кепілдік береміз.

Екі жүйелік түйінді жүзеге асыру

Бұл конфигурация үшін біз өзгеріссіз бірдей қызмет енгізулерін пайдаланамыз. Жалғыз айырмашылығы - қазір бізде әртүрлі қызметтер жиынтығын жүзеге асыратын екі нысан бар:

  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 пайдаланған кезде конфигурацияңызды жақсарту үшін тіл мүмкіндіктерінің кең ауқымы қолжетімді. Мысалы, біз пайдалана аламыз
    параметрлерді топтау үшін нысандарды пайдалана отырып, әдепкі мәндерге арналған белгілерді қоршау ауқымында тек бір рет (DRY) жарияланған мәндерге сілтеме жасай аламыз. Кез келген сыныптарды тікелей конфигурация ішінде жасауға болады (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әдепкі мәндері бар параметрлер үшін). Егер сіз өзіңіздің DSL-ді енгізсеңіз, мұны ішінара болдырмауға болады. Сонымен қатар, конфигурацияның басқа түрлері де (мысалы, XML) файл құрылымына белгілі бір шектеулер қояды.
  3. Осы жазбаның мақсаттары үшін біз ұқсас түйіндердің кластерін динамикалық қайта конфигурациялауды қарастырмаймыз.

қорытынды

Бұл мақалада біз Scala типті жүйенің кеңейтілген мүмкіндіктерін пайдалана отырып, бастапқы кодта конфигурацияны көрсету идеясын зерттедік. Бұл тәсіл әртүрлі қолданбаларда xml немесе мәтіндік файлдарға негізделген дәстүрлі конфигурация әдістерін ауыстыру ретінде қолданылуы мүмкін. Біздің мысал Scala-да жүзеге асырылса да, бірдей идеяларды басқа құрастырылған тілдерге (мысалы, Kotlin, C#, Swift, ...) беруге болады. Бұл тәсілді келесі жобалардың бірінде қолданып көруге болады, егер ол жұмыс істемесе, жетіспейтін бөліктерді қосып, мәтіндік файлға өтіңіз.

Әрине, құрастырылған конфигурация жоғары сапалы әзірлеу процесін талап етеді. Өз кезегінде конфигурациялардың жоғары сапасы мен сенімділігі қамтамасыз етіледі.

Қарастырылған тәсілді кеңейтуге болады:

  1. Компиляция уақытын тексеру үшін макростарды пайдалануға болады.
  2. Конфигурацияны соңғы пайдаланушылар үшін қолжетімді етіп көрсету үшін DSL енгізуге болады.
  3. Конфигурацияны автоматты түрде реттеу арқылы динамикалық ресурстарды басқаруды жүзеге асыруға болады. Мысалы, кластердегі түйіндер санын өзгерту (1) әрбір түйін сәл басқа конфигурацияны алуды талап етеді; (2) кластер менеджері жаңа түйіндер туралы ақпаратты алды.

Алғыс

Мен Андрей Саксоновқа, Павел Поповқа және Антон Нехаевқа мақала жобасына сындарлы сын айтқандары үшін алғыс айтқым келеді.

Ақпарат көзі: www.habr.com

пікір қалдыру