안녕하세요. 배민프론트서버개발팀 박제현입니다.

주소 검색 서비스를 개선하면서 Real-time Service Configuration으로 consul을 적용한 사례를 공유하고자 합니다.

들어가며

먼저 제가 속한 팀부터 소개해볼까 합니다.

  • 배달의민족 앱에 사용될 데이터를 전달하는 서비스 개발(메인, 주소, 회원, 쿠폰, 리뷰 등)

저희 팀에는 고객과 밀접하게 운영되고 있는 서비스가 많다 보니 쉽게 개선하기 어려운 레거시 시스템이 많은데요.

맘 같아서는 쉽게 수정해서 바꿔버리고 싶지만, 한 부분만 고치려 해도 여러 군데 엮여있어서 작업이 쉽지 않습니다. 그러다 다른 우선순위에 밀려 개선하지 못하는 사이 그 시스템에서 장애가 빵 터지는 경우가 많죠. ㅠㅠ

어느 날, 팀 회식이어서 쌀통닭을 맛있게 냠냠하고 있었는데 주소 검색 서비스에 장애가 났다고 마구 알림이 떴습니다. ‘주소 검색이 안 된다고??? 왜???’ 하면서 급하게 노트북을 꺼내서 살펴봤는데…

저희가 해결할 방법이 없었습니다.

주소 검색 서비스는 A사의 지도 API에 전적으로 의존하다 보니, 당장 사용자들이 배달 주소지를 설정하지 못해 불편을 겪는 상황에서도 A사에서 장애 상황을 해결할 때까지 발만 동동 구르고 있었죠.
그러던 중 갑자기 의문이 들었습니다.

“진짜 우리가 할 수 있는 게 아무것도 없을까?”

장애 상황 이후에 우리가 해야 할 것

부사장님 이메일

위 이미지의 내용은 김범준 부사장님께서 장애 후속 조치와 관련하여 개발 조직 전체에 보내신 메일을 캡쳐한 부분입니다.

네. 우리는 계속 겪는 장애에서 배워나가야 합니다.
장애가 발생할 가능성이 남아있는 서비스에서 장애가 해결되었다고 ‘이제 잘 되겠지’라는 막연한 생각으로 넘어간다면, 그 문제는 나중에 더 큰 장애로 큰 타격을 줄 겁니다.

반복되는 A사 Map API 장애에도 우리 서비스는 정상 작동해야 합니다. 우리 서비스가 외부서비스에 영향을 받아 문제가 생기더라도 짧은 시간 내에 다시 정상으로 복구되어야 합니다.

이 장애를 통해 우리는 한 발자국 더 나아가야 합니다.

단기적 과제 - 주소 검색 서비스의 SPOF를 해결하자!

언제 또 장애가 발생할지 모르는 불안한 상황이지만 정신 똑띠차리고, 주소 검색 서비스와 단기적 과제를 먼저 정리합니다.

AS-IS 주소 검색 서비스

  • 사용자의 주소(장소) 및 위치를 조회하는 시스템(PHP 레거시)
  • A사 Map API(의존도 100%)
    • POI 통합검색 API: 전자지도 위에 표시된 건물과 상점 등 검색
    • 역지오 검색 API: 위경도 좌표 -> 주소
  • 단일 장애점(SPOF - Single Point Of Failure)
    • 장애가 발생하면 배달의민족 서비스에 큰 이슈(배달 주소지를 설정하지 못하므로 주문 자체를 하지 못하는 상황 발생)

단기적 과제

  • SPOF의 해결책으로 다른 위치정보사업자의 서비스를 백업 Plan으로 추가한다.
  • 다른 위치정보사업자의 서비스를 사용하더라도 기존 앱 대응을 위해 현재 Response 구조와 동일해야 한다.
  • ALB(Application Load Balancer)에서 2개의 Target Group(A사 Map API, 다른 위치정보사업자의 서비스)의 우선순위 변경을 통해 Traffic을 전환하도록 설정하자.

백업 Plan 추가 - B사 Map API로 Poi API 개발

조사해보니 B사 Map API에는 A사의 Poi API 역할을 하는 API가 없었습니다. 이럴 수가..

  • B사 Map API
    • geocode: 주소 -> 좌표 변환 API
    • reverseGeocoding: 좌표 -> 주소 변환 API
    • search: 지역별 업체 및 상호 검색 API

