Бөлінген жүйенің компиляцияланатын конфигурациясы

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

Бөлінген жүйенің компиляцияланатын конфигурациясы

(орысша)

кіріспе

Күшті бөлінген жүйелерді құру барлық түйіндерде дұрыс және үйлесімді конфигурацияны пайдалануды талап етеді. Типтік шешім мәтіндік орналастыру сипаттамасын (терраформа, ansible немесе ұқсас нәрсе) және автоматты түрде жасалған конфигурация файлдарын (көбінесе әрбір түйін/рөлге арналған) пайдалану болып табылады. Біз сондай-ақ әрбір байланыс түйінінде бірдей нұсқалардың бірдей протоколдарын пайдаланғымыз келеді (әйтпесе үйлесімсіздік мәселелері туындайды). JVM әлемінде бұл кем дегенде хабар алмасу кітапханасы барлық байланыс түйіндерінде бірдей нұсқа болуы керек дегенді білдіреді.

Жүйені сынау туралы не деуге болады? Әрине, интеграциялық сынақтарға келмес бұрын бізде барлық компоненттер үшін бірлік сынақтары болуы керек. Орындалу уақытында сынақ нәтижелерін экстраполяциялау үшін біз барлық кітапханалардың нұсқалары орындалу уақытында да, сынақ орталарында да бірдей сақталуын қамтамасыз етуіміз керек.

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

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

Қазіргі орталарда конфигурация файлдары енді қолмен өзгертілмейді. Әдетте біз жасаймыз
конфигурациялау файлдарын орналастыру уақытында және оларға ешқашан тиіспеңіз кейін. Сонымен, біз конфигурация файлдары үшін неге әлі де мәтін пішімін пайдаланамыз? Тиімді опция конфигурацияны компиляция бірлігіне орналастыру және компиляция уақытының конфигурациясын тексеруден пайда алу болып табылады.

Бұл постта біз құрастырылған артефактта конфигурацияны сақтау идеясын қарастырамыз.

Құрастырылатын конфигурация

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

Әдеттегі бөлінген жүйе бірнеше түйіндерден тұрады. Түйіндерді қандай да бір түрді пайдаланып анықтауға болады:

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

Тазаланған түрлері

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

HTTP (REST) ​​үшін бізге қызмет жолы қажет болуы мүмкін:

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

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

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

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

sealed trait JsonHttpRestProtocol[RequestMessage, ResponseMessage]

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

Осы жазбаның мақсаттары үшін біз протоколдың қарапайым нұсқасын қолданамыз:

sealed trait SimpleHttpGetRest[RequestMessage, ResponseMessage]

Бұл хаттамада сұрау хабары url мекенжайына қосылады және жауап хабары қарапайым жол ретінде қайтарылады.

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

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

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

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

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

Тәуелділіктің түрі бар echoService. Атап айтқанда, дәл осындай хаттаманы талап етеді. Демек, егер біз осы екі тәуелділікті байланыстырсақ, олар дұрыс жұмыс істейтініне сенімді бола аламыз.

Қызметтерді жүзеге асыру

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

Бұл интерфейсті пайдалану арқылы біз бірнеше қызметтерді жүзеге асыра аламыз. Мысалы, ештеңе жасамайтын қызмет:

  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
}

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

Түйін мекенжайының рұқсаты

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

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

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

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

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

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

  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, ал echo клиенті конфигурацияланған соңғы ұзақтықтан кейін аяқталады. қараңыз стартер қолданбасы Толық ақпарат алу үшін.

Жалпы даму процесі

Бұл тәсіл конфигурациямен жұмыс істеу тәсілін қалай өзгертетінін көрейік.

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

Конфигурацияны өзгерту кодты өзгертуге айналады. Сондықтан ол бірдей сапаны қамтамасыз ету процесімен қамтылуы керек:

Билет -> PR -> шолу -> біріктіру -> үздіксіз интеграция -> үздіксіз орналастыру

