코드로서의 관찰 가능한 인프라 시설

지난 몇 주 동안 저는 인프라 시설의 배치를 위해 모니터링을 설치하는 데 실제 제품과 관련된 일을 지연시켰습니다.
나는 TypeScript로 나의 인프라를 정의하고 내가 필요로 하는 모든 공급자를 사용할 수 있도록 클라우드 공급자와 독립된 IaC 프레임워크를 사용하고 있다.
나는 Honeycomb's의 관찰적 감시 방법에 관한 글을 읽었는데, 이것은 나로 하여금 나의 배치 코드를 생각하게 했다.잠시 후, GitHub과 같은 동작을 실행할 수 있기 때문에, 이것은 내 기계에 실행되지 않을 것이다.이것은 내가 문제가 발생하지 않도록 약간의 견해를 필요로 한다는 것을 의미한다.하지만 시간이 지날수록 배치를 보는 것도 좋을 것 같다.새로운 자원은 어떻게 배치 지속 시간 등에 영향을 줍니까?
이것이 바로 벌집 속의 흔적이다.

왜 내가 배치를 개발기기에서 클라우드로 옮기기 전에 설치하지 않습니까?
본문은 당신에게 어떻게 사용하는지 보여 줄 것입니다

선결 조건

  • Node.js
  • A Pulumi account
  • The Pulumi CLI
  • Pulumi 스택
  • A Honeycomb account
  • Pulumi 자동화 API


    API는 Pulumi CLI의 프로그래밍 인터페이스입니다.크롬이 아닌 풀미의 인형 배우를 생각해봐.
    CLI를 제어하기 때문에 CLI가 할 수 있는 모든 것, 심지어 더 많은 것을 할 수 있습니다.
    우리의 용례에 대해, 벌집에 어떤 자원을 배치했는지 알려주는 정보가 필요합니다. 벌집은 우리를 위해 아름다운 추적도를 만들 것입니다.
    자동 API 실행pulumi up 명령을 사용하려면 다음 코드가 필요합니다.
    const { LocalWorkspace } = require("@pulumi/pulumi/automation")
    
    const main = async () => {
      const stack = await LocalWorkspace.createOrSelectStack({
        stackName: "development",
        workDir: ".",
      })
      await stack.up()
    }
    
    main()
    
    @pulumi/pulumi 패키지는 이미 자동화 API를 포함하고 있기 때문에 우리가 유일하게 필요로 하는 것이다.
    우리는 LocalWorkspace 대상을 사용하여 창고를 불러옵니다.이것은 모든 창고를 완전무결하게 할 것이다.CLI를 사용하여 여전히 배포/제거할 수 있습니다.
    그리고 우리는 전화stack.up()로 기다렸다.API 생성된 출력은 CLI 생성된 출력보다 예쁘지는 않지만 동일한 정보를 포함합니다.
    또한 API의 up 명령을 사용하면 배포 중에 발생한 모든 이벤트를 캡처할 수 있습니다.이것은 우리가 찾는 데이터다.

    배포 이벤트

    up 대상의 stack 방법은 config 대상을 받아들인다.우리는 onEvent 속성을 사용하여 모든 변경 사항을 감청할 수 있다.
    stack.up({
      onEvent: (event) => {
        console.log(event)
      },
    })
    
    우리는 이곳에서 모든 사건을 취소한다.출력을 보면 다양한 유형의 이벤트가 있습니다.
  • preludeEvent는 처음부터 터치하였으며 창고 설정에 대한 정보를 포함하였다.
  • resourcePreEvent는 자원을 배치하기 전에 촉발하고resOutputsEvent는 자원을 배치한 후에 촉발한다.
  • diagnosticEvent에는 발생할 수 있는 최종 오류가 포함되어 있습니다.
  • summaryEvent에는 이미 무엇을 했는지, 모든 것이 성공했는지에 대한 정보가 포함되어 있다.
  • 사건의 구조가 좀 이상하다.이 이벤트들은 우리가 덮어쓸 수 있는 형식 속성이 아니라 이벤트 형식과 유사한 속성입니다.
    다음 코드는 올바른 이벤트를 가져오는 방법과 해당 이벤트의 데이터가 숨겨진 위치를 보여 줍니다.
    onEvent: (event) => {
      if (event["preludeEvent"] !== undefined) 
        return console.log(event.preludeEvent.config)
    
      if (event["resourcePreEvent"] !== undefined) 
        return console.log(event.resourcePreEvent.metadata)
    
      if (event["resOutputsEvent"] !== undefined) 
        return console.log(event.resOutputsEvent.metadata)
    
      if (event["diagnosticEvent"] !== undefined) 
        return console.log(event.diagnosticEvent)
    
      if (event["summaryEvent"] !== undefined) 
        return console.log(event.summaryEvent)
    },
    
    만약 우리가 이런 프로그램을 실행한다면, 우리는 벌집에 보내야 할 모든 정보를 얻을 수 있을 것이다.다음 단계는 허니콤에게 무슨 일이 일어났는지 알려주는 것이다.

    벌집에 데이터 보내기


    벌집은 우리에게 두 개의 도서관을 제공했다.Node.js BeelineLibhoney.
    Libhoney는 서비스 API에 원본 이벤트를 보낼 수 있는 저급 벌집 클라이언트입니다.직선은 Libhoney 위에 있는 추상적인 것으로 검측과 추적을 돕는 데 사용된다.
    일반적으로 선을 사용하는 것은 노드를 설정하고 사용하기 쉽기 때문이다.js의 내부 HTTP 모듈과 Express 프레임워크는 상자를 열면 바로 사용할 수 있습니다.
    이 예에서는 HTTP 서버를 구축하지 않았기 때문에 직선 탐지에 큰 도움이 되지 않습니다.
    따라서 우리의 배치 예시에 대해 나는 계속 Libhoney를 사용할 것이다.

    벌집 사건과 흔적


    벌집은 시스템에서 무슨 일이 일어났는지 알아보기 위해 이벤트를 사용한다.가령 사건이 특수한 속성을 가지고 있다고 가정하면 switchtrace.trace_id 벌집은 그것들을 연결시킬 수 있다.이런 방식을 통해 "당신의 요청에 대한 추적은 100밀리초의 신분 검증과 200밀리초의 데이터베이스 접근을 포함하여 300밀리초가 필요하다"는 것을 알 수 있다.
    그래서 당신은 사건을 통해 모든 정보를 벌집으로 전송할 수 있습니다.때로는 연관이 필요 없다.서버가 20퍼센트의 메모리를 사용했다는 것만 알려주고 싶어요.그러나 우리의 예에서 우리는 하나의 배치와 관련된 모든 사건을 하나의 추적에 연결하기를 희망한다. 그러면 우리는 우리가 무엇을 배치했는지, 얼마나 걸리는지, 문제가 발생하면 어떤 자원이 초래했는지 확인할 수 있다.

    Pulumi와 벌집을 연결합니다.


    Libhoney를 초기화하고 모든 Pulumi 이벤트에 정확한 벌집 이벤트를 보내야 합니다
    하지만 벌집 중 하나부터 시작하자.pulumi up 이벤트.
    const Libhoney = require("libhoney")
    const { LocalWorkspace } = require("@pulumi/pulumi/automation")
    
    const hny = new Libhoney({
      writeKey: "<HONEYCOMB_API_KEY>",
      dataset: "example-iac",
    })
    
    const id = (name) => `${name}-${Date.now()}`
    
    const traceId = id`trace`
    const rootSpanId = id`trace-root-span`
    const startTimestamp = Date.now()
    
    const main = async () => {
      const stack = await LocalWorkspace.createOrSelectStack({
        stackName: "development",
        workDir: ".",
      })
      await stack.up({
        onEvent: (event) => {},
      })
    }
    
    main().then(() => {
      hny.sendNow({
        name: "up",
        service_name: "Pulumi",
        "trace.trace_id": traceId,
        "trace.span_id": rootSpanId,
        duration_ms: Date.now() - startTimestamp,
      })
    })
    
    우리는 시작할 때 atrace.span_id, atraceId와 arootSpanId를 정의하고 모든 것이 끝난 후에 그것을 보냈다.startTimestamp가 있는 이벤트는 동일한 추적 ID를 가진 다른 모든 이벤트와 함께 그룹화됩니다. 이 경우 이벤트는 하나뿐입니다.trace.trace_id는 일반적으로 사용자가 실행하고 측정하고자 하는 작업이고 name는 이 작업을 수행하는 서비스입니다.이 예에서 우리는 운행한다service_name.
    마지막으로, 우리는 일이 얼마나 걸릴지 추적하기 위해 pulumi up 속성을 보냈다.
    결과는 다음과 같습니다.

    이것은 별로 흥미롭지 않지만, 적어도 우리는 그것이 운행할 때 붕괴되지 않았고, 얼마나 걸렸는지 안다.

    자세한 내용


    다음 단계는 세부 사항을 이해하는 것이다.구성 매개 변수는 무엇입니까?배치의 다른 부분은 얼마나 걸렸습니까?
    이를 위해 우리는 두 가지 사건duration_mspreludeEvent을 연구해야 한다.
    지금까지 Pulumi up 명령에 대해서만 벌집 이벤트를 보냈습니다.
    현재, 우리는 창고를 위해 세 개의 사건을 보낼 것이다.
  • summaryEvent 이벤트는 실제 자원 배치에 필요한 시간과 창고
  • 의 설정 파라미터를 포함합니다
  • init 이벤트는 모든 리소스가 작업을 완료하는 데 필요한 기간을 포함합니다.
  • run 이벤트에는 자동화 API 종료 기간과 요약 데이터가 포함됩니다.
  • 업데이트된 샘플 코드를 살펴보겠습니다.
    ...
    
    const traceId = id`trace`
    const rootSpanId = id`trace`
    const startTimestamp = Date.now()
    let stackFinishStartTimestamp
    
    const main = async () => {
      const initStartTimestamp = Date.now()
      const runStackSpanId = id`stack`
      let stackRunStartTimestamp
    
      const stack = await LocalWorkspace.createOrSelectStack({
        stackName: "development",
        workDir: ".",
      })
      await stack.up({
        onEvent: (event) => {
          if (event["preludeEvent"] !== undefined) {
            const hnyEvent = hny.newEvent()
            hnyEvent.timestamp = new Date(initStartTimestamp)
            hnyEvent.add({
              name: "init",
              service_name: "Stack",
              "trace.trace_id": traceId,
              "trace.parent_id": rootSpanId,
              "trace.span_id": id`stack`,
              duration_ms: Date.now() - initStartTimestamp,
              ...event.preludeEvent.config,
            })
            hnyEvent.send()
            stackRunStartTimestamp = Date.now()
            return
          }
    
          if (event["summaryEvent"] !== undefined) {
            const hnyEvent = hny.newEvent()
            hnyEvent.timestamp = new Date(stackRunStartTimestamp)
            hnyEvent.add({
              name: "run",
              service_name: "Stack",
              "trace.trace_id": traceId,
              "trace.parent_id": rootSpanId,
              "trace.span_id": runStackSpanId,
              duration_ms: Date.now() - stackRunStartTimestamp,
            })
            hnyEvent.send()
            stackFinishStartTimestamp = Date.now()
            return
          }
        },
      })
    }
    
    main().then(() => {
      let hnyEvent = hny.newEvent()
      hnyEvent.timestamp = new Date(stackFinishStartTimestamp)
      hnyEvent.add({
        name: "finish",
        service_name: "Stack",
        "trace.trace_id": traceId,
        "trace.parent_id": rootSpanId,
        "trace.span_id": id`stack`,
        duration_ms: Date.now() - stackFinishStartTimestamp,
      })
      hnyEvent.send()
    
      hnyEvent = hny.newEvent()
      hnyEvent.timestamp = new Date(startTimestamp)
      hnyEvent.add({
        name: "up",
        service_name: "Pulumi",
        "trace.trace_id": traceId,
        "trace.span_id": rootSpanId,
        duration_ms: Date.now() - startTimestamp,
      })
      hnyEvent.send()
    })
    
    우선, 우리는 세 개의 사건 시작 시간을 포획하는 코드에 변수를 추가해야 한다.finish 이벤트는 up 명령을 호출하기 전에 시작되며 자동화 API 트리거init까지 지속됩니다.preludeEvent사건은 run사건이 끝난 후 즉시 시작하여 init사건이 촉발될 때까지 지속된다.summaryEvent 이벤트는 finish 완료 즉시 시작되고 Pulumi up의 상위 이벤트가 완료되기 전에 중지됩니다.
    이 이벤트들은 모두 Pulumi up 이벤트run를 얻었기 때문에 나중에 벌집 보기에 끼워 넣을 것이다.
    또한 trace.parent_id 이벤트 획득init 대상이기 때문에 우리는 창고가 어떻게 벌집 내부에 배치되었는지 볼 수 있다.
    예를 실행하면 다음과 같은 내용을 볼 수 있습니다.

    리소스 배포 기간 확보


    자원은 우리에게 더 많은 세부 사항을 제공했다.이벤트event.preludeEvent.config로서 저희는 작업(창설, 업데이트, 삭제)을 사용하고namePulumi 정의에서 자원의 이름을 사용합니다.
    리소스의 상위 레벨을 얻으려면 각 리소스의 URN을 저장하여 나중에 사용할 수 있도록 해야 합니다.그러나 우리는 어떻게든 가동 시간을 유지해야 하기 때문에 service_name 가동 시 한 걸음에 완성할 수 있다.
    예시 코드를 보여 주세요.이번에 나는 가독성을 높이기 위해 샘플 코드와 다른 이벤트를 삭제했다.
    const main = async () => {
      // ...
    
      const resourceSpans = new Map()
    
      // ...
    
      await stack.up({
        onEvent: (event) => {
          // ...
    
          if (event["resourcePreEvent"] !== undefined) {
            const { metadata } = event.resourcePreEvent
            resourceSpans.set(metadata.urn, {
              spanId: id`span`,
              startTimestamp: Date.now(),
            })
            return
          }
    
          if (event["resOutputsEvent"] !== undefined) {
            const { metadata } = event.resOutputsEvent
    
            const serviceName = metadata.urn.split("::")[3]
            const { spanId, startTimestamp } = resourceSpans.get(metadata.urn)
            const parentUrn = metadata.new?.parent ?? metadata.old?.parent
            const parentId = resourceSpans.get(parentUrn)?.spanId ?? runStackSpanId
    
            const hnyEvent = hny.newEvent()
            hnyEvent.timestamp = new Date(startTimestamp)
            hnyEvent.add({
              name: metadata.op,
              service_name: serviceName,
              "trace.trace_id": traceId,
              "trace.parent_id": parentId,
              "trace.span_id": spanId,
              duration_ms: Date.now() - startTimestamp,
            })
    
            if (metadata.op === "update")
              hnyEvent.addField("diffs", metadata.diffs.join(", "))
    
            hnyEvent.send()
            return
          }
    
          // ...
        },
      })
    }
    
    이게 어떻게 된 일입니까?
    우선, 자원은 그 차원 구조에 따라 배치되지만 동급은 서로 다른 순서에 따라 배치할 수 있다.이것이 바로 우리가 그들의 resourcePreEventspanId를 보존해야 하는 이유이다. 우리가 startTimestamp를 얻었을 때.
    나는 이를 위해 하나resourcePreEvent를 사용했고 자원의 URN으로 그것을 키 제어했다. 왜냐하면 URN은 부자 관계에 사용되기 때문에 우리가 관심을 가지는 모든 사건의 일부이기 때문이다.
    나중에 Map에 불이 났을 때, 그것을 모두 포장하여 벌집 회사로 보낼 때가 되었다.
    사건은 URN이 관련resOutputsEventspanId를 찾아왔다.리소스의 Parent 속성 중 URN을 사용하여 리소스의 상위 레벨startTimestamp을 가져올 수도 있습니다.이렇게 하면 우리의 경계가 벌집에 정확하게 박힐 수 있다.
    만약 자원 작업이 spanId라면, 우리는 update를 이 이벤트에 추가할 것입니다.이런 방식을 통해 우리는 벌집에서 지난번 배치 이후 입력에 어떤 변화가 생겼는지 볼 수 있다.
    마지막 추적은 다음과 같을 것이다.

    실수


    마지막 단계에서 배치 오류를 얻으려면 독자들에게 연습으로 남겨 두겠습니다.
    힌트를 드릴게요!
    우선, 흥미로운 사건은diffs인데 diagnosticEventseverity 속성을 가지고 있다.오류만 있으면 messagedebug 심각성을 필터해야 한다.
    이 이벤트는 오류를 잠시 후의 벌집 경계info와 연결하는 데 사용할 수 있는 urn도 포함한다.오류가 URN과 무관하면 런 스택 이벤트를 대상으로 간단하게 사용할 수 있습니다.
    벌집 이벤트에 resourceSpans 필드를 추가하면, 벌집의 오류를 자동으로 계산합니다.

    요약


    Pulumi와 Honeycomb를 사용할 때 IaC의 관측성은 상당히 간단해졌다.
    Pulumi의 자동화 API를 사용하면 JavaScript를 사용하여 CLI를 제어할 수 있지만 Honeycomb의 Libhoney 레벨은 낮아서 배치 과정의 모든 부분에 도구를 제공할 수 있습니다.
    이런 방식을 통해 우리는 어떤 자원이 우리의 배치를 늦추었거나 어떤 자원이 배치 실패를 초래했는지 추적할 수 있다.

    좋은 웹페이지 즐겨찾기