따라서, B사 Map API 3개를 조합하여 A사의 Poi API와 같은 데이터를 내려주도록 작업을 해야 합니다. 또한, Response는 A사의 Poi API를 사용할 때와 같도록 Converting 작업을 꼭 해주어야 합니다.

아… 너무 복잡하고 진행도 잘 안 되는 어려운 작업이었는데요. 설명하기조차 괴로우니 과감히 설명을 생략하도록 합니다.^^

B사 Map API 주소 검색 서비스의 인프라 설정

힘들게 작업한 B사 Map API 주소 검색 서비스를 새로운 Target Group에 배포합니다. 이렇게 설정해두면 ALB에서 우선순위만 변경해서 바로 B사 Map API로 전환할 수 있게 되는 거죠.

ALB TargetGroup 설정

  • beta-location-php-tg: A사 Map API를 사용하는 주소 서비스(PHP)
  • beta-location-java-tg: B사 Map API를 사용하는 주소 서비스(JAVA)

이후 A사 Map 서비스의 방화벽 작업 등의 순단현상이 발생할 수 있는 상황에서 B사 Map API로의 전환을 통해 순조롭게 상황을 넘길 수 있었습니다. 마음 한편의 불안감이 잠시나마 해소되었죠!

하지만 여전히 A사 Map API에 장애가 발생하면 AWS에 접속해서 Target Group을 마우스 클릭으로 변경해야 합니다.
음…아직 뭔가 후련하지가 않습니다.

본격적인 과제 논의를 위해 팀원분들께 미팅콜을 마구 날려봅니다. (띵동띵동)

장기적 과제

팀원분들의 도움으로 장기적 과제 목록이 챡챡 정리되었습니다.

  • A사 Map API, B사 Map API를 통합하여 추상화하고 Map Service 간에 유연한 Traffic 전환
    • 맵서비스의 Request 비율 조정(ex. A사 맵서비스 50%, B사 맵서비스 50%)
  • Real-time Service Configuration 도입
    • 맵서비스의 Request 비율을 실시간으로 바꿀 수 있게 하자.
    • Git 연동을 통한 환경 구성 정보의 변경 이력 관리
    • 환경 구성 정보를 변경하면 최대 1분 이내에 Client Service에 바뀐 값으로 적용되도록 하자.
    • RCS(Remote Configuration Server)를 도입하면 팀에서 여러 가지로 활용할 수 있다.
  • PHP -> JAVA 전환

하..이 어려운 걸 하라고 하시네요ㅠㅠ

본격적인 작업에 들어가기에 앞서

잠깐 몇 가지 정리 좀 하고 갈까요? 간단히 조사를 좀 해봤습니다. 아무것도 모르는 상태로 진행하면 개발이 산으로 갈 테니까요!

Remote Configuration Server(RCS)란?

RCS란 Client에서 필요한 환경구성 속성들을 외부에 저장하고 관리하는 서버입니다.

환경설정 속성들은 주로 dev, beta, prod 등과 같이 여러 개의 프로파일로 운영됩니다. 이런 속성들은 마이크로서비스 인스턴스가 많을 땐 관리가 어려워지는데요. 이 속성들을 외부화하고 중앙 집중화해서 여러 환경에 배포되는 Client에 적절하게 사용할 수 있습니다.

또한, 안정적인 배포를 위해서도 Feature Toggle을 많이 사용하는데요.
새로운 기능을 배포할 때 해당 기능의 ON/OFF 상태를 즉시 변경하는 데에도 사용할 수 있습니다. 이를 real-time으로 변경하기 위해서 고가용성의 Configuration Management를 사용합니다.

Run-time Service Configuration VS Real-time Service Configuration

run-time service configuration와 real-time service configuration 비교

  • Run-time Service Configuration
    서비스에는 feature flags 또는 maintenance mode와 같이 실시간으로 전파되어야 하는 많은 런타임 구성이 있습니다.
    configuration management를 사용하거나 서비스를 재배포하여 이러한 업데이트를 진행하기 위해서는 몇 분에서 몇 시간이 걸릴 수 있습니다.
    이러한 Rollout 기간 동안 인프라가 동기화되지 않고 서비스 구성이 올바르지 않을 수 있습니다.

  • Real-time Service Configuration
    전 세계에 분산되어있는 수많은 서비스에서 실시간으로 서비스 구성을 업데이트할 수 있습니다. Configuration은 계층형 Key/Value Store에 저장되며 효율적인 Edge Trigger는 응용 프로그램에 대한 변경 내용을 신속하게 푸시하게 됩니다.