Тәсілдің келесі салдары бар:

  1. Конфигурация белгілі бір жүйе данасы үшін үйлесімді. Түйіндер арасында дұрыс емес байланыстың болуы мүмкін емес сияқты.
  2. Бір түйінде конфигурацияны өзгерту оңай емес. Жүйеге кіру және кейбір мәтіндік файлдарды өзгерту қисынсыз сияқты. Осылайша конфигурацияның дрейфі азаяды.
  3. Кішігірім конфигурация өзгерістерін жасау оңай емес.
  4. Конфигурация өзгерістерінің көпшілігі бірдей әзірлеу процесінде болады және ол біраз тексеруден өтеді.

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

Вариациялар

Басқа конфигурацияларды басқару әдістерімен салыстырғанда ұсынылған тәсілдің оң және теріс жақтарын қарастырайық.

Ең алдымен, біз конфигурациямен жұмыс істеудің ұсынылған әдісінің әртүрлі аспектілеріне бірнеше баламаларды тізімдейміз:

  1. Мақсатты құрылғыдағы мәтіндік файл.
  2. Орталықтандырылған кілт-мәнді сақтау (мысалы etcd/zookeeper).
  3. Процесті қайта іске қоспай-ақ қайта конфигурациялауға/қайта іске қосуға болатын қосалқы процесс құрамдастары.
  4. Артефакт пен нұсқаны басқарудан тыс конфигурация.

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

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

Орталықтандырылған кілт-мәнді сақтау қолданбаның мета параметрлерін таратудың жақсы механизмі болып табылады. Бұл жерде біз конфигурация мәндері деп санайтын нәрселерді және жай деректер дегенді ойлауымыз керек. Функция берілген C => A => B біз әдетте сирек өзгеретін мәндерді атаймыз C деректер жиі өзгерген кезде «конфигурация». A - тек деректерді енгізу. Конфигурация функцияға деректерден ертерек берілуі керек A. Осы идеяны ескере отырып, конфигурация деректерін жай деректерден ажырату үшін пайдаланылуы мүмкін өзгерістердің күтілетін жиілігі деп айта аламыз. Сондай-ақ деректер әдетте бір көзден (пайдаланушы) және конфигурация басқа көзден (әкімші) келеді. Баптандыру процесінен кейін өзгертуге болатын параметрлермен жұмыс істеу қолданбаның күрделілігінің артуына әкеледі. Мұндай параметрлер үшін біз олардың жеткізу механизмін, талдауды және тексеруді, қате мәндерді өңдеуді өңдеуіміз керек. Демек, бағдарламаның күрделілігін азайту үшін орындалу уақытында өзгеруі мүмкін параметрлердің санын азайтқан дұрыс (немесе тіпті оларды мүлдем жойған).

Осы посттың тұрғысынан біз статикалық және динамикалық параметрлерді ажыратуымыз керек. Егер қызмет логикасы орындау уақытында кейбір параметрлердің сирек өзгеруін талап етсе, онда біз оларды динамикалық параметрлер деп атауға болады. Әйтпесе, олар статикалық болып табылады және ұсынылған тәсіл арқылы конфигурациялануы мүмкін. Динамикалық қайта конфигурациялау үшін басқа тәсілдер қажет болуы мүмкін. Мысалы, жүйенің бөліктері таратылған жүйенің бөлек процестерін қайта іске қосу сияқты жаңа конфигурация параметрлерімен қайта іске қосылуы мүмкін.
(Менің кішіпейіл пікірім - жұмыс уақытын қайта конфигурациялаудан аулақ болу, себебі бұл жүйенің күрделілігін арттырады.
Процестерді қайта іске қосу үшін ОЖ қолдауына сену оңайырақ болуы мүмкін. Дегенмен, бұл әрқашан мүмкін емес.)

Кейде адамдарды динамикалық конфигурацияны (басқа себептерсіз) қарастыруға мәжбүр ететін статикалық конфигурацияны пайдаланудың маңызды аспектісі конфигурацияны жаңарту кезінде қызметтің тоқтап қалуы болып табылады. Шынында да, егер статикалық конфигурацияға өзгертулер енгізу керек болса, жаңа мәндер тиімді болуы үшін жүйені қайта іске қосуымыз керек. Тоқтау уақытына қойылатын талаптар әртүрлі жүйелер үшін әртүрлі, сондықтан ол соншалықты маңызды болмауы мүмкін. Егер бұл өте маңызды болса, жүйені қайта іске қосуды алдын ала жоспарлауымыз керек. Мысалы, біз іске асыра аламыз AWS ELB қосылымының төгілуі. Бұл сценарийде жүйені қайта іске қосу қажет болғанда, біз жүйенің жаңа данасын қатарлас бастаймыз, содан кейін ескі жүйеге бар қосылымдарға қызмет көрсетуді аяқтауға мүмкіндік бере отырып, оған ELB ауыстырамыз.

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

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

