no image
[Linux] 리눅스 쉘 기본 명령어
메뉴얼 조회매뉴얼 조회 명령어인 man 을 사용하려고 할 때 아래의 메시지가 나왔다.This system has been minimized by removing packages and content that arenot required on a system that users do not log into.To restore this content, including manpages, you can run the 'unminimize'command. You will still need to ensure the 'man-db' package is installed. 위 메시지는 시스템이 최소화(minimized)되어 man 페이지와 같은 비필수 패키지 및 콘텐츠가 제거된 상태라는 의미이다. 이러한 최소화된 ..
2024.11.04
no image
[네트워크 이론] 미시적으로 보는 네트워크
이글은 혼자 공부하는 네트워크(저자 : 강민철)의 책과 강의 내용을 개인적으로 정리하는 글임을 알립니다.프로토콜(protocol)현대 인터넷은 호스트 간 패킷을 교환하는 방식으로 대부분 패킷 교환 방식을 사용한다.언어가 정보를 주고받기 위해 사회적으로 합의된 의사소통 방식이라면, 프로토콜은 노드 간에 정보를 올바르게 주고받기 위해 합의된 규칙이나 방법을 의미한다.즉, 서로 다른 통신 장치들이 정보를 주고받으려면 프로토콜이 통해야 한다. 우리가 인터넷을 이용할 수 있는 것은 모두 상대 호스트와 동일한 프로토콜을 사용하기 때문이다.다만 일상 속 언어와는 달리 통신 과정에서는 하나의 프로토콜만 사용하지 않는다. 일반적으로는 여러 프로토콜을 함께 사용한다. 모든 프로토콜에는 저마다의 목적과 특징이 있다.프로토콜..
2024.10.17
no image
[네트워크 이론] 거시적으로 보는 네트워크
이글은 혼자 공부하는 네트워크(저자 : 강민철)의 책과 강의 내용을 개인적으로 정리하는 글임을 알립니다.네트워크란 여러 장치가 서로 연결되어 정보를 주고받을 수 있는 통신망이다.네트워크는 그래프라는 자료구조 모양을 띄고 있다.그래프는 아래의 그림처럼 노드와 노드를 연결하는 간선으로 이루어진 자료구조이다.  네트워크의 기본 구조호스트(host)네트워크의 가장자리에 위치한 노드는 네트워크를 통해 흐르는 정보를 최초로 생성 및 송신하고, 최종적으로 수신한다.우리가 일상에서 사용하는 네트워크 기기 대부분이 여기에 속한다고 봐도 무방하다.이러한 가장자리 노드를 네트워크에서 호스트라고 부른다. 때로는 호스트가 네트워크상에서 특정한 역할을 수행하기도 하는데, 대표적인 역할로는 서버와 클라이언트가 있다.서버 : 어떠한..
2024.10.16
[Docker] 애플리케이션 종료시 Compose 자동시작
환경AWS EC2 (ubuntu) Docker Compose 로 여러개의 도커 컨테이너가 묶여있고, EC2 인스턴스로 배포된 상황에서 여러가지 이슈들로 인스턴스가 재부팅 되거나 애플리케이션이 종료될 수 있다.이때 재부팅되거나 배포한 애플리케이션이 종료되었을 때 자동으로 애플리케이션이 시작되게끔 할 수 있다.먼저 Docker Compose 애플리케이션 서비스 파일을 생성해야 한다.sudo vi /etc/systemd/system/docker-compose-app.service 이후 아래의 서비스 파일을 입력한다.[Unit]Description=Docker Compose Application ServiceAfter=docker.serviceRequires=docker.service[Service]Restar..
2024.10.13
no image
[Docker + SpringBoot] 스프링부트 로그 파일 남기기
개발환경SpringBoot v3.3.3AWS EC2 (ubuntu)RDSDocker ComposeGithubActions 로그를 파일로 저장하기(환경 분리 포함)스프링부트에서 local 환경과 prod 환경을 분리하여 로그를 남기는 방법은 아래의 글에서 참고하였다.https://blog.pium.life/server-logging/ Logback을 이용해 운영 환경 별 로그 남기기이 글은 우테코 피움팀 크루 '그레이'가 작성했습니다. 로깅이란 ? 우리가 처음 개발을 할 때 System.out.println(), cout blog.pium.life 이 글은 local 환경에서는 콘솔에서 로그를 확인할 수 있고 prod환경에선 info 로그와 error 로그 파일을 분리할 수 있는 설명이 담겨있다. 먼저 a..
2024.10.11
no image
[Docker + SpringBoot] Docker와 SpringBoot의 타임존 동기화
환경스프링부트 v3.3.3AWS EC2RDSDocker ComposeGithub Actions 문제 상황현재 내가 진행하고 있는 프로젝트에서 설정된 시간에 알람 시간을 설정하는 기능이 있다.DB에 저장할 때 기본값이 오전 8시인데 자꾸 9시간 뒤인 오후 5시로 설정되는 문제를 확인하였다.이렇게 9시간 차이가 나는 것은 보통 타임존 설정 문제인데, 나는 이미 타임존 관련 문제를 아래와 같이 세팅해둔 상태라서 더욱 당황했다. @PostConstruct 를 이용한 JVM 타임존 설정@SpringBootApplicationpublic class PromiseApplication { public static void main(String[] args) { SpringApplication.run(PromiseAp..
2024.10.09
no image
[AWS + SpringBoot] 시간대 설정하기
리전을 서울로 설정했더라도 EC2는 기본적으로 외국에 있기 때문에 날짜관련된 로직이 들어갔을 때 정상적으로 작동하지 않을 가능성이 있다. 아래의 두 가지 방법중 하나를 선택해서 이러한 문제를 해결할 수 있다. @PostConstruct를 이용해 타임존 변경애플리케이션 시작 시점에 명시적으로 JVM의 시간대를 설정할 수 있다. @SpringBootApplicationpublic class PromiseApplication { public static void main(String[] args) { SpringApplication.run(PromiseApplication.class, args); } @PostConstruct public void init() { // JVM의 기본 시간대를 Asia/Se..
2024.09.20
no image
[Redis] 캐싱 전략
이 글은 인프런의 지식 공유자 박재성님의 강의를 듣고 개인적으로 정리하는 글임을 알립니다.조회 전략데이터를 조회할 때 주로 사용하는 전략이 Cache Aside 전략이다. Look Aside 전략 또는 Lazy Loading 전략이라고 부른다. 캐시에 데이터가 있을 경우 (= Cache Hit)  캐시에 데이터가 없을 경우 (= Cache Miss) Cache Aside 전략은 캐시(Cache)에서 데이터를 확인하고, 없다면 DB를 통해 조회해오는 방식이다.  쓰기 전략Write Around 전략Cache Aside 전략이 데이터를 어떻게 조회할 지에 대한 전략이었다면, Write Around 전략은 데이터를 어떻게 쓸지(저장, 수정, 삭제)에 대한 전략이다. Write Around 전략은 Cache ..
2024.09.15

메뉴얼 조회

매뉴얼 조회 명령어인 man 을 사용하려고 할 때 아래의 메시지가 나왔다.

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, including manpages, you can run the 'unminimize'
command. You will still need to ensure the 'man-db' package is installed.

 

위 메시지는 시스템이 최소화(minimized)되어 man 페이지와 같은 비필수 패키지 및 콘텐츠가 제거된 상태라는 의미이다. 이러한 최소화된 환경에서는 man 명령어의 설명 페이지가 제공되지 않는다.

아래의 명령어는 최소화된 시스템에서 제거된 콘텐츠를 복원하는 데 사용된다.

sudo unminimize

이렇게 하면 man 페이지를 포함한 문서화가 복원되고 man ls와 같은 명령어를 사용할 수 있게 된다.

 

man 명령어는 “manual”의 약자로, 리눅스와 유닉스 계열 시스템에서 명령어 및 프로그램에 대한 설명서(manual page)를 표시하는 데 사용된다. 사용자가 명령어의 사용법, 옵션, 인자, 예제 등을 확인할 수 있게 도와준다.

man command_name

command_name 자리에 확인하고자 하는 명령어나 프로그램 이름을 입력하면 해당 명령어의 설명서가 출력된다.

 

/(슬래시)문자열을 입력하면 원하는 내용을 검색할 수도 있다.

->ex) /help

검색 결과가 여러개일 경우 n을 누르면 다음 검색 결과로 이동한다.

 

나가려면 q 를 누르면 된다.

 

파일 목록/내용 조회

ls

ls 명령어는 현재 경로의 디렉토리와 파일을 확인할 수 있는 명령어이다. 기본적으로 ls 명령어를 입력하면 현재 디렉토리에 있는 파일과 디렉토리의 목록이 출력된다.

추가적인 옵션을 사용하면 더 많은 정보를 볼 수 있다.

  • ls -l: 파일 및 디렉토리의 세부 정보(권한, 소유자, 파일 크기, 수정 날짜 등)를 포함한 목록을 출력한다.
  • ls -a: 숨김 파일(파일명 앞에 .이 있는 파일 포함)을 포함하여 모든 파일과 디렉토리를 출력한다.
  • ls -d */: 현재 디렉토리 내의 디렉토리만 출력한다.

 

cat

cat 명령어는 “concatenate”의 약자로, 파일의 내용을 출력하거나 여러 파일을 결합하여 표시하고, 새로운 파일을 생성할 때 사용된다. 주로 파일의 내용을 빠르게 확인하기 위해 사용된다.

 

  • 파일 내용 출력
cat filename.txt

 

  • 여러 파일 결합 및 출력
cat file1.txt file2.txt

여러 파일의 내용을 한 번에 결합하여 출력한다.

 

  • 파일 내용 결합 후 새로운 파일 생성
cat file1.txt file2.txt > combined.txt

file1.txt와 file2.txt의 내용을 결합하여 combined.txt라는 새로운 파일에 저장한다.

 

  • 파일에 내용 추가
cat file3.txt >> existing_file.txt

file3.txt의 내용을 existing_file.txt의 끝에 추가한다.

 

주요 옵션

  • -n: 출력되는 각 라인에 번호를 붙여서 보여준다.
cat -n filename.txt

 

  • -b: 빈 줄을 제외한 각 라인에 번호를 붙인다.
cat -b filename.txt

 

  • -s: 연속된 빈 줄을 한 줄로 축소하여 출력한다.
cat -s filename.txt

 

head 및 tail

head와 tail 명령어는 파일의 내용을 부분적으로 출력할 때 사용하는 명령어이다. 두 명령어 모두 텍스트 파일의 특정 부분을 확인할 때 유용하다.

head

head는 기본적으로 파일의 처음 몇 줄을 출력하는 명령어이다. 기본값으로 처음 10줄을 출력하지만, 옵션을 사용하여 출력할 줄 수를 조정할 수 있다.

head filename.txt

 

  • -n: 출력할 줄 수를 지정한다.
head -n 5 filename.txt

filename.txt의 처음 5줄을 출력한다.

 

tail

tail은 기본적으로 파일의 마지막 몇 줄을 출력하는 명령어이다. 기본값으로 마지막 10줄을 출력하지만, 옵션을 사용하여 출력할 줄 수를 변경할 수 있다.

tail filename.txt

 

  • -n: 출력할 줄 수를 지정한다.
tail -n 15 filename.txt

filename.txt의 마지막 15줄을 출력한다.

 

  • -f: 실시간으로 파일의 추가되는 내용을 출력한다. 로그 파일 모니터링에 주로 사용된다.
tail -f logfile.txt

logfile.txt의 내용을 실시간으로 모니터링하며, 새로운 내용이 추가될 때마다 업데이트되어 출력된다.

 

검색 및 탐색

grep

grep 명령어는 주어진 패턴을 파일이나 표준 입력에서 검색하고, 일치하는 줄을 출력하는 강력한 텍스트 검색 도구이다. 주로 특정 텍스트 패턴을 찾아내거나 필터링할 때 사용된다.

grep "pattern" filename

filename에서 pattern이 포함된 줄을 출력한다.

 

  • -i (대소문자 구분 없이 검색)
grep -i "pattern" filename

 

  • -r (재귀적으로 디렉토리 검색)
grep -r "pattern" /path/to/directory

 

  • -n (줄 번호 함께 출력)
grep -n "pattern" filename

 

  • -v (일치하지 않는 줄 출력)
grep -v "pattern" filename

 

  • -c (일치하는 줄 수 출력)
grep -c "pattern" filename

 

grep 명령어는 파이프(|)와 결합하여 다른 명령어의 출력을 필터링할 때 매우 유용하다. 파이프는 한 명령어의 출력을 다른 명령어의 입력으로 전달하는 기능을 제공한다.

 

  • 명령어 출력에서 특정 패턴 찾기
ls -l | grep "txt"

 

  • 로그 파일에서 특정 키워드 찾기
cat /var/log/syslog | grep "error"

 

  • 디렉토리 내 파일명 필터링
find /path/to/dir -type f | grep "log"

 

 

find

find 명령어는 리눅스와 유닉스 계열 시스템에서 파일과 디렉토리를 검색하는 데 사용되는 강력한 도구이다. 다양한 옵션과 조건을 이용해 원하는 파일이나 디렉토리를 효율적으로 찾을 수 있다.

 

  • 특정 경로에서 파일 찾기
find /path/to/search -name "filename"

지정된 경로에서 filename이라는 이름의 파일을 찾는다. 이름은 대소문자를 구분한다.

 

  • 대소문자를 구분하지 않고 파일 찾기
find /path/to/search -iname "filename"

-iname 옵션은 대소문자를 구분하지 않고 파일을 찾는다.

 

  • 특정 확장자의 파일 찾기
find /path/to/search -name "*.txt"

 

  • 특정 크기 이상의 파일 찾기
find /path/to/search -size +100M

100MB보다 큰 파일을 찾는다. 크기는 M(메가바이트), K(킬로바이트), G(기가바이트) 등의 단위로 지정할 수 있다.

 

  • 파일 삭제
find /path/to/search -name "*.tmp" -type f -delete

.tmp 파일을 찾고 삭제한다. 이 명령어는 주의해서 사용해야 한다.

 

  • 파이프와 결합
find . | grep "conf"

현재 디렉토리(.)와 모든 하위 디렉토리에서 conf라는 문자열이 포함된 파일 또는 디렉토리를 찾는다.

 

  • find 명령어는 논리 연산자(-and, -or, !)를 사용하여 검색 조건을 조합할 수 있다.
find /path/to/search -name "*.sh" -or -name "*.py"

.sh 파일이나 .py 파일을 찾는다.

 

find /path/to/search ! -name "*.bak"

.bak 확장자가 아닌 파일을 찾는다.

 

압축 및 해제

특징 tar gzip zip
기능 파일 및 디렉토리를 아카이브(묶기) 단일 파일 압축 다중 파일/디렉토리 아카이브 및 압축
확장자 .tar .gz .zip
압축 지원 압축 기능 없음 (gzip 등과 함께 사용) 압축 기능 있음 압축 기능 있음
압축 후 원본 파일 유지 기본적으로 삭제됨 유지
복원 시 구조 여러 파일 및 디렉토리 복원 단일 파일 복원 여러 파일 및 디렉토리 복원
사용 예시 tar -cvf archive.tar /path/to/files gzip filename zip archive.zip file1 file2 dir/
주 사용 환경 리눅스/유닉스 리눅스/유닉스 Windows, 리눅스, macOS

 

tar

tar 명령어는 리눅스 및 유닉스 계열 시스템에서 여러 파일을 하나의 파일로 묶는 아카이브(archive)를 생성하거나, 그 아카이브를 해제하는 데 사용된다. 일반적으로 파일 백업 및 배포에 많이 활용된다. tar는 “tape archive”의 약자이다.

  • 아카이브 생성: 여러 파일을 하나의 .tar 파일로 묶는다.
  • 압축: 아카이브 파일을 생성하면서 동시에 압축할 수 있다.
  • 아카이브 해제: .tar 파일을 해제하여 파일과 디렉토리를 원래대로 복원한다.

 

아카이브 생성

tar -cvf archive.tar /path/to/files

/path/to/files 경로의 파일들을 archive.tar라는 아카이브 파일로 묶는다.

  • -c: 새 아카이브 생성 (create)
  • -v: 진행 과정을 자세히 출력 (verbose)
  • -f: 파일 이름 지정 (file)

이 명령어는 단순히 파일을 묶는 것일 뿐 압축하는 것은 아니다.

 

  • 압축된 아카이브 생성
tar -czvf archive.tar.gz /path/to/files

 

-z: 아카이브 한 파일을 gzip을 사용해 압축 (compress with gzip)

 

  • 아카이브 해제
tar -xvf archive.tar

 

-x: 아카이브 해제 (extract)

archive.tar 파일의 내용을 현재 디렉토리에 해제한다.

 

  • 압축된 아카이브 해제
tar -xzvf archive.tar.gz

-z: gzip 압축 해제

 

  • 특정 디렉토리에 해제
tar -xvf archive.tar -C /target/directory

 

-C: 특정 디렉토리 지정 (change to directory)

archive.tar 파일의 내용을 /target/directory 경로에 해제한다.

 

gzip / gunzip

gzip과 gunzip은 파일을 압축하거나 압축 해제하는 데 사용되는 명령어이다. gzip은 파일을 압축하는 데 사용되며, gunzip은 gzip으로 압축된 파일을 해제하는 데 사용된다. 두 명령어 모두 기본적으로 .gz 확장자를 사용한다.

단일 파일만 압축할 수 있으며, 압축된 파일은 .gz 확장자를 갖는다.
다수의 파일이나 디렉토리를 압축하려면 먼저 tar과 결합하여 하나의 아카이브 파일을 만들고, 이를 gzip으로 압축해야 한다. 

gzip은 파일을 압축하여 파일 크기를 줄인다. 원본 파일은 압축된 파일로 대체되며, 압축된 파일의 이름은 원래 파일 이름에 .gz 확장자가 추가된다.

 

gzip filename

filename 파일을 filename.gz로 압축한다.

 

  • 디렉토리 내 파일 압축
gzip -r mydirectory

mydirectory 내의 모든 파일을 .gz 확장자로 압축한다.

이 명령어는 mydirectory 디렉토리 내의 각 파일을 개별적으로 압축할 뿐, 디렉토리 전체를 하나의 파일로 묶어 압축하지 않는다.

 

  • -k: 압축 후에도 원본 파일을 그대로 유지한다.
gzip -k filename

 

  • -d: 압축 해제(이 경우 gunzip을 사용하는 것과 동일).
gzip -d filename.gz

 

 

gunzip은 gzip으로 압축된 .gz 파일을 원래 상태로 복원하는 데 사용된다.

gunzip filename.gz

filename.gz를 압축 해제하여 원본 filename으로 복원한다.

 

  • -k: 압축 해제 후에도 .gz 파일을 유지한다.
gunzip -k filename.gz

 

zip / unzip

zip과 unzip 명령어는 파일과 디렉토리를 압축하고 해제하는 데 사용되는 명령어이다. zip은 파일이나 디렉토리를 .zip 형식으로 압축하는 데 사용되고, unzip은 .zip 파일을 해제하여 원래 파일이나 디렉토리를 복원하는 데 사용된다.

zip은 여러 파일과 디렉토리를 하나의 .zip 파일로 압축할 수 있다.

zip archive.zip file1.txt

 

  • 파일과 디렉토리 함께 압축
zip archive.zip file1.txt file2.txt myfolder/

이 명령어는 file1.txt, file2.txt, 그리고 myfolder/와 그 안의 모든 내용을 압축하여 archive.zip에 포함시킨다.

 

  • 디렉토리만 압축
zip -r archive.zip myfolder/

myfolder/ 내 모든 파일과 하위 디렉토리를 포함하여 압축한다.

 

  • unzip은 .zip 파일을 압축 해제하는 명령어이다.
unzip archive.zip

 

  • -d: 특정 디렉토리에 해제.
unzip archive.zip -d /path/to/destination

/path/to/destination 디렉토리에 압축을 해제한다.

 

  • -o: 동일한 이름의 파일이 존재할 경우 덮어쓰기.

 

시간

date

date 명령어는 현재 날짜와 시간을 출력할 때 사용되며, 다양한 형식으로 출력할 수 있도록 옵션을 제공한다. + 뒤에 형식을 지정하여 원하는 날짜와 시간 부분만 출력할 수 있다. 각 옵션은 다음과 같은 의미를 가진다.

  • +%y: 연도를 두 자리로 출력한다 (예: 24).
  • +%Y: 연도를 네 자리로 출력한다 (예: 2024).
  • +%m: 월을 두 자리 숫자로 출력한다 (예: 11).
  • +%d: 일을 두 자리 숫자로 출력한다 (예: 04).

 

각 옵션을 결합하여 날짜를 특정 형식으로 출력할 수도 있다.

date +%Y-%m-%d

 

 

cal

cal 명령어는 현재 월 또는 지정한 월과 연도의 달력을 출력하는 데 사용된다.

  • 현재 월의 달력 표시
cal

 

  • 특정 연도의 달력 출력
cal 2024

 

  • 특정 월과 연도의 달력 출력
cal 11 2024

 

기타

echo

echo 명령어는 문자열을 출력하는 데 사용된다. 주로 텍스트 메시지를 표시하거나, 변수의 값을 출력하고, 스크립트에서 데이터를 전달할 때 많이 사용된다.

echo "Hello, World!"

 

  • 변수 값 출력
name="Alice"
echo "Hello, $name"

 

  • 특수 문자 해석

-e 옵션을 사용하면 \n, \t, \\ 등의 특수 문자를 해석하여 출력한다.

echo -e "Hello\nWorld"

 

  • 명령어 결과 출력
echo "Today's date is $(date)"

$(command) 구문을 사용하면 명령어의 결과를 출력에 포함할 수 있다.

 

  • 환경 변수 출력
echo $PATH

시스템의 PATH 환경 변수를 출력한다.

 

  • 파일에 텍스트 저장
echo "This is a test" > test.txt

"This is a test"라는 텍스트를 test.txt 파일에 저장한다.

 

  • 텍스트를 파일에 추가
echo "Appended text" >> test.txt

test.txt 파일에 "Appended text"를 추가한다.

 

history

history 명령어는 사용자가 입력한 이전 명령어들의 목록을 표시하는 데 사용된다. 리눅스와 유닉스 계열 시스템에서 매우 유용하며, 사용자가 실행했던 명령어를 추적하거나 다시 실행할 때 활용된다.

 

  • 기본 사용법
history

이 명령어는 사용자가 실행한 명령어 목록을 출력한다. 출력된 목록에는 각 명령어에 번호가 매겨져 있어 특정 명령어를 쉽게 참조할 수 있다.

 

  • 특정 개수의 명령어 출력
history 10

최근 10개의 명령어만 출력한다.

 

  • 특정 명령어 다시 실행
!123

123번 명령어를 다시 실행한다. ! 뒤에 명령어 번호를 붙이면 해당 명령어가 재실행된다.

 

touch

touch 명령어는 주로 빈 파일을 생성하거나, 파일의 마지막 수정 시간을 업데이트하는 데 사용된다.

touch newfile.txt

파일이 이미 존재할 경우, touch 명령어는 파일의 수정 시간과 접근 시간을 현재 시간으로 업데이트한다.

 

mv

mv 명령어는 파일이나 디렉토리를 이동하거나 이름을 변경할 때 사용한다.

 

  • 파일 이동
mv file.txt /path/to/directory/

이 명령어는 file.txt를 지정한 디렉토리(/path/to/directory/)로 이동시킨다.

 

  • 파일 이름 변경
mv oldname.txt newname.txt

이 명령어는 oldname.txt의 이름을 newname.txt로 변경한다.

 

자주 사용하는 옵션

  • -i (interactive): 대상 파일이 이미 존재할 경우, 덮어쓰기를 확인하는 메시지를 출력한다.
  • -f (force): 파일을 강제로 덮어쓴다. 대상 파일이 이미 존재해도 사용자에게 확인하지 않고 덮어쓴다.
  • -n (no-clobber): 대상 파일이 이미 존재하면 아무 작업도 하지 않고 덮어쓰지 않는다.

 

cp

리눅스에서 cp 명령어는 파일이나 디렉토리를 복사하는 데 사용된다. 이 명령어를 통해 파일을 다른 위치에 복사하거나 이름을 바꿔서 복사할 수 있으며, 디렉토리 전체를 복사할 수도 있다.

 

  • 파일 복사
cp file1.txt /home/user/Documents/

이 명령어는 file1.txt를 /home/user/Documents/ 디렉토리로 복사한다.

 

  • 파일 복사 및 이름 변경
cp file1.txt newfile.txt

이 명령어는 file1.txt를 복사하여 newfile.txt라는 이름으로 저장한다.

 

  • 여러 파일 복사
cp file1.txt file2.txt /home/user/Documents/

 

file1.txt와 file2.txt를 /home/user/Documents/ 디렉토리에 복사한다.

 

자주 사용하는 속성

  • -r 또는 -R (recursive): 디렉토리를 복사할 때 사용한다. 하위 디렉토리와 파일까지 모두 복사한다.
  • -i (interactive): 대상 파일이 이미 존재할 경우 덮어쓸지 여부를 묻는다.
  • -f (force): 대상 파일이 이미 존재하더라도 강제로 덮어쓴다. 사용자에게 확인을 묻지 않는다.
  • -u (update): 대상 파일이 존재할 경우, 원본 파일이 더 최신일 때만 복사한다. 파일이 변경되었을 때만 덮어쓰고, 그렇지 않으면 복사하지 않는다.

 

rm

  • 파일 삭제
rm file1.txt

 

  • 여러 파일 삭제
rm file1.txt file2.txt file3.txt

file1.txt, file2.txt, file3.txt를 동시에 삭제한다.

 

자주 사용하는 옵션

  • -r 또는 -R (recursive): 디렉토리를 삭제할 때 사용한다. 하위 디렉토리와 파일까지 모두 삭제한다.
  • -f (force): 강제로 삭제한다. 확인 없이 바로 삭제하며, 존재하지 않는 파일에 대해 오류 메시지를 표시하지 않는다.

 

rm -rf: 이 조합은 강제 삭제와 재귀 삭제를 결합한 것으로, 디렉토리와 하위 모든 파일 및 디렉토리를 강제로 삭제한다. 사용 시 매우 주의해야 한다.

rm -rf /path/to/directory

 

 

패키지 매니저

apt

apt는 Debian 계열의 리눅스 배포판(예: Ubuntu)에서 패키지를 관리하는 명령어이다.

apt 명령어는 패키지 설치, 업데이트, 업그레이드, 삭제 등 다양한 패키지 관리 작업을 수행할 수 있도록 해준다.

apt-get, apt-cache 등의 명령어를 통합하여 사용하기 쉽게 개선된 명령어이다.

 

  • 패키지 목록 업데이트
sudo apt update

시스템에 설치 가능한 패키지의 목록을 업데이트한다. 이를 통해 새로 추가된 패키지나 업데이트된 패키지 정보를 알 수 있다.

 

  • 패키지 업그레이드
sudo apt upgrade

설치된 모든 패키지를 최신 버전으로 업그레이드한다. 업그레이드 시 사용자 승인이 필요할 수 있다.

 

  • 패키지 제거
sudo apt remove package-name

package-name에 해당하는 패키지를 제거한다. 설정 파일은 남아있을 수 있다.

 

  • 패키지 완전 제거(설정 파일 포함)
sudo apt purge package-name

package-name의 패키지와 설정 파일을 모두 제거한다.

 

  • 시스템 전체 업그레이드
sudo apt full-upgrade

기존 패키지를 업그레이드하면서 필요에 따라 새로운 패키지를 설치하거나 기존 패키지를 제거할 수 있다. upgrade와 달리 의존성 충돌을 해결하면서 업그레이드한다.

 

  • 패키지 검색
apt search package-name

package-name 패키지의 상세 정보를 확인할 수 있다.

 

  • 설치된 모든 패키지의 목록을 표시
apt list --installed

패키지 이름, 버전, 설치 여부 등이 출력

이글은 혼자 공부하는 네트워크(저자 : 강민철)의 책과 강의 내용을 개인적으로 정리하는 글임을 알립니다.


프로토콜(protocol)

현대 인터넷은 호스트 간 패킷을 교환하는 방식으로 대부분 패킷 교환 방식을 사용한다.

언어가 정보를 주고받기 위해 사회적으로 합의된 의사소통 방식이라면, 프로토콜은 노드 간에 정보를 올바르게 주고받기 위해 합의된 규칙이나 방법을 의미한다.

즉, 서로 다른 통신 장치들이 정보를 주고받으려면 프로토콜이 통해야 한다. 우리가 인터넷을 이용할 수 있는 것은 모두 상대 호스트와 동일한 프로토콜을 사용하기 때문이다.

다만 일상 속 언어와는 달리 통신 과정에서는 하나의 프로토콜만 사용하지 않는다. 일반적으로는 여러 프로토콜을 함께 사용한다.

 

모든 프로토콜에는 저마다의 목적과 특징이 있다.

프로토콜의 존재 이유는 정해져 있기 때문에 저마다 목적과 특징이 다양하다.

프로토콜마다 목적과 특징이 다르기에 이에 부합하는 정보도 달라질 수 있으며, 따라서 특정 프로토콜로 주고받는 패킷의 부가 정보도 달라질 수 있다.

즉, 프로토콜마다 패킷의 헤더 내용이 달라질 수 있다.

 

네트워크 참조 모델

네트워크를 통해 정보를 주고 받을 때는 정형화된 여러 단계를 거친다.

이 과정은 계층으로 표현할 수 있다.

이렇게 통신이 일어나는 각 과정을 계층으로 나눈 구조를 네트워크 참조 모델이라고 한다.

계층으로 표현한다는 점에서 네트워크 계층 모델이라 부르기도 한다.

 

이와 같이 통신 과정을 계층으로 나눈 이유는 아래와 같이 크게 두 가지이다.

  1. 네트워크 구성과 설계가 용이하다.
  2. 네트워크 문제 진단과 해결이 용이하다.

 

OSI 모델

OSI 모델은 국제 표준화 기구에서 만든 네트워크 참조 모델이다.

통신 단계를 7개의 계층으로 나누는데, 최하위 계층에서 최상위 계층순으로 각각 물리, 데이터 링크, 네트워크, 전송, 세션, 표현, 응용 계층이다.

 

TCP/IP 모델

OSI 모델은 주로 네트워크를 이론적으로 기술하고 이해할 때 사용하는 반명에 TCP/IP 모델은 이론 보다는 구현에 중점을 둔 네트워크 참조 모델이다.

OSI 모델의 목적이 '이상적 설계'에 가깝다면, TCP/IP 모델은 '실용적 구현'에 가깝다.

TCP/IP 모델은 TCP/IP 4계층, 인터넷 프로토콜 스위트, TCP/IP 프로토콜 스택이라고도 부른다.

TCP와 IP 모두 프로토콜의 이름이다.

OSI 7계층, TCP/IP 4계층은 사실 아무것도 해주지 않는다.

네트워크 참조 모델은 네트워크 프로토콜과 장비가 따라야 할 필수 규칙이 아닌 가이드라인이며, 특정 계층으로 나누어 설명하기 어려운 프로토콜이나 장비가 존재할 수 있다. 네트워크 참조 모델은 네트워크 작동을 직접적으로 책임지지 않고, 통신 구조를 이해하기 위한 참고용 모델이다.

 

 

TCP/IP 모델 확장

전통적인 TCP/IP 모델에서의 최하위 계층은 OSI 모델에서 물리 계층보다는 데이터 링크 계층 역할을 수행하는 쪽에 가까웠다.

TCP/IP 몯레에는 OSI 모델에서의 물리 계층에 해당하는 개념이 없다는 견해도 있다.

그래서 많은 공식 문서와 전공 서적에서는 OSI 모델과 TCP/IP 모델을 대응하여 설명하기 위햇 TCP/IP 모델에 물리 계층을 추가해 TCP/IP 모델을 5계층으로 확장하여 기술하기도 한다.

 

 

캡슐화와 역캡슐화

패킷은 송신 과정에서 캡슐화가 이루어지고, 수신 과정에서 역캡슐화가 이뤄진다.

송수신하는 메시지는 송신지 입장에서는 가장 높은 계층에서부터 가장 낮은 계층으로 이동하고, 수신지 입장에서는 가장 낮은 계층에서부터 가장 높은 계층으로 이동한다.

 

캡슐화(encapsulation)

패킷 교환 네트워크에서 메시지는 패킷 단위로 송수신된다.

  • 패킷은 헤더와 페이로드, 때로는 트레일러를 포함하여 구성됨
  • 프로토콜의 목적과 특징에 따라 헤더의 내용은 달라질 수 있다.

어떤 정보를 송신할 때 각 계층에서 상위 계층으로부터 내려받은 패킷을 페이로드로 삼아, 프로토콜에 걸맞은 헤더(혹은 트레일러)를 덧붙인 후 하위 계층으로 전달한다.

즉, 한 단계 아래 계층은 바로 위의 계층으로부터 받은 패킷에 헤더 및 트레일러를 추가해 나간다.

이렇게 송신 과정에서 헤더 및 트레일러를 추가해 나가는 과정을 캡슐화라고 부른다.

 

역캡슐화(decapsulation)

역캡슐화는 어떤 메시지를 수신할 때는 캡슐화 과정에서 붙였던 헤더 및 트레일러를 각 계층에서 확인한 뒤 제거한다.

이를 역캡슐화라고 한다.

 

PDU(Protocol Data Unit)

각 계층에서 송수신되는 메시지의 단위를 PDU라고 한다. 즉, 상위 계층에서 전달받은 데이터에 현재 계층의 프로토콜 헤더 및 트레일러를 추가하면 현재 계층의 PDU가 된다.

응용 계층의 PDU는 메시지(message), 네트워크 계층의 PDU는 IP 데이터그램(IP datagram), 물리 계층의 PDU는 심볼(symbol)이라 지칭하기도 한다.

  • PDU는 주로 전송 계층 이하의 메시지를 구분하기 위해 사용한다. 전송 계층보다 높은 계층에서는 일반적으로 데이터 혹은 메시지로만 지칭하는 경우가 많다.
  • 전송 계층의 PDU는 TCP 프로토콜이 사용되었을 경우에는 세그먼트, UDP 프로토콜이 사용되었을 경우에는 데이터그램이 된다.
  • 참고로 패킷이라는 용어는 패킷 교환 네트워크에서 쪼개어져 전송되는 단위를 통칭하기 위한 일반적인 용어로 사용되기도 하지만, 네트워크 계층에서의 송수신 단위를 지칭하기 위해 사용되기도 한다.
    네트워크 계층의 PDU인 패킷은 후자이고, 패킷 교환 네트워크에서 사용된 패킷은 전자인 셈이다.
    이 글에서 혼란을 방지하기 위해 네트워크 계층의 PDU는 IP 패킷이라고 지칭한다.

 

트래픽과 네트워크 성능 지표

  • 트래픽(traffic) : 네트워크 내의 정보량을 의미

특정 노드에 트래픽이 몰린다는 것은 해당 노드가 특정 시간동안 처리해야 할 정보가 많음을 의미한다.

이 경우 해당 노드에 과부하가 생길 수 있다. 컴퓨터에서 여러 프로그램을 동시에 실행하면 CPU가 뜨거워지며 성능이 저하되는 것처럼, 트래픽이 몰려 특정 노드에 과부하가 생기면 성능의 저하로 이어질 수 있다.

 

네트워크의 성능을 평가할 수 있는 세가지 대중적인 지표가있다.

  • 처리율: 처리율(throughput)은 네트워크를 통해 실제로 전송되는 정보의 양을 의미하며, 주로 bps, Mbps, Gbps와 같은 단위로 표현된다. 이 값은 순간적인 전송 속도를 보여주며, 네트워크의 실시간 성능을 측정할 때 사용된다.
  • 대역폭: 대역폭(bandwidth)은 네트워크가 최대로 전송할 수 있는 데이터 양을 나타낸다. 이는 주로 bps, Mbps, Gbps 단위로 표현되며, 넓은 대역폭은 더 많은 데이터를 송수신할 수 있는 능력을 의미한다.
  • 패킷 손실: 패킷 손실(packet loss)은 네트워크에서 전송된 패킷이 목적지에 도달하지 못하는 상황을 말한다. 이는 네트워크 과부하나 장애로 인해 발생할 수 있으며, 손실된 패킷의 비율로 표현된다.

이글은 혼자 공부하는 네트워크(저자 : 강민철)의 책과 강의 내용을 개인적으로 정리하는 글임을 알립니다.


네트워크란 여러 장치가 서로 연결되어 정보를 주고받을 수 있는 통신망이다.

네트워크는 그래프라는 자료구조 모양을 띄고 있다.

그래프는 아래의 그림처럼 노드와 노드를 연결하는 간선으로 이루어진 자료구조이다.

 

 

네트워크의 기본 구조

호스트(host)

네트워크의 가장자리에 위치한 노드는 네트워크를 통해 흐르는 정보를 최초로 생성 및 송신하고, 최종적으로 수신한다.

우리가 일상에서 사용하는 네트워크 기기 대부분이 여기에 속한다고 봐도 무방하다.

이러한 가장자리 노드를 네트워크에서 호스트라고 부른다.

 

때로는 호스트가 네트워크상에서 특정한 역할을 수행하기도 하는데, 대표적인 역할로는 서버와 클라이언트가 있다.

  • 서버 : 어떠한 서비스를 제공하는 호스트
  • 클라이언트 : 서버에게 어떠한 서비스를 요청하고 서버의 응답을 제공받는 호스트

 

 

네트워크 장비

네트워크 가장자리에 위치하지 않은 노드, 즉 호스트간 주고받을 정보가 중간에 거치되는 노드를 중간 노드라고 한다.

대표적으로 이더넷 허브, 스위치, 라우터, 공유기 등이 있다.

네트워크 장비는 호스트 간 주고받는 정보가 원하는 수신지까지 안정적이고 안전하게 전송될 수 있도록 한다.

호스트, 네트워크 장비, 서버, 클라이언트는 완전히 배타적인 개념이 아니다.

이들은 그저 노드의 역할에 따라 구분한 기준에 불과하다. 현대 네트워크에서 이와 같은 개념들이 칼로 자르듯 명확하게 구분되지 않는다.
일반적인 관점으로 아래와 같이 기억해야 한다.

-호스트 역할을 수행할 수 있는 노드, 네트워크 장비 역할을 수행할 수 있는 노드가 있다.
-서버 역할을 수행할 수 있는 노드, 클라이언트 역할을 수행할 수 있는 노드가있다.

 

 

통신 매체

각 노드를 연결하는 간선이 통신 매체이다.

통신 매체는 노드들을 유선으로 연결하는 유선 매체, 무선으로 연결하는 무선매체가 있다.

 

 

메시지

통신 매체로 연결된 노드가 주고받는 정보를 메시지라고 한다.

네트워크는 가장자리 노드인 호스트, 중간 노드인 네트워크 장비, 노드들을 연결하는 간선인 통신 매체, 노드들이 주고받는 정보인 메시지로 구성된다.

 

범위에 따른 네트워크 분류

네트워크의 구성 범위가 다양한 만큼, 네트워크를 범위에 따라 크게 LAN과 WAN으로 구분한다.(CAN과 MAN도 존재하긴 함)

 

LAN

LAN은 Local Area Network의 약자로 이름 그대로 가까운 지역을 연결한 근거리 통신망을 의미한다.

 

WAN

WAN은 Wide Area Network의 약자로 이름 그대로 먼 지역을 연결하는 광역 통신망을 의미한다.

멀리 떨어진 LAN을 연결할 수 있는 네트워크가 바로 WAN이다.

같은 LAN에 속한 호스트끼리 메시지를 주고받아야 할 때는 인터넷 연결과 같은 WAN이 필요 없지만, 다른 LAN에 속한 호스트와 메시지를 주고받아야할 때는 WAN이 필요하다.

 

우리가 인터넷을 사용하기 위해 접속하는 WAN은 ISP(Internet Service Provider)라는 인터넷 서비스 업체가 구축하고 관리한다.

인터넷을 사용하기 위해 ISP와 계약하여 인터넷 사용 요금을 내는 것은 이러한 이유 때문이다.

 

멀리 떨어진 LAN을 연결하기 위해 특정 조직에서 불특정 다수에게 공개되지 않은 WAN을 얼마든지 구축할 수도 있다.

CAN(Campus Arear Noetwork)은 학교 또는 회사의 여러 건물 단위로 연결되는 규모의 네트워크를 의미함.
MAN(Metropolitan Area Nerwork)은 도시나 대도시 단위로 연결되는 규모의 네트워크를 의미함.

 

 

메시지 교환 방식에 따른 네트워크 분류

네트워크로 메시지를 주고받는 방식은 대표적으로 회선 교환 방식과 패킷 교환 방식으로 나눌 수 있다.

각 방식을 사용하는 네트워크를 각각 회선 교환 네트워크, 패킷 교환 네트워크라고 한다.

 

회선 교환 방식

메시지 전송로인 회선을 설정하고 이를 통해 메시지를 주고받는 방식

회선 교환 네트워크에서는 호스트들이 메시지를 주고받기 전에 두 호스트를 연결한 후, 연결된 경로로 메시지를 주고 받는다.

회선 교환 방식은 두 호스트 사이에 연결을 확보한 후에 메시지를 주고받는 특성 때문에 주어진 시간 동안 전송되는 정보의 양이 비교적 일정하다는 장점이 있다.

 

회선 교환 네트워크가 올바르게 동작하기 위해서는 호스트 간의 회선을 적절하게 설정해야 한다.

이 역할을 수행하는 회선 교환 네트워크 장비로는 회선 스위치가 있다.
즉, 회선 스위치는 호스트 사이에 일대일 전송로를 확보하는 네트워크 장비이다.

하지만 회선 교환 방식에는 문제가 있다.

가능한 모든 회선에 끊임없이 메시지가 흐르고 있어야만 회선의 이용 효율이 높아진다. 하지만 메시지를 주고받지 않으면서 회선을 점유하는 것은 이용 효율이 낮아진다라고 볼 수 있다.

따라서 모든 회선에서 끊임없이 메시지가 흐르지 않을수록 이용 효율은 낮아질 수 밖에 없다.

 

패킷 교환 방식

패킷 교환 방식은 회선 교환 방식의 문제점을 해결한 방식으로, 메시지를 패킷이라는 작은 단위로 쩌개어 전송한다.

여기서 패킷은 패킷 교환 네트워크상에서 송수신되는 메시지 단위이다.

현재 인터넷은 대부분 패킷 교환 방식을 이용한다.

 

예를 들어서 패킷 교환 방식으로 2GB 크기의 영화 파일을 다운로드 한다면 영화 파일이 한 번에 컴퓨터로 전송되는 것이 아니라 패킷의 크기만큼 분할되어 전송된다.

이렇게 쪼개진 패킷들은 수신자의 컴퓨터에 도달한 뒤 재조립된다.

 

패킷 교환 네트워크는 두 호스트가 하나의 전송 경로를 점유하지 않기에 네트워크 이용 효율이 상대적으로 높다.

 

패킷 교환 방식은 정해진 경로만으로 메시지를 송수신하지 않는다. 이 과정에서 메시지는 다양한 중간 노드를 거칠 수 있는데, 이때 중간 노드인 패킷 스위치는 패킷이 수신지까지 올바르게 도달할 수 있도록 최적의 경로를 결정하거나 패킷의 송수신지를 식별한다.

 

대표적인 패킷 스위치 네트워크 장비로는 라우터(router)와 스위치(switch)가 있다.

 

패킷을 통해 전송하고자 하는 데이터를 페이로드(payload)라고 하고, 페이로드와 더블어 헤더(header)라는 정보도 패킷 앞에 포함된다. 때로는 트레일러(trailer)라는 정보가 포함되기도 한다.

헤더와 트레일러는 패킷에 붙는 일종의 부가 정보, 내지는 제어 정보이다. 즉, 페이로드가 택배 안에 담을 물품이라면 헤더나 트레일러는 택배 상자에 붙이는 송장과 같다.

 

송수신지 유형에 따른 전송방식

유니캐스트: 일반적인 형태의 송수신 방식으로, 하나의 수신지에 메시지를 전송하는 방식, 송신지와 수신지가 일대일로 메시지를 주고받는 경우

브로드캐스트: 자신을 제외한 네트워크상의 모든 호스트에게 전송하는 방식, 브로드 캐스트가 전송되는 범위를 브로드캐스트 도메인이라고 한다. 즉, 브로드캐스트의 수신지는 브로드캐스트 도메인이며 이는 자신을 제외한 네트워크상의 모든 호스트이다.

멀티캐스트: 네트워크 내의 동일 그룹에 속한 호스트에게만 전송하는 방식

애니캐스트: 네트워크 내의 동일 그룹에 속한 호스트 중 가장 가까운 호스트에게 전송하는 방식

환경

  • AWS EC2 (ubuntu)

 

Docker Compose 로 여러개의 도커 컨테이너가 묶여있고, EC2 인스턴스로 배포된 상황에서 여러가지 이슈들로 인스턴스가 재부팅 되거나 애플리케이션이 종료될 수 있다.

이때 재부팅되거나 배포한 애플리케이션이 종료되었을 때 자동으로 애플리케이션이 시작되게끔 할 수 있다.

먼저 Docker Compose 애플리케이션 서비스 파일을 생성해야 한다.

sudo vi /etc/systemd/system/docker-compose-app.service

 

이후 아래의 서비스 파일을 입력한다.

[Unit]
Description=Docker Compose Application Service
After=docker.service
Requires=docker.service

[Service]
Restart=always
RestartSec=10s  # 재시작 간격 설정
WorkingDirectory=<compose.yml이 있는 폴더 경로>
ExecStart=/usr/bin/docker compose up -d
#ExecStop=/usr/bin/docker compose down 필요시 주석 해제

[Install]
WantedBy=multi-user.target
  • RestartSec=10s 옵션을 추가하면, 서비스가 실패한 후 재시작하기 전까지 기다리는 시간을 10초로 설정하게 된다. 즉, 서비스가 비정상적으로 종료되었을 때, systemd는 10초 후에 다시 서비스를 재시작한다는 의미이다.
  • ExecStop=/usr/bin/docker compose down은 systemd 서비스가 종료될 때 실행할 명령어를 정의하는 옵션이다. 이 설정은 시스템이 docker-compose-app.service를 중지하거나, 재시작할 때 기존에 실행 중인 Docker Compose 애플리케이션을 깨끗하게 종료시키는 역할을 한다. 나는 이 설정으로 애플리케이션이 시작과 종료가 계속 반복되어 주석처리하였다.
  • WorkingDirectory=<compose.yml이 있는 폴더 경로> 는 말그대로 compose.yml이 있는 폴더 경로를 설정한다.
    예를들어, /home/ubuntu/server/compose.yml이 있는경우
    WorkingDirectory=/home/ubuntu/server  이렇게 지정하면 된다.

 

이후 서비스 파일을 저장한 후, systemd에 수정된 파일을 적용하고 다시 시작한다.

sudo systemctl daemon-reload
sudo systemctl start docker-compose-app

 

마지막으로 서비스 상태를 확인하여 잘 실행되고 있는지 확인한다.

sudo systemctl status docker-compose-app

 

개발환경

  • SpringBoot v3.3.3
  • AWS EC2 (ubuntu)
  • RDS
  • Docker Compose
  • GithubActions

 

로그를 파일로 저장하기(환경 분리 포함)

스프링부트에서 local 환경과 prod 환경을 분리하여 로그를 남기는 방법은 아래의 글에서 참고하였다.

https://blog.pium.life/server-logging/

 

Logback을 이용해 운영 환경 별 로그 남기기

이 글은 우테코 피움팀 크루 '그레이'가 작성했습니다. 로깅이란 ? 우리가 처음 개발을 할 때 System.out.println(), cout << "hello world" << endl, print() 등으로 원하는 대로 동작하고 있는지 출력하곤 했을

blog.pium.life

 

이 글은 local 환경에서는 콘솔에서 로그를 확인할 수 있고 prod환경에선 info 로그와 error 로그 파일을 분리할 수 있는 설명이 담겨있다.

 

먼저 application.yml에서 프로필을 분리해주어야 한다.

  • application-local.yml
spring:
  config:
    activate:
      on-profile: local

 

  • application-prod.yml
spring:
  config:
    activate:
      on-profile: prod

 

 

이후 /src/main/resources 에 아래의 파일을 정의한다.

이 파일들은 로그 내용을 파일로 저장할 때 쓰는 파일이다.(Logback)

  • console-appender.xml
<included>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>
</included>

 

  • file-info-appender.xml
<included>
    <appender name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>./log/info-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <maxHistory>60</maxHistory>
            <totalSizeCap>5GB</totalSizeCap>
        </rollingPolicy>
    </appender>
</included>

 

  • file-error-appender.xml
<included>
    <appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>./log/error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <maxHistory>60</maxHistory>
            <totalSizeCap>5GB</totalSizeCap>
        </rollingPolicy>
    </appender>
</included>

 

  • logback-spring.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>

    <property name="CONSOLE_LOG_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %clr(%5level) %cyan(%logger) - %msg%n"/>
    <property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %5level %logger - %msg%n"/>

    <!--local-->
    <springProfile name="local">
        <include resource="console-appender.xml"/>

        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <!--prod-->
    <springProfile name="prod">
        <include resource="file-info-appender.xml"/>
        <include resource="file-error-appender.xml"/>

        <root level="INFO">
            <appender-ref ref="FILE-INFO"/>
            <appender-ref ref="FILE-ERROR"/>
        </root>
    </springProfile>

</configuration>

 

이렇게 설정하면 local  환경에서는 콘솔로 로그를 확인할 수 있고, prod 환경에서는 ./log 폴더에 날짜별로 info와 error 로그 내용을 저장할 수 있다.

날짜 뒤에 숫자는 같은 날짜의 로그파일을 구분하는 숫자이고, 로그 파일이 50MB를 넘었을 때 새로운 파일에 로그를 저장하고 숫자가 1 증가하여 저장된다.

  • 예를들어 error-2024-10-11.0.log의 파일 용량이 50mb가 넘었을 시에 error-2024-10-11.1.log이 생성되고 여기에 로그를 마저 저장하는 것이다.

또한 error-2024-10-11.0.log의 파일 용량이 50mb가 넘었을경우 gz라는 확장자로 압축하여 저장하게 된다.

이를 통해 로그 파일의 용량을 최소화하여 저장할 수 있다.

마지막으로 error를 저장하는 로그와 info를 저장하는 로그 파일  각각 60개가 넘었을 시에 가장 오래된 로그파일을 삭제하도록 되어있다.

 

이 로그파일은 ./log 폴더에 저장되는데 내 프로젝트는 도커 환경에서 실행되므로 도커 볼륨을 통하여 도커 컨테이너 외부에 저장할 필요가 있다.

 

Docker Volume 을 이용하여 로그파일을 컨테이너 외부에 저장

먼저 dockerfile은 아래와 같다.

  • dockerfile
# 베이스 이미지로 OpenJDK 17 사용
FROM openjdk:17-jdk

# 애플리케이션을 위한 작업 디렉토리 설정
WORKDIR /spring-boot

# 빌드된 JAR 파일을 컨테이너로 복사
COPY build/libs/*SNAPSHOT.jar promise.jar

# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "/spring-boot/promise.jar"]

WORKDIR 이 /spring-boot로 설정되어있다.

그래서 log 폴더는 컨테이너 내부의 spring-boot/log에 생성된다.

 

  • compose.yml
services:
  ...
    volumes:
      - /home/ubuntu/promise/log:/spring-boot/log
  ...

이 옵션을 통해서 컨테이너에 존재하고 있던 log 폴더를 외부로 빼낼 수 있다.

즉, 도커 컨테이너 내부의 spring-boot/log 폴더를 ec2 인스턴스의 /home/ubuntu/promise/log 폴더와 공유하게 된다.

이렇게 하면 도커 컨테이너가 종료되거나 삭제되어도 로그 파일을 유지할 수 있다.

환경

  • 스프링부트 v3.3.3
  • AWS EC2
  • RDS
  • Docker Compose
  • Github Actions

 

문제 상황

현재 내가 진행하고 있는 프로젝트에서 설정된 시간에 알람 시간을 설정하는 기능이 있다.

DB에 저장할 때 기본값이 오전 8시인데 자꾸 9시간 뒤인 오후 5시로 설정되는 문제를 확인하였다.

이렇게 9시간 차이가 나는 것은 보통 타임존 설정 문제인데, 나는 이미 타임존 관련 문제를 아래와 같이 세팅해둔 상태라서 더욱 당황했다.

 

@PostConstruct 를 이용한 JVM 타임존 설정

@SpringBootApplication
public class PromiseApplication {

	public static void main(String[] args) {
		SpringApplication.run(PromiseApplication.class, args);
	}

	@PostConstruct
	public void init()
	{
		// JVM의 기본 시간대를 Asia/Seoul로 설정
		TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
	}
}

 

RDS의 타임존 설정

 

EC2 인스턴스의 타임존 설정

 

모두 한국 시간으로 되어있었다.

 

하지만 EC2에서 실행중인 스프링부트 프로젝트의 로깅 시간을 보면 자꾸 현재 시간보다 9시간이 느렸다.

 

알람 시간은 9시간 느리고, 로깅 시간은 9시간 빠르고 아주그냥 대 혼란이었다.

 

 

해결 방법

GPT에게도 물어봤지만 전부 해결이 안되는 답변이었고, 폭풍 구글링을 한 결과 아래의 글에서 해답을 찾을 수 있었다.

https://hyeonyeee.tistory.com/56

 

Docker 의 timezone과 java application(Spring boot)의 timezone을 맞추자!

Docker의 timezone 과 java application 의 timezone의 sync를 맞추자! 혹은.. 서버의 timezone과 java application의 sync... docker의 timezone을 설정하고, spring boot인 application 을 띄웠는데.. 다음과 같이 localtime을 지정해

hyeonyeee.tistory.com

 

내 프로젝트는 도커 컴포즈를 이용하고 있는데 아래의 옵션을 주면 호스트 시간대와 도커 컨테이너 시간대와 동기화된다는 것을 알았다.

docker run -d -v /etc/localtime:/etc/localtime:ro -v /usr/share/zoneinfo/Asia/Seoul:/etc/timezone:ro

 

컴포즈와 같은 경우 아래와 같이 설정할 수 있다.

compose.yml

services:
  ....
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /usr/share/zoneinfo/Asia/Seoul:/etc/timezone:ro
  ....

 

 

이렇게 컴포즈 파일을 설정하고 다시 실행해보니 정상적으로 타임존이 동기화된 것을 확인할 수 있었다.

리전을 서울로 설정했더라도 EC2는 기본적으로 외국에 있기 때문에 날짜관련된 로직이 들어갔을 때 정상적으로 작동하지 않을 가능성이 있다.

 

아래의 두 가지 방법중 하나를 선택해서 이러한 문제를 해결할 수 있다.

 

@PostConstruct를 이용해 타임존 변경

애플리케이션 시작 시점에 명시적으로 JVM의 시간대를 설정할 수 있다. 

@SpringBootApplication
public class PromiseApplication {

	public static void main(String[] args) {
		SpringApplication.run(PromiseApplication.class, args);
	}

	@PostConstruct
	public void init() {
		// JVM의 기본 시간대를 Asia/Seoul로 설정
		TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
	}

}

 

 

EC2의 시간대 변경

sudo timedatectl set-timezone Asia/Seoul

위 명령어를 입력하면 타임존을 아시아/서울로 변경할 수 있다.

이 글은 인프런의 지식 공유자 박재성님의 강의를 듣고 개인적으로 정리하는 글임을 알립니다.


조회 전략

데이터를 조회할 때 주로 사용하는 전략이 Cache Aside 전략이다. Look Aside 전략 또는 Lazy Loading 전략이라고 부른다.

 

캐시에 데이터가 있을 경우 (= Cache Hit)

 

 

캐시에 데이터가 없을 경우 (= Cache Miss)

 

Cache Aside 전략은 캐시(Cache)에서 데이터를 확인하고, 없다면 DB를 통해 조회해오는 방식이다. 

 

쓰기 전략

Write Around 전략

Cache Aside 전략이 데이터를 어떻게 조회할 지에 대한 전략이었다면, Write Around 전략은 데이터를 어떻게 쓸지(저장, 수정, 삭제)에 대한 전략이다. 

Write Around 전략은 Cache Aside 전략과 같이 자주 활용되는 전략이다.

Write Around 전략은 데이터를 저장할 때는 레디스에 저장하지 않고 데이터베이스에만 저장하는 방식이다.

그러다 데이터를 조회할 때 레디스에 데이터가 없으면 데이터베이스로부터 데이터를 조회해와서 레디스에 저장시켜주는 방식이다. 

Write Around 전략은 쓰기 작업(저장, 수정, 삭제)을 캐시에는 반영하지 않고, DB에만 반영하는 방식을 뜻한다.

 

Cache Aside, Write Around 전략의 한계점

  • 캐시 미스(cache miss) 발생: 데이터가 처음 요청될 때 캐시에 저장되지 않기 때문에, 데이터가 자주 사용되지 않는다면 캐시가 활용되지 않고, 그로 인해 캐시 미스가 자주 발생할 수 있다. 이는 캐시의 효율성을 떨어뜨릴 수 있다.

  • 데이터 일관성 문제데이터베이스에 업데이트가 일어나면, 캐시에 있는 데이터를 직접적으로 갱신하지 않기 때문에 캐시와 데이터베이스 간에 일관성 문제가 생길 수 있다. 데이터베이스가 변경된 후 캐시가 갱신되지 않으면 오래된 데이터를 참조하게 될 수 있다.

  • 저장 공간 문제: DB는 디스크(Disk)에 저장해서 많은 양을 저장하기 용이하지만, 캐시는 메모리(RAM)에 저장하기 때문에 DB에 비해 많은 양의 데이터를 저장할 수가 없다.

 

이러한 한계점 때문에 캐시를 적용시키기에 적절한 데이터는 아래와 같다.

  • 자주 조회되는 데이터
  • 잘 변하지 않는 데이터
  • 실시간으로 정확하게 일치하지 않아도 되는 데이터

 

장기간동안 데이터가 일치하지 않는 것은 큰 문제가 될 수 있기 때문에 적절한 주기로 데이터를 동기화시켜서 싱크를 맞추어줘야 한다.

이때 활용하는 기능이 레디스의 TTL(만료시간 설정) 기능이다.

 

일정 시간이 지나면 데이터가 캐시에서 삭제된다. 그럼 특정 사용자가 조회를 하는 순간 Cache Miss가 발생한다. DB의 데이터를 새로 조회해와서 캐시에 데이터를 넣게 된다. 즉, 데이터가 새롭게 갱신되는 효과가 있는 것이다. 

 

 

캐싱보단 SQL 튜닝이 우선

캐싱으로 조회 성능을 하기전에 SQL 튜닝을 먼저 최우선적으로 고려해야 한다.

SQL 튜닝을 제외한 나머지 방법은 추가적인 시스템을 구축해야 한다. 따라서 금전적, 시간적 비용이 추가적으로 발생한다.

조금 더 복잡해진 시스템 구조로 인해 관리 비용이 늘어난다. 그에 비해 SQL 튜닝은 기존의 시스템 변경 없이 성능을 개선할 수 있다.

 

또한 근본적인 문제를 해결하는 방법이 SQL 튜닝일 가능성이 높다.

SQL 자체가 비효율적으로 작성됐다면 아무리 시스템적으로 성능을 개선한다고 하더라도 한계가 있다.

하지만 SQL 튜닝을 통해 기본적으로 성능을 향상시킨다면, 시스템적인 성능 개선이 필요없거나 훨씬 간단한 개선으로 큰 성능 개선 효과를 얻을 수 있다.

'데이터베이스 > Redis' 카테고리의 다른 글

[Redis] 레디스 개념과 기본 명령어  (7) 2024.09.14