Spring Cloud Config, Consul, Archaius 간단 비교 및 선택

  • 공통점
    • 환경 설정 정보를 외부에 보관하고 변경 내용을 동적으로 적용한다.
  • Spring Cloud Config
    • Spring Cloud
    • Git 저장소를 지원하는 중앙 집중식 Remote Configuration Management
    • 수동으로 각 인스턴스에 API를 호출하여 Refresh 하거나, Spring Cloud Bus을 통해 변경사항을 동적으로 인스턴스에 전파.
  • Archaius
    • Spring Cloud Netflix
    • 하나 이상의 Configuration Source를 지원 (JDBC, REST, .properties File, Dynamo DB)
  • Consul
    • Hashicorp
    • 클라우드 환경에서 서비스를 연결(Connect), 보안(Secure) 및 구성(Cofigure)하는 분산 Service Mesh
    • Web-UI 기능 존재
  • Consul로 선택!
    • Consul이 자동화 가능성 및 편의성이 가장 높다고 판단. (HTTP API가 잘 정의되어 있음)
    • Web-UI도 있다!
    • Spring Cloud Config를 사용해봤으나 수동 Refresh 등 자동화의 어려움이 있어 보임
    • Archaius는 여러 개의 Configuration Source를 설정할 수 있는 것이 매력적이지만 지금 시스템에 필요하지 않다.

본격적으로 신주소 서비스에 Consul을 적용해 봅시다.

장기적 과제라고 여유 부리면 안됩니다. 해야 할 작업이 많아요. ㅠㅠ

아키텍쳐

consul-architecture

전체 아키텍쳐 입니다. 하나씩 순서대로 설명을 해드릴요.

  1. Git Repository에 환경 구성 정보를 저장 또는 수정합니다. (Push)
  2. push 이벤트를 감지하면 설정된 Git Hook을 통해 Jenkins의 Job이 실행됩니다.
  3. Jenkins Job은 Consul Server에서 제공하는 HTTP API를 호출합니다.
  4. Consul Server는 KV Store에 환경 구성 정보를 업데이트합니다.
  5. Consul Server를 Watch하고 있는 Client Service(각 Instance)들이 Consul Server의 환경 구성 정보가 변경됨을 감지합니다.
  6. Client Service의 모든 Server가 환경 구성 정보를 업데이트합니다.

제 설명이 많이 부족해서 이해가 잘 안 될 수도 있습니다. ㅠㅠ 아래 설명들을 더 보시면 이해가 되실 거예요!
이제 아키텍쳐 흐름 순서대로 구현을 해보도록 합니다.

Git Repository - 환경 구성 정보를 코드로 저장하고 관리

  1. Git Repository 생성
    git-repository
    • {service}-{profile} 파일명으로 ‘.yml’을 생성 (ex. location-beta.yml, location-prod.yml)
    • yml 문법으로 환경 구성 정보를 저장.
  2. Webhook 추가
    git-webhook

Jenkins - Webhook으로 Job을 실행시켜 Key/Value를 Update

아! RCS 서비스의 이름을 정해봤는데요. 시시각각 자유자재로 피부를 변화시키는 카멜레온의 이름을 따서 카멜레온 서비스라고 팀내 명칭을 통일했습니다. (나중에 알았는데 Netflix의 Archaius도 카멜레온의 한 종류의 이름을 따온 거라네요!)

  1. Jenkins Job을 생성
    jenkins-job-create

  2. String Parameter 설정
    jenkins-job-parameter

  3. GitHub hook trigger 체크
    jenkins-job-webhook-trigger

  4. Shell script 설정
    jenkins-job-script

     # 먼저 /kv/config/ 경로의 환경 구성 정보를 삭제합니다.
     curl \
     -XDELETE \
     ${BETA_CONSUL_SERVER_HOST_PORT}/v1/kv/config/?recurse;
        
     # Git Repository의 .yml을 읽어옵니다.
     pwd=$(pwd)
        
     for file in $(pwd)/config/*.yml;
     do
         filename=${file##*/}
            
         # beta profile만 적용합니다. 
         if [[ "$filename" =~ "beta" ]]; then
             curl \
             --request PUT \
             --data-binary @$file \
             ${BETA_CONSUL_SERVER_HOST_PORT}${KV_PATH}$filename ;
         else
             echo $filename;
         fi
     done
    