Артықшылықтары мен кемшіліктері

Мұнда біз ұсынылған тәсілдің кейбір артықшылықтарын атап өтіп, кейбір кемшіліктерін талқылағымыз келеді.

артықшылықтары

Толық бөлінген жүйенің құрастырылатын конфигурациясының ерекшеліктері:

  1. Конфигурацияны статикалық тексеру. Бұл конфигурация типті шектеулермен дұрыс екеніне сенімділіктің жоғары деңгейін береді.
  2. Конфигурацияның бай тілі. Әдетте басқа конфигурация тәсілдері ең көп айнымалы ауыстырумен шектеледі.
    Scala көмегімен конфигурацияны жақсарту үшін тіл мүмкіндіктерінің кең ауқымын пайдалануға болады. Мысалы, біз әдепкі мәндерді қамтамасыз ету үшін белгілерді, әртүрлі аумақты орнату үшін нысандарды пайдалана аламыз. vals сыртқы ауқымда (ҚҰРҒАҚ) тек бір рет анықталады. Литералдық тізбектерді немесе белгілі бір сыныптардың даналарын пайдалануға болады (Seq, MapЖәне т.б.).
  3. DSL. Scala DSL жазушыларына лайықты қолдау көрсетеді. Бұл мүмкіндіктерді соңғы конфигурацияны ең болмағанда домен пайдаланушылары оқи алатындай етіп ыңғайлырақ және соңғы пайдаланушыға ыңғайлы конфигурация тілін орнату үшін пайдалануға болады.
  4. Түйіндер бойынша тұтастық пен үйлесімділік. Бүкіл үлестірілген жүйе үшін конфигурацияның бір жерде болуының артықшылықтарының бірі - барлық мәндер бір рет қатаң түрде анықталады, содан кейін олар бізге қажет барлық жерлерде қайта пайдаланылады. Сондай-ақ қауіпсіз порт мәлімдемелерін теріңіз, барлық ықтимал дұрыс конфигурацияларда жүйе түйіндерінің бір тілде сөйлейтініне көз жеткізіңіз. Түйіндер арасында нақты тәуелділіктер бар, бұл кейбір қызметтерді ұсынуды ұмытуды қиындатады.
  5. Өзгерістердің жоғары сапасы. Конфигурация өзгерістерін қалыпты PR процесі арқылы өткізудің жалпы тәсілі конфигурацияда да жоғары сапа стандарттарын белгілейді.
  6. Бір мезгілде конфигурация өзгерістері. Конфигурацияда қандай да бір өзгертулер енгізген сайын автоматты орналастыру барлық түйіндердің жаңартылуын қамтамасыз етеді.
  7. Қолданбаны жеңілдету. Қолданбаға конфигурацияны талдау және тексеру және қате конфигурация мәндерін өңдеу қажет емес. Бұл жалпы қолданбаны жеңілдетеді. (Күрделіліктің біршама артуы конфигурацияның өзінде бар, бірақ бұл қауіпсіздікке саналы түрде келіседі.) Қарапайым конфигурацияға оралу өте оңай — жетіспейтін бөліктерді қосыңыз. Құрастырылған конфигурацияны бастау оңайырақ және қосымша бөліктерді іске асыруды кейінірек кейінге қалдыру.
  8. Нұсқаланған конфигурация. Конфигурация өзгерістері бірдей даму процесіне байланысты болғандықтан, нәтижесінде біз бірегей нұсқасы бар артефакт аламыз. Бұл қажет болған жағдайда конфигурацияны кері ауыстыруға мүмкіндік береді. Біз тіпті бір жыл бұрын қолданылған конфигурацияны қолдана аламыз және ол дәл осылай жұмыс істейді. Тұрақты конфигурация таратылған жүйенің болжамдылығы мен сенімділігін жақсартады. Конфигурация компиляция уақытында бекітіледі және оны өндіріс жүйесінде оңай өзгерту мүмкін емес.
  9. Модульдік. Ұсынылған құрылым модульдік және модульдерді әртүрлі тәсілдермен біріктіруге болады
    әртүрлі конфигурацияларды (орнатулар/орналастырулар) қолдайды. Атап айтқанда, шағын масштабты бір түйін орналасуы және үлкен ауқымды көп түйін параметрі болуы мүмкін. Бірнеше өндіріс схемалары болуы орынды.
  10. Тестілеу. Тестілеу мақсатында жалған қызметті жүзеге асыруға және оны қауіпсіз түрдегі тәуелділік ретінде пайдалануға болады. Әртүрлі бөліктері мысқылға ауыстырылған бірнеше түрлі сынақ схемаларын бір уақытта сақтауға болады.
  11. Интеграциялық тестілеу. Кейде бөлінген жүйелерде интеграциялық сынақтарды орындау қиынға соғады. Толық бөлінген жүйенің қауіпсіз конфигурациясын теру үшін сипатталған тәсілді пайдалана отырып, біз барлық бөлінген бөліктерді бір серверде басқарылатын жолмен іске қоса аламыз. Жағдайды еліктеу оңай
    қызметтердің бірі қолжетімсіз болғанда.