Consul Server - 환경 구성 정보를 Consul Server의 Key/Value Store에 보관

  1. Consul Server - 로컬에 환경을 구축해서 테스트할 때 사용합니다.
    • https://www.consul.io/downloads.html 에서 zip 파일을 내려받습니다.
    • 압축을 풀고 consul binary를 실행합니다. (현재 최신 버전은 1.2.3입니다. 저는 1.1.0 버전으로 진행했었습니다.)
        # 아래 명령어를 실행합니다. 
        $ ./consul agent -dev
              
      
    • localhost:8500 로 접속해서 Consul UI에 들어가 봅시다.
      consul-server-main
  2. Consul Server - AWS Cloud에 구축
    1. VPC와 IAM은 사내 인프라에 맞게 설정해서 사용합니다.
    2. Security Group
      • ex) consul-security-group
      • Inbound
        • Source는 사내 보안 규정에 맞게 바꿔줘야 합니다.
        Type Protocol Port Range Source Description
        Custom TCP Rule TCP 8300 - 8302 0.0.0.0/0  
        Custom TCP Rule TCP 8400 0.0.0.0/0  
        Custom TCP Rule TCP 8500 0.0.0.0/0  
        Custom TCP Rule TCP 8600 0.0.0.0/0  
      • Outbound

        Type Protocol Port Range Source Description
        All traffic All All 0.0.0.0/0  
    3. ELB(Classic Load Balancer)
      • ex) consul-elb
      • Listeners

        LB Protocol LB Port Instance Protocol Instance Port Cipher SSL Certificate
        HTTP 80 HTTP 8500 N/A N/A
        HTTPS 443 HTTP 8500   xxxxxxxxxxxxxxxxxxxx (ACM)
      • Security Groups
        • 위에서 생성한 consul-security-group을 선택
      • Health Check

        Health Check  
        Ping Target HTTP:8500/v1/status/leader
        Timeout 5 seconds
        Interval 30 seconds
        Unhealthy threshold 2
        Healthy threshold 10
    4. Launch Configuration
      • ex) consul-launch-configuration
      • Advanced Details
        • AutoScalingGroup 에서 인스턴스를 띄울 때 아래 스크립트를 실행하도록 User Data에 설정합니다.
        • User Data
            #!/bin/sh
            # Consul Download 
            curl -O https://releases.hashicorp.com/consul/1.1.0/consul_1.1.0_linux_amd64.zip
            unzip consul_1.1.0_linux_amd64.zip
            rm -f consul_1.1.0_linux_amd64.zip
            mv consul /usr/local/bin
                          
            # consul.conf에 설정 저장
            cat <<'EOF' >> /etc/init/consul.conf
            description "Consul"
            author "jehyunpark"
          
            start on filesystem or runlevel [2345]
            stop on shutdown
                          
            script
                          
            /usr/local/bin/consul agent \
            -server \
            -data-dir=/tmp/consul \
            -client=0.0.0.0 \
            -bind='{{ GetInterfaceIP "eth0" }}' \
            -datacenter=ap-northeast-2 \
            -bootstrap-expect=3 \
            -ui \
            -retry-join "provider=aws tag_key=rcs tag_value=rcs"
                          
            end script
            EOF
                          
            initctl reload-configuration
            initctl start consul
          
      • 각 인스턴스의 Consul Server를 Clustering 하기 위해 retry-join 옵션을 추가했는데요.
        Auto Scaling Group Tag에 Key/Value를 rcs로 설정하면 Cluster로 묶이게 됩니다. Tag는 다른 값으로 설정할 수 있습니다.
        -retry-join "provider=aws tag_key=rcs tag_value=rcs"
    5. Auto Scaling Group
      • Launch Configuration: consul-launch-configuration 선택
      • Network(VPC), Subnet 은 사내 환경에 맞게 설정
      • Tags 추가

        Key Value
        rcs rcs
      • Load Balancers: consul-elb 선택
      • Instance: 3개 이상으로 홀수 단위 권장(ex. 3,5,7…)
    6. Consul Server 접속
      • ELB DNS로 접속
        • http://{HOST}:8500/ui

Client Service - Consul Server를 Watch 하면서 동적으로 환경 구성 정보를 변경할 Client Service

  1. build.gradle에 의존성 추가
     compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-consul-all', version: '1.3.3.RELEASE'
    
  2. bootstrap.yml 파일 추가
    • /resources 하위 경로에 생성한다. (application.yml 과 같은 depth)
        spring:
          application:
            name: location-api
              
        spring.profiles.active: local
              
        ---
        spring:
          profiles: local
          cloud:
            consul:
              host: localhost
              port: 8500
              discovery:
                instanceId: ${spring.application.name}:${server.port}
                healthCheckPath: /healthcheck
                healthCheckInterval: 20s
              config:
                format: FILES
                prefix: config
                watch:
                  delay: 1000
        ---
        spring:
          profiles: beta
          cloud:
            consul:
             # ELB Host 경로 입력
              host: elb-host
              port: 80
              discovery:
                instanceId: ${spring.application.name}:${server.port}
                healthCheckPath: /healthcheck
                healthCheckInterval: 20s
              config:
                format: FILES
                prefix: config
                watch:
                  delay: 1000
      
  3. MapConfigProperties.java 설정
     package com.baemin.location.configuration;
        
     import org.springframework.beans.factory.annotation.Value;
     import org.springframework.cloud.context.config.annotation.RefreshScope;
     import org.springframework.context.annotation.Configuration;
        
     @RefreshScope
     @Configuration
     public class MapConfigProperties {
        
         @Value("${aMapPercentage:100}")
         private int aMapPercentage;
         @Value("${bMapPercentage:0}")
         private int bMapPercentage;
        
         public MapConfigProperties() {
         }
        
         public int getAMapPercent() {
             return aMapPercentage;
         }
        
         public int getBMapPercent() {
             return bMapPercentage;
         }
        
     }
    
  4. Configuration Value 사용 방법
     @Autowired
     private MapConfigProperties mapConfigProperties;
    
     public void getMapPercentage() {
         int aMapPercentage = mapConfigProperties.getAMapPercent();
         int bMapPercentage = mapConfigProperties.getBMapPercent();
     }
    
  5. Client Server 시작 시 Consul Server에 저장된 값(aMapPercentage, bMapPercentage)을 가져와서 사용.
    • Client에서는 Consul Server를 계속 watch 하고 있으며 Consul Server의 값이 변경되면 즉시 값을 Refresh 하여 적용합니다.

신주소 서비스 내부 로직 - A사, B사 서비스를 추상화하고 비율로 Routing

A사 Map API는 “내비게이션 용”이고 B사 Map API는 “검색용” 이어서 추상화하기에는 많은 어려움이 있었는데요.
백업 Plan으로 B사 Map API를 도입하면서 구현했던 로직을 참고하여 A, B 사의 Map API를 추상화했습니다.

  1. Class Diagram
    new-location-api-class-diagram
    • MapService Interface를 생성하고 searchAddress(), reverseAddress() 메서드를 AMapService, BMapService에서 구현합니다.
  2. Flow Sequence Diagram
    new-location-api-flow-diagram

    1. Client(사용자)가 /location/address를 호출합니다. (LocationController의 searchAddress(searchValue))
       package com.baemin.location.controller;
              
       import ...
              
       @RestController
       @RequestMapping("/location")
       public class LocationController {
              
           @Autowired
           private MapServiceSelector mapServiceSelector;
              
           @Override
           @GetMapping(value = "/address")
           public AddressListDto searchAddress(@RequestParam("schvalue") String searchValue) {
                      
               MapService mapService = mapServiceSelector.select();
               return mapService.searchAddress(searchValue);
           }  
       }
      
    2. MapServiceSelector 의 select()는 aMapPercentage, bMapPercentage 값으로 맵서비스의 비율을 계산하여 MapService 구현체를 반환합니다.
       package com.baemin.location.configuration;
              
       import ...
              
       public class MapServiceSelector {
           @Autowired
           private MapConfigProperties mapConfigProperties;
           @Autowired
           private AMapService aMapService;
           @Autowired
           private BMapService bMapService;
              
           public MapService select() {
               int aMapPercentage = mapConfigProperties.getAMapPercentage();
               int bMapPercentage = mapConfigProperties.getBMapPercentage();
              
               // aMapPercentage와 bMapPercentage 의 비율을 계산해 MapService 구현체를 반환
               //...
               return mapService;
           }
       }
      
    3. 반환된 MapService의 searchAddress(searchValue)를 실행하여 결괏값을 받습니다.
       package com.baemin.location.service;
              
       import ...
               
       @Service
       public class AMapService implements MapService {
           @Autowired
           private AMapApiCallService aMapApiCallService;
           @Autowired
           private AMapAddressResponseConverter aMapAddressResponseConverter;
              
           @Override
           public AddressListDto searchAddress(String searchValue) throws LocationException {
              
               AMapPoisResponseDto aMapPoisResponseDto = aMapApiCallService.pois(searchValue);
               AddressListDto addressListDto = aMapAddressResponseConverter.convertAddressListDto(aMapPoisResponseDto);
              
               return addressListDto;
           }
       }
      