кемшіліктері

Құрастырылған конфигурация тәсілі «қалыпты» конфигурациядан ерекшеленеді және ол барлық қажеттіліктерге сәйкес келмеуі мүмкін. Міне, құрастырылған конфигурацияның кейбір кемшіліктері:

  1. Статикалық конфигурация. Ол барлық қолданбаларға сәйкес келмеуі мүмкін. Кейбір жағдайларда барлық қауіпсіздік шараларын айналып өтіп, өндірісте конфигурацияны жылдам бекіту қажет. Бұл тәсіл оны қиындатады. Құрастыру және қайта орналастыру конфигурацияға кез келген өзгерту енгізілгеннен кейін қажет. Бұл – әрі қасиет, әрі ауыртпалық.
  2. Конфигурацияны құру. Конфигурация кейбір автоматтандыру құралы арқылы жасалған кезде бұл тәсіл кейінгі компиляцияны қажет етеді (ол өз кезегінде сәтсіз болуы мүмкін). Бұл қосымша қадамды құрастыру жүйесіне біріктіру үшін қосымша күш қажет болуы мүмкін.
  3. Құралдар. Бүгінгі таңда мәтіндік конфигурацияларға негізделген көптеген құралдар қолданылады. Олардың кейбіреулері
    конфигурация құрастырылған кезде қолданылмайды.
  4. Сананы өзгерту қажет. Әзірлеушілер мен DevOps мәтіндік конфигурация файлдарымен таныс. Конфигурацияны құрастыру идеясы оларға оғаш көрінуі мүмкін.
  5. Құрастырылатын конфигурацияны енгізбес бұрын жоғары сапалы бағдарламалық жасақтаманы әзірлеу процесі қажет.

Іске асырылған мысалдың кейбір шектеулері бар:

  1. Егер біз түйінді іске асыру талап етпейтін қосымша конфигурацияны қамтамасыз етсек, компилятор бізге жоқ іске асыруды анықтауға көмектеспейді. Мұны пайдалану арқылы шешуге болады HList немесе белгілер мен торт үлгісінің орнына түйін конфигурациясы үшін ADT (жағдайлар кластары).
  2. Біз конфигурация файлында кейбір қосымша мәліметтерді беруіміз керек: (package, import, object декларациялар;
    override defәдепкі мәндері бар параметрлер үшін). Бұл DSL көмегімен ішінара шешілуі мүмкін.
  3. Бұл мақалада біз ұқсас түйіндердің кластерлерінің динамикалық қайта конфигурациясын қарастырмаймыз.

қорытынды

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

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

Бұл тәсілді әртүрлі жолдармен кеңейтуге болады:

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

рахмет

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

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