설정 끝. 사용은 어떻게?

  • Git Repository에 저장된 환경 구성 정보를 변경 후 push 한다. 간단간단

테스트

마지막으로 환경 구성 정보 변경 시 Client Service에 적용되기까지의 시간을 테스트해봤는데요. 목표했던 1분보다 더 빠르게 적용됨을 확인할 수 있었습니다!

  • Git에 환경 구성 정보 저장 - aMapPercentage: 100, bMapPercentage: 0
  • Git에 저장된 환경 구성 정보 변경 - aMapPercentage: 0, bMapPercentage: 100
  • Client Service에 환경 구성 정보가 변경되어 AMapService 100%를 BMapService 100%로 적용되기까지 시간 측정

    적용내용 경과(초)
    Git에 저장된 환경 구성 정보 변경 0
    Jenkins Hook 실행 1.5
    Jenkins Job 실행 3.5
    Consul Server 설정값 변경 및 Client 설정값 변경 완료 14.5
    합계 19.5
  • 결과: Client Service에 환경 구성 정보가 변경되기까지의 시간 약 20초

PHP -> JAVA 전환

드디어 주소 검색 서비스를! PHP 레거시의 일부를! JAVA로 전환했습니다!!!
주소 검색 서비스 외에도 여러 도메인을 JAVA로 전환하고 있는데 노하우가 생기는지 점점 속도가 붙고 있습니다.
아! 그리고 PHP가 안 좋은 언어라서 JAVA로 변경하는 것은 아닙니다! 오해는 없었으면 좋겠네요.^^ 할많하않;

앞으로 기대되는 일들

  • RCS의 추후 활용 방안
    • 이번에 구축한 카멜레온 서비스를 다른 팀들과 공통으로 사용하기 위해 현재 가이드 문서를 작성하며 논의하고 있습니다.
  • Consul Server 업데이트
    • 운영에 서비스 중인 카멜레온 서비스를 최신 버전인 1.2.3 버전으로 업데이트할 계획입니다. 지금 구축을 고려 중이시면 1.2.3 버전을 고려해보세요!
  • 레거시 개선과 기술부채 청산!
    • 쌓여있는 기술부채를 하나하나 청산해나가는 재미(고통)가 있습니다.
  • Cloud Native 시스템 도입

무엇을 배웠고 어떤 생각을 했는지?

  • 오버엔지니어링을 주의하라
    새로운 기술을 도입하다 보면 기술에 매몰돼서 현실을 직시하지 못할 수 있습니다. 나 자신뿐 아니라 팀의 상황을 계속 확인하며 진행하는 것이 중요합니다.
  • 디테일을 놓치지 말 것
    서비스의 장애가 많이 줄어들었다고 하지만 여전히 발생 가능성이 있고, 장애가 발생하면 과거보다 파장이 더욱 커진 상황입니다.
    새로운 기술을 사용해서 빨리 서비스에 배포하고 싶겠지만, 그만큼 신중하게 진행해야 합니다. 세세한 부분까지 꼼꼼히 검토해야 합니다.
  • 사람이 전부다.
    이번 작업을 진행하면서 어려움이 많았습니다. 인프라나 보안, 세세한 네트워크 세팅 등 모르는 부분이 많기 때문인데요. 그럴 때마다 많은 분을 찾아가서 여쭤보며 배웠고, 특히 팀원의 도움이 정말 큰 힘이 되었습니다.
    모르면 물어보자. 그리고 내가 아는 건 잘 알려주자. 라는 생각이 많이 들었던 것 같습니다.
  • 개인적 성장 포인트
    이번 개선 과제를 진행하면서 개인적으로 많이 성장했다고 생각하는데요. 몰라서 막히는 부분을 어떻게든 해결하려고 삽질하는 사이, 지식과 경험이 쌓인 것 같습니다. 새로운 일도 잘할 수 있다는 자신감도 커졌고요.
    내가 조금만 더 용기를 낸다면 더 나아갈 수 있다.라는 말이 자꾸 떠오릅니다. 앞으로도 어려운 일에 계속 도전하면서 더 치열하게 일해봐야겠습니다.


제 글은 여기까지입니다.

긴 글 읽어주셔서 감사합니다.