가상화 (Virtualization)

컴퓨터 자원을 물리적인 형태와는 독립적으로 사용할 수 있도록 만드는 기술
하나의 컴퓨터를 여러 대처럼 나눠서 쓰는 것

1. 하나의 컴퓨터에서 windows 와 linux를 동시에 실행하는 것처럼 가상 컴퓨터를 나눌 수 있음.
2. 자원을 효율적으로 활용 / 물리적인 서버 한 대에 가상 컴퓨터 여러 대를 올려서 서버 자원을 효율적으로 활용 가능
3. [ 하이퍼바이저 ] 이 소프트웨어가 물리적 자원을 가상화해서 여러 운영체제가 동시에 사용할 수 있게 해줌.

물리적인 컴퓨터 = 아파트 건물
가상 컴퓨터 = 아파트 안에 있는 각 세대

적고 생각해보니 그럼 클라우드랑 다른 점?
클라우드는 서비스(결과), 가상화는 기술(수단)

클라우드는 가상화 기술을 활용해서 제공하는 서비스
인터넷을 통해 컴퓨터 자원을 빌려씀.


AWS의 꽃 EC2는 무엇일까?

Amazon Elastic Compute Cloud 여서 EC2였다..

C가 두개여서 EC2로 불리는 것 또 처음알게된 사실

 

EC2는 크기 조정이 가능한 컴퓨팅을 클라우드에서 제공하는 웹 서비스임, 고로 해보면 좀 더 쉽게 클라우드 컴퓨팅 작업을 할 수 있음.

Amazon EC2의 간단한 웹 서비스 인터페이스를 통해 간편하게 필요한 용량을 얻을 수 있다.

 

언제사용하는지?

우리가 사용하는 게임서버 또는 웹서버 , 애플리케이션 서버를 구축할 때 EC2를 사용함.

 

비트코인 채굴도 가능한데 그럼 비트코인 채굴로 쓰는것도 가능하지 않을까?

AWS에서 서비스 제공안하고 비트코인 채굴하면 더 사업성에 좋지 않을까?

 - 비트코인 채굴로만 사용하면 경제성이 떨어질 수 있음

(채굴난이도 시간 지나면 증가하며 장비 업글해야함 그러면 AWS 회사 손해볼 수 있음 또한 당연하게도 비트코인 채굴하는 것보다 수익이 안좋으면 회사 손실이 큼)

 - 클라우드 서비스가 꾸준한 수익이고 안정성이 더 좋음 , 정부 규제로 AWS같은 대형 기업이 채굴 사업에 집중하면 규제 리스트에 영향도 크고 안좋을 수 있음.

단기적으로는 매력이 있어보이나 , 장기적으로는 서비스 제공이 더 좋다.

 

온디맨드 : 수요에 반응하여 가격이 초 단위로 결정됨

 

인스턴스 : 가상 서버 CPU,메모리,그래픽카드 연산 하드 웨어 담당

EBS : 가상 하드디스크 

AMI : 실행정보담고 있는 이미지

보안 그룹 : 가상 방화벽

 

 

1. 리액트 앱 생성

npx create-react-app ./
# 현재 폴더에 리액트 앱 생성

 

2. 도커 파일 생성

 

.dockerignore파일

package-lock.json
node_modules

 

docker-compose-dev.yml

services:
  web:
    build:
      context:  .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      -/app/node_modules
      - .:/app
  tests:
    stdin_open: true
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - /app/node_modules
      - .:/app
    command: ["npm", "run", "test"]

 

docker-compose.yml

version: "3"
services:
  web:
    build: 
      context: .
      dockerfile: Dockerfile
    ports:
      - "80:80"

 

Dockerfile

FROM node:16-alpine as builder
WORKDIR '/app'
COPY package.json .
RUN npm install
COPY . .
RUN npm run build

FROM nginx
CMD --from=builder /app/build /usr/share/nginx/html

 

Dockerfile.dev

FROM node:16-alpine

WORKDIR '/app'

COPY package.json .
RUN npm install

COPY . .

CMD ["npm", "run", "start"]

 

3. 아마존 가입 후 IAM에서 역할 먼저 생성 

 - AWSElasticBeanStalk에서 멀티컨테이너,웹티어,워커티어 설정

 

AWS Elastic Beanstalk은 애플리케이션 배포 및 관리 간소화 해줌 PaaS임( 사용자가 인프라 관리 X)

복잡한 인프라 설정없이 빠르게 클라우드로 애플리케이션 배포 가능

 

서버 프로비저닝 , 로드 밸런싱 , 자동 스케일링

서버 프로비저닝 : 마치 컴퓨터 사서 운영체제 필요한거 설치 작업을 AWS가 해준다.

로드 밸런싱 : 들어오는 트래픽 여러 서버로 고르게 분배하여 서버 과부하 방지

자동 스케일링 : 트래픽 변화에 따라 서버의 개수를 자동으로 늘리거나 줄이는 기능

 

4. 검색에 elasticbeanstalk으로 간다.

환경 생성해준다.

5. IAM 가서 사용자도 생성

보안 자격증명에서 

엑세스 키 만들어야 함

만들면 액세스키랑 비밀 액세스 키 생성된다.

 

필요하기 때문에 개인 보관

6. S3 이동

클라우드 스토리지 서비스 , 데이터 인터넷에 안전하게 저장하고 관리 , 디지털 저장소

자동으로 생성된 버킷에 들어간다.

 

객체소유권에서 객체 라이터 , ACL 활성화됨 선택 잘되어있는지 확인

 

객체라이터 : 각 계정이 자신이 업로드한 데이터의 소유권과 관리를 계속 유지해야될 때

ACL 활성화 : 객체 소유자가 버킷 소유자와 다른 경우가 많아, 버킷 정책만으로 권한 관리가 어려운 경우

 

7. 리액트 빌드 테스트 성공하면 방금 만든 elasticbeanstalk에서 바로 배포(저장소 필요 깃헙생성)

8. 원격 저장소와 react app 연결 후 소스코드 올려줌

9. 올라간거 확인하고 Travis CI 대신 Github action 이용할 것

프로젝트에 .github 폴더와 workflows 폴더 그리고 deploy.yml 파일 작성한다.

10. deploy.yml에 필요한 계정 비밀 액세스 키 등은 깃허브 settings에서 Secrets and variables에서 설정가능하다.

Actions에서 추가 가능

깃 액션에서 자동으로 배포해주는 모습을 보여준다. 현재는 에러나서 해결 해야함.

 

도커 비밀번호가 틀려서 제대로 동작하지 못했음.

비밀번호 변경 후
elasticbeanstalk에서도 성공한모습을 볼 수 있다

성공해서 도메인 들어갔는데 도커랑 연결이 되었다는 화면만 뜰분 내가 배포한 리액트 기본 이미지는 나오지 않았음

 

원인 분석해보니 Elastic Beanstalk의 애플리케이션 이름과 환경이 설정이 되지 않았음

  • **actions/checkout**은 GitHub 리포지토리에서 코드 작업을 할 때 필수적인 기본 액션입니다.
  • **einaregilsson/beanstalk-deploy**는 AWS Elastic Beanstalk에 애플리케이션을 배포하는 데 사용되는 액션입니다.

따라서, actions/checkout은 코드 다운로드에 사용되고, einaregilsson/beanstalk-deploy는 배포를 위한 액션입니다.

 

uses 에서도 actions를 배포를 위한 액션으로 바꿔줘야한다.

v20,v18 버전으로 바꿔봤으나 둘 다 빌드 실패했음 어느게 문제일까?

로그탭에서 로그 전체를 받아서 분석해본결과 docker-compose.yml파일에 version삭제하라해서 삭제했는데도 안됨..

 

FROM이 from으로 되어있다고 했는데 그건 문제가 아니였고

ngnix로 정적 파일 디렉토링해서 옮겨야되는데 CMD로 적어서 오류난거였음 COPY로 수정한다.

 

 

 

Docker Compose는 무엇인가?

다중 컨테이너 도커 애플리케이션을 정의하고 실행하기 위한 도구

npm init

기본적인 노드 부분 생성

{
  "name": "docker-compose-app",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "express":"4.21.1",
    "redis":"3.0.2"
  },
  "author": "",
  "license": "ISC",
  "description": ""
}

 

엔트리 포인트를 만듦 

애플리케이션의 실행 시작 지점이 설정되고, 이를 통해 서버가 가동되기 때문에 server.js를 "엔트리 포인트"라고 부름

 

레디스 복습 Remote Dicitonary Server의 줄임말 Redis

메모리 기반의 키-값 구조 데이터 관리 시스템임 , 모든 데이터 메모리에 저장 빠르게 조회할 수 있는 비관계형 데이터베이스

메모리에 저장해서 Mysql같은 데이터베이스에 데이터를 저장하는 것과 데이터를 불러올때 훨씬 빠르게 처리 가능

메모리에 저장하지만 영속적으로 보관 가능, 서버를 재부팅해도 데이터를 유지할 수 있다는 장점

 

Node.js 환경에서 레디스 쓰려면?

redis-server 작동 시키고 , redis 모듈 다운 , 레디스 모듈을 받은 후 클라이언트를 생성하기 위해서 Redis에서 제공하는 

createClient() 함수를 이용해서 redis.createClient로 레디스 클라이언트를 생성

 

//레디스 클라이언트 생성
const client = redis.createClient({
    host:"redis-server",
    port:6379
})

도커 사용하지 않은 환경에서는 url을 https://redis~~.com 이런식으로 명시해줘야하지만

도커 Compose를 사용할 때는 host 옵션을 docker-compose.yml 파일에 명시한 컨테이너 이름으로 주면 됨

client.set("number",0);

app.get('/',(req,res) => {
    client("number", (err, number) => {
        // 현재 숫자를 가져온 후 1씩 올려줌
        client.set("number", parseInt(number) + 1)
        res.send("숫자가 1씩 올라감. 숫자 : " + number)
    })
})

path에 /로 오면 

 

app.get()은 express 특정 url 경로로 들어오면 실행할 코드 명시

(req,res) => {...}는 콜백 함수로, GET 요청이 들어오면 실행될 함수

req : 클라이언트가 서버로 요청할 때 보낸 데이터를 포함. 요청 헤더,URL 파라미터,쿼리,문자열,폼 데이터 등을 다룸

res : 서버가 클라이언트에게 응답을 보낼 때 사용하는 객체.이를 통해 클라이언트에게 데이터를 전송할 수 있음

 

redis실행하고 지금 생성한 파일 build해서 실행시키면 오류

실패했다고 나오는데 왜 실패하는지? 

컨테이너1 : 레디스 서버 실행

컨테이너2 : node js 작성 + 레디스 클라이언트

 

서로 다른 컨테이너는 아무런 설정없이 접근 불가능하다.

 

가능하게 하려면? Docker compose 이용

yml 얌 파일

얌 팡리 작성 yml은 YAML ain't markup language의 약자

xml이나 json 많이 쓰지만 좀 더 사람이 읽기 쉬운 포맷

 

컨테이너 1 , 2를 docker-compose.yml을 이용해서 같이 쓰게한다.

version: "3"
services:
  redis-server:
    image: "redis"
  node-app:
    build: .
    port:
      - "5000:8080"

 

yml파일을 작성한 것 

redis-server는 redis이미지 컨테이너 이용 node-app은 현재있는 파일 "."으로 표시 port 매핑

docker-compose up으로 도커 컴포즈 실행 가능

오타 있어서 실행안됐음 ports로 변경

version: "3"
services:
  redis-server:
    image: "redis"
  node-app:
    build: .
    ports:
     - "5000:8080"

잘빌드되는 것 확인

근데 무한대기 현상이 생김

Dockerfile의 노드 버전을 명시해주니 해결완료

변경해주니 잘되었음 Redis-stack은 Redis에 더해서 몇가지 기능이 추가된 것

 

build는 되었으나 게속해서 server.js에 오류가 난다고 나옴

 

ckdbi@DESKTOP-QDP16JK MINGW64 ~/Desktop/docker-compose-app  
$ docker-compose up --build

node 버전이 16으로 바꿔줬는데 최신 버전이 아닌 것 같음 그리고 또 build를 다시하고 컴포즈를 실행시킴

 

그래도 안됨 ..

도커파일에서 node버전 16으로 바꾸고

docker-compose 설정파일에서 redis 버전을 많이 올려줬음

설정파일에 depends_on 설정 추가해서 redis-server 서비스가 먼저 시작되도록 설정


docker compose up

docker compose up --build

docker compose down 도커 내리기

docker-compose up -d는 앱을 백그라운드에서 실행 시킴 ouput 표출 X 

실행시키면 빌드 찍히거나 그러진 않음


Dockerfile.dev 로 하면 그냥 build하면 찾지못하니 -f 옵션을 이용해서 찾는다.

 

Ngnix를 이용해서 운영 환경에서 리액트 실행함 

개발서버 대신 쓰는건데 개발환경에 특화되어 있는 서버 예를들면 개발서버는 전부 빌드를 하는데 Ngnix는 소스를 다시 변경해서 반영할때 그러한 기능이 필요없어서 더 빠른 Ngnix를 사용한다.

Docker 실습 쿠팡 크롤링

간식 정리해서 이름 , 수량 개수 , 금액 등을 excel로 정리해서 올려야 함.

매월 1번씩 하는데 한번 만들어놓고 랜덤으로 과자 조건 줘서 시킬 수 있게 자동화 해보자.


url뒤에 /robots.txt를 붙이면 해당 웹사이트의 "robots.txt" 파일 요청
크롤링하도록 허용하거나 차단하고 있는지를 확인 가능

 

## Google
User-agent: Googlebot
Allow: /vp/products/
Allow: /np/categories/
Allow: /np/search?q=*
Disallow: /vm/direct-orders/
Disallow: /vm/cart/
Disallow: /gppu-choice-widget?itemId=*
Disallow: /vendor-items/
Disallow: /other-seller-json

User-agent : Googlebot 구글 봇에 대해 규칙이 적용되어 있음 (*이면 모든 봇에 대해 규칙 적용)

Allow : 경로에 있는 페이지 크롤링해도 좋다.

Disallow : 경로에 있는 페이지는 크롤링 하지 말아달라.

 

내가 볼 곳은 상품페이지 인데 Allow로 되어있는것을 확인할 수 있음

내 user-agent 확인 방법

개발자 도구를 열고 navigator.userAgent 입력한다.


1. Selenium 설치

pip install selenium

pip : 파이썬 패키지 설치하고 관리할 수 있는 관리 도구 

자동화 테스트 셀레니움 설치

 

2. chromedriver_autoinstaller 설치

 

pip install chromedriver-autoinstaller

ChromeDriver의 버전을 자동으로 맞추어주는 라이브러리.

셀레니움을 Chrome에서 자동화할 때, ChromeDriver의 버전은 Chrome버전과 일치해야되므로 버전 호환성 문제를 해결해줌.

$ pip show chromedriver-autoinstaller
Name: chromedriver-autoinstaller
Version: 0.6.4
Summary: Automatically install chromedriver that supports the currently installed version of chrome.
Home-page: https://github.com/yeongbin-jo/python-chromedriver-autoinstaller
Author: Yeongbin Jo
Author-email: iam.yeongbin.jo@gmail.com
License: MIT
Location: C:\Users\user\AppData\Local\Programs\Python\Python312\Lib\site-packages
Requires: packaging
Required-by:

제대로 설치 됐는지 확인하려면 show 명령어 통해 확인

 

3. 파이썬 코드 작성

import time
import random
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import chromedriver_autoinstaller

시간,랜덤값 그리고 셀레니움 필요 설정 import

 - webdriver : 웹 브라우저 제어 객체 (필수) 

 - By : 요소 찾을 때 (ID, CLASS_NAME , CSS_SELECTOR) javascript로 생각하면 documents.getElementsById 같은 느낌

 - Keys : 키보드 입력 모방할 수 있는 기능 제공

 

처음실행될 때 호환성 문제를 해결하기 위해서 ChromeDriver를 자동으로 설치 해야함.

chromedriver_autoinstaller.install()

 

나는 Docker에서 사용할것이므로 GUI가 필요없다. 그러므로 옵션 추가해줘서 백그라운드에서 작업하게 해줘야함.

options.add_argument("headless")
options.add_argument("no-sandbox")
options.add_argument("disable-dev-shm-usage")

headless 옵션 : GUI를 렌더링하지 않기 때문에 CPU 메모리 절약(속도 빠름).

no-sandbox 옵션 : 샌드박스 모드를 비활성화(도커에서 샌드박스 제대로 동작하지 않을 수 있어 비활성화 시킨다.

                              샌드박스 : 애플리케이션 안전하게 실행하기 위해 격리된 환경

disable-dev-shm-usage 옵션 : /dev/shm가 공유메모리인데 비활성화 시킴 파일 시스템을 통해 데이터를 저장하도록 하는 것

 

이제 옵션 설정다되었으면 해당 인스턴스를 생성하여 제어한다.

driver = webdriver.Chrome(options=options)

 

그리고 아까 import했던 selenium의 by문법을 이용하여 쿠팡의 input 박스를 찾음

input box 태그값 "q"

searchBox = driver.find_element(By.Name, "q")
searchBox.send_keys("과자")
searchBox.send_keys(Keys.RETURN)

RETURN은 ENTER를 의미함 몇가지 더 있는데

 

Keys.TAB - 탭 키
Keys.BACKSPACE - 백스페이스 키
Keys.DELETE - 삭제 키
Keys.HOME - 홈 키
Keys.END - 엔드 키
Keys.PAGE_DOWN - 페이지 다운 키
Keys.PAGE_UP - 페이지 업 키
Keys.ARROW_UP - 위쪽 화살표 키
Keys.ARROW_DOWN - 아래쪽 화살표 키

 

등등이 있음.

 

검색 후 페이지 로드되는 것 기다려야함 

time.sleep(2)

 

내가 원하는 가격 설정 후 while문을 이용해서 채워질때까지 반복문을 실행

 

로켓배송 붙은 상품
상품 클래스명
로켓배송과 로켓배송 없는 li 태그 분석

data-is-rocket 값이 true이면 로켓배송 아니면 일반배송임

해당 div에 있는 name 값과 price-value가 필요하니 변수 선언해서 가져온다

price는 누적해서 내가 설정한 값을 넘기면 안되므로 int형으로 바뀐 후 누적해서 더해준다.

        #랜덤으로 로켓배송 상품 선택
        item = random.choice(rocket_items)
        try:
            #상품 이름 금액 추출
            item_name = item.find_element(By.CSS_SELECTOR, "div.name").text
            item_price_txt = item.find_element(By.CSS_SELCTOR, "strong.price-value").text
            #int로 변환
            item_price = int(item_price_txt.replace(",", ""))

            # total_price 넘으면 안됨
            if now_price + item_price <= total_price:
                product_list.append({"name" : item_name , "price" : item_price})
                now_price += item_price
                items_cnt += 1

        except Exception as e:
            print("예외가 발생하였습니다." , e)

2개를 찾았으면 끝나고 다음 페이지에서 또 2개를 채워줘야함 , 클릭한 뒤 랜더링 되는 시간 2초 빼준다.

    try:
        next_button = driver.find_element(By.CSS_SELECTOR, "a.btn-next")
        next_button.click()
        time.sleep(2) # 랜더링 대기
    except Exception as e:
        print("예외가 발생하였습니다." , e)

 

엑셀로 만들어서 쓰기 위해 Pandas 라이브러리 추가해준다.

 

pandas가 할 수 있는 일

1. 엑셀 파일 읽고 쓰는 작업

2. CSV 파일을 읽고 쓰는 작업

3. 데이터 정렬 , 그룹화 , 필터링 등등 

4. 수치형 데이터 분석과 같은 다양한 데이터로 반환

 

pip install pandas

판다스깔고 임포트 시킨 후 사용

df = pd.DataFrame(product_list)  # product_list를 DataFrame으로 변환
df.to_excel("product_list.xlsx", index=False)  # 엑셀 파일로 저장

도커 파이썬 슬림이용

슬림 버전은 불필요한 툴과 패키지 최소화하여 이미지 크기 줄였음

FROM python:3.10.15-slim
RUN apt-get update && apt-get install -y \
    curl \
    unzip \
    chromium-driver \
    && rm -rf /var/lib/apt/lists/*

필요한 소프트웨어 패키지를 설치

apt-get update는 패키지 목록 최신으로 

apt-get install -y는 필요한 패키지를 설치. -y는 모든 응답에 "y"로 대답하여 넘어가는 것

curl : 웹에서 파일 다운로드 또는 API 호출을 통해 데이터를 받아오거나 전송할 때 주로 사용

WORKDIR 작업디렉토리 생성

COPY로 먼저 txt 복사 이후 내용 설치 (캐시 비우기 옵션 추가 이미지 가볍게 하기 위해)

컨테이너 안에서 실행시켜야되므로 COPY해서 py실행

WORKDIR /app

# requirements.txt 복사 및 라이브러리 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 크롤러 스크립트 복사
COPY crawlling.py .

# 환경 변수 설정
ENV CHROME_BIN=/usr/bin/chromium
ENV CHROMEDRIVER_PATH=/usr/bin/chromedriver

# 크롤러 실행
CMD ["python", "crawler.py"]

 

빌드하고 실행하는데 에러뜬다.

확인해보니 크롤러 실행하는 파일 이름 달랐음

도커 컨테이너 내부에 pandas 없다는 오류

확인해보니 requirements.txt에 필요한 파이썬 패키지를 넣지 않았음

 

하지만 실행해도 아무일 일어나지 않음

도커 컨테이너에서 생성한 파일은 컨테이너 파일 시스템에 저장! 컨테이너가 종료되면 파일또한 역시 삭제 됨. 그래서 삭제 전에 
호스트 시스템에서 확인하고 싶다면 실행된 상태에서 파일을 복사해야함.

 

docker cp <컨테이너_ID>:/app/파일명 /호스트/경로/
C:\Users\user

호스트 경로는 바탕화면에 할거면 이런식으로 자기 컴퓨터에 맞게 cp라는 명령어로 copy복사한다는 것 

 

또는 호스트의 디렉토리와 공유하는 방법이 있음

접근이 거부되었다고 나옴

내부코드에서 driver = webdriver.WebDriver 문법 맞지 않다고 나옴 수정 필요

Name으로 적으면 안됨 NAME으로 적어야 함

 

페이지로드가 되기전에 p를 불러서 그런지 계속해서 오류가 난다

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

추가하여 페이지 랜더링 시간 기다려보도록 함.

그래도 찾지 못했음 input 태그를 찾는데 오래걸리는걸까?

searchBox = WebDriverWait(driver, 100000000000).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='q']"))
)

input 태그의 name "q"를 찾으라고 하니 바로 찾고 다른 라인으로 넘어갔다.

pandas
selenium
chromedriver-autoinstaller
openpyxl

openpyx1도 엑셀이용하려면 라이브러리 필요하므로 추가해준다.

 

차단당해서 안되는건지 input태그를 잘잡지 못함

# 랜덤 User-Agent 리스트
user_agents = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15",
    "Mozilla/5.0 (Linux; Android 10; SM-G950F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Mobile Safari/537.36",
    "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1",
    # 추가 User-Agent를 여기에 추가할 수 있습니다.
]

# 랜덤으로 User-Agent 선택
random_user_agent = random.choice(user_agents)

 

options.add_argument(f"user-agent={random_user_agent}")

셀레니움 옵션에 추가해주고 다시 빌드한 후 실행해봄.

 

일단 잘 돌아가는 것을 볼 수 있는데 예외가 계속해서 발생했음

 

user-agent가 또 막혔는지 잘 못찾고 코드 수정을 더 해줌

 

from fake_useragent import UserAgent

fake_useragent로 더 많은 user-agent 생성했음

 

스크롤을 못해서 데이터를 못가져오는건가 싶어서 sleep()도 충분히 넣어줌

    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  # 페이지 끝까지 스크롤

스크롤 하는 명령어 까지

print("1 : " + driver.page_source) # 현재 DOM 페이지 표시

왜 못찾는지 보기위해서 현재 찾은 dom을 전부 분석해봄

 

쿠팡으로간다음 검색창에 "과자"를 입력해야되는데 입력창을 못잡는 것 같음 headless 모드여서 그런진 모르겠는데

그래서 과자를 입력 한뒤 작업부터 하니깐 불러와지긴하는데 name값이랑 value값을 잡지 못함

#driver.get("https://www.coupang.com/") #랜더링이 전체가 되지않고 카테고리까지 돼서 수정
driver.get("https://www.coupang.com/np/search?component=&q=%EA%B3%BC%EC%9E%90&channel=user")

 

로그 분석을 다시해야함

페이지의 html이 많은 관계로 로그볼 수 있는 줄 수를 늘린다.

docker logs --tail 1000 < 컨테이너 아이디 >

 

full 로그 분석 결과 분석 너무 많음 태그가

docker logs e2c7604b6d95 | grep "price-value"

docker id를 가지고 특정 키워드 분석 가능


너무 실행이 안돼서 이것저것 수정 많이해보고 로그 다 찍어보았다.

1. 랜더링 대기 시간 랜덤

ime.sleep(random.uniform(3, 7)) # 랜더링 대기

2. 랜덤으로 상품 선택 시 태그 값 상세하게

        price_wraps = WebDriverWait(driver, 20).until(
            EC.presence_of_all_elements_located((By.CSS_SELECTOR, '#productList .search-product'))
        )

3. 선택된 요소 리스트에서 제거

price_wraps.remove(price_wrap)  # 선택된 요소를 리스트에서 제거

여기서도 예외는 발생하지만 페이지가 넘어가면서 채우긴 하는데 시간이 오래걸림

예외발생하면서 채우는데 일단 결과값까지 떨어지는지 확인하고 예외 수정

200000원 전까지 계속해서 도는거여서 로직 변경이 필요

                # total_price를 넘지 않도록 체크
                if now_price + item_price <= total_price:
                    product_list.append({"name": item_name, "price": item_price})
                    now_price += item_price
                    items_cnt += 1

이 부분의 if문이 필요가 없다 안그러면 딱 맞지 않을떄까지 채워지지 않기 때문

그리고 테스트니깐 50,000정도로 설정한다 200,000너무 오래걸린다.

 

또 갑자기 안되는데 url값에 파라미터가 달라졌음 

쿠팡 시간이 지나면 channel이 변경되는 것 같음 이에 맞게 수정해주거나 이 부분도 랜덤값으로 선택되게 변경해줌

 

로그보니 잘 작성되었고 엑셀도 생성되는 것 같은데 이미지가 종료돼서 도커에 exec로 접근할 방법이 없다.

 

if os.path.exists(file_path):
    print(f"파일 생성 성공: {file_path}")
else:
    print("파일 생성 실패")

 

종료된 곳에서 파일을 생성해보니 잘나왔음

docker cp 75a83581a365:/app/product_list.xlsx /c/Users/user/Desktop/

성공한 폴더 도커에서 cp 명령어를 이용해 복사한 다음 가져왔더니 성공했음

 

 


자주쓰는 명령어

docker logs e2c7604b6d95 | grep "price-value"

docker build -t kaki5507/randomcoupang:latest ./

docker run -v /c/Users/user/Desktop/Random:/app kaki5507/randomcoupang:latest

docker cp 75a83581a365:/app/product_list.xlsx /c/Users/user/Desktop/

도커 다시 정리해보기

 

도커를 쓰는 기업이 많다. 거의 기본적으로 다 쓰고있다고 생각하면 된다.

크롬 같은 것을 실행하려고 하면 구글에 바로 검색해서 실행할 수 있는것이 아님.

 

installer 받고 실행해야되는데 이 경우 컴퓨터마다 설정이 다를 수 있고 에러가 날 수 있다.

 

이러한 문제를 해결해주는 것이 도커라고 생각하면된다.

 

도커에서 그럼 제일 많이 사용되는 용어 컨테이너,이미지 등등 명확하게 알고 넘어가야 됨.

 

컨테이너는 우리가 생각하는 그 컨테이너와 같고 그 컨테이너 안에는 우리가 사용하는 응용 프로그램이라고 생각하면된다.

sql,tomcat 등등 이러한 것이 컨테이너안에 있다고 생각하면된다.

 

컨테이너 이미지는 런타임,시스템 라이브러리 같은 설정 응용 프로그램 등 독립적인 실행 가능한 소프트웨어 패키지이다.

 

도커를 다운받고 설치해보자.

 

도커 공식 홈페이지에 들어가 자신 컴퓨터 운영체제에 맞는 installer를 설치한 뒤 로그인한다.

cmd를 이용하여 docker version을 썻을경우 해당과 같이 나오면 올바르게 설치된 것임.


도커 클라이언트에서 명령어를 입력하면 도커 서버에서 실행됨

 

docker run ~~~ 명령어 이용하여 도커 실행

Unable to find image나오면서 찾을 수 없다고 나온다.

hello-world라는 프로그램은 Hello from Docker! 같이 알려주는 이미지임 이걸 가져와서 pulling 해서 보여주는 것

 

내가 입력한 docker run ~ 명령어 입력하고 도커 서버로 이동함

hello-world라는 이미지 있는지 먼저 확인을 하고 없어서 도커 허브라는 곳에서 많은 이미지들 중에 해당 이미지를 가져온 것

 

가져온다는 문구 Pulling ~ 해서 해당 프로그램 실행된 것


도커 vs 가상화 기술 차이

 

만약 한대의 서버 100GB인데 하나의 용도로만 사용했는데 비효율적이였음

하이퍼 바이저 기반의 가상화가 출현하면서 vm이라는 가상 환경 서버를 이용하여  독립적인 가상 환경의 서버 이용이 가능하게 됨

 

하이퍼 바이저는 네이티브 하이퍼 바이저, 호스트형 하이퍼 바이저로 나뉨

네이티브 하이퍼바이저는 하드웨어 바로 위에서 하이퍼 바이저를 실행하고

호스트형 하이퍼바이저는 하드웨어 -> OS -> 하이퍼 바이저여서 OS위에서 하이퍼 바이저를 실행함

이렇게 되면 하이퍼바이저 위에 게스트 OS를 이용하여 윈도우를 설치하거나 리눅스 설치 가능

 

도커 컨테이너와 기존 가상화 기술은 기본 하드웨어에서 격리된 환경 내에 애플리케이션 배치하는 것

도커는 아까 호스트위에 OS를 배포하면 되는데

VM은 비슷하지만 VM을 띄우고 자원 할당, 게스트 OS 부팅해서 어플리케이션 실행해야돼서 훨씬 복잡하고 무거움.

 

카톡 컨테이너 , MYSQL 컨테이너 , 크롬 컨테이너 프로세스들이 실행되고 있고 

커널은 공유됨. (커널 : 소프트웨어,하드웨어 중재자)

하드디스크에도 카톡 컨테이너 , MYSQL 처럼 격리되어 있음.


아까말한 이미지들이 모여서 컨테이너가 되는데 어떻게 되는건지?

 

프로그램 실행한 필요한 설정들이 도커 이미지인데,

컨테이너안에서 프로그램을 실행하는건데 실행하는 명령어 필요함

run chrome 같은 명령어를 이미지에서 가지고 있고(예시임) 

chrome 파일도 가지고 있어야 함, 이렇게 모든 것을 가지고 있어야 함.

 

docker run chrome 이런식으로 실행하면 chrome 스냅샷을 하드 디스크에 넣어줌

 

명령어를 컨테이너에 넣어줘서 컨테이너가 실행할때 run chrome이라고 치면

컨테이너가 실행되고 커널을 통해서 하드디스크에 있는 크롬 파일 실행 , 네트워크 , ram , cpu 등등 이용 


크롬 , mysql 실행하기 위해 컨테이너 커널 하드 디스크에 파일 시스템이 있는데

이렇게 나누기 위해서 사용하는 것이 Cgroup과 네임 스페이스라고 했음

근데 이 Cgroup하고 네임 스페이스는 리눅스 환경에서 사용되는 것임..

근데 어떻게 windows에서 사용 가능한지?

docker version을 보면 server는 리눅스로 되어있음

 

도커는 리눅스 환경에서 돌아가고 있어서 나뉘어 질 수 있다고 보면 됨


도커 명령어 사용해보기

docker                          run              chrome

도커 클라이언트를 언급  실행한다 ~  크롬 이미지를 ~

 

도커 서버로 가짐.

도커 서버에서 컨테이너를 위한 이미지가 캐쉬에 있는지 확인한다.

없으면 도커 허브에서 받아옴.(Pulling)

 

docker run alpine ls

가져온다음 ls 파일 리스트 표출 커맨드를 보여줌

파일 스냅샷(필요한 설정파일들)이 하드디스크에 들어오고

ls명령어를 실행해서 목록이 보이는데 hello-world 했을때는 ls 안됐음.

alpine 이미지는 ls를 실행할 수 있는 파일이 있어서 가능한거고 hello-world는 없어서 그런것임.


현재 실행중인 컨테이너 나열 명령어 docker ps 바로 실행해봄

없어서 안나옴. ps(process status) 프로세스 상태

cmd 두개 켜서 하나는 실행 시키고 ps로 상태 찍어보면 나온다.

4번째 구문이 저장된 구문을 alpine이라는 컨테이너 이미지에 저장된 명령어를 통해 실행무시하고 해당 명령어를 실행시킴

 

docker ps를 이용하여 어떤 이미지가 실행되고 있는지 확인이 가능하다.

 

docker ps를 이용하면 몇가지 정보가 나오는데

CONTAINER ID : 컨테이너 고유한 해쉬값(일부만 나오는 것)

IMAGE : 컨테이너 생성시 사용 도커 이미지

COMMAND : 컨테이너 시작시 실행될 명령어

CREATED : 컨테이너 생성 시간

STATUS : 컨테이너의 상태

PORTS : 컨테이너가 개방한 포트와 호스트에 연결한 포트

NAMES : 컨테이너 고유 이름을 줄 수 있음(안하면 엔진이 알아서)

docker ps --format 'table{{.Status}}\table{{.Image}}'

원하는 항목만 볼 수 있음 Status보고 싶으면

 

 

>docker ps --format "table {{.Names}}\t{{.Image}}"

 

모든 컨테이너 보려면? 실행되지 않은 것도

docker ps -a

 


도커 컨테이너의 생명주기

크게는

생성(create)->시작(start)->실행(running)->중지(stopped)->삭제(deleted)

생성한 것 

신기하게도 아이디 앞에 몇글자만 복사해서 start 시키면 알아듣는다

-a옵션은 attach 붙이다 옵션임!

결과값 잘나온다.

docker run alpine ping localhost

알파인이용해서 핑 보내는 것 stop해보기

stop을 한 뒤에 느리게 종료되는 것 확인 

 

다음은 kill 시험

바로 종료가 되는 것 확인

 

삭제하는 방법은 중지된 후 

docker rm 아이디 또는 이름

원하는 컨테이너 삭제

모든 컨테이너 삭제 power shell 에서 명령어(cmd는 좀 복잡)

 

★ 도커 안쓰고 있는 컨테이너,이미지,네트워크 모두 삭제 ★

docker system prune

도커를 많이쓰면 용량이 커서 쓰지않을때도 용량을 많이 잡고 있어서 사용하면 유용하다.(실행중인 컨테이너에는 영향 X)

실행중인 컨테이너에 명령어 전달

실행중인 컨테이너에 명령어 전달하여 확인할 수 있음

run은 새로 실행 exec이미 실행된 프로세스 컨테이너에 명령어 전달 후 이용


도커에서 redis 실행

레디스 클라이언트가 레디스 서버가 있는 컨테이너 밖에서 실행을 하려 하니 레디스 명령어 불가능

레디스 클라이언트도 컨테이너 안에서 실행 시켜야 함

 

-it 명령어를 실행한 후 계속 명령어 적을 수 있음 

레디스 클라이언트에서 redis-cli명령어를 써야하므로 exec로 실행중인 컨테이너에 명령어 보내야 함 -it 옵션을 적지 않으면

바로 들어갔다 그냥 나와버리는게 되므로 명령어를 전달하려면 -it 옵션을 넣어야함.


먹히지 않은 명령어 같은 경우 쉘환경이 안맞아서 powershell을 켜서 진행했는데

docker exec -it 컨테이너 명령어(powershell)

이런식으로 쉘 환경으로 접근이 가능하다고 함 바로 해본다!

알파인 실행하고 해보는데 잘 안됨.. cmd에서는 $() 또는 백틱(`)을 지원하지 않아서 그런 것 같음

 

다른 명령어로 학습해본다.

ls는 잘 접근

ctrl + C로 잘 안나와짐 ctrl + D로해야 나와진다.

docker run -it mcr.microsoft.com/powershell pwsh

powershell 쓰려면 powershell 컨테이너 실행시켜서 쓸 수 있다는데 해당 도커를 쓰려고 하니 안됨

 

왜냐하면 파워쉘을 실행시키는데 이 파워쉘안에는 도커의 cli나 도커의 서버(데몬)이 없기 때문


도커 이미지 만들기

 

도커파일작성 > 도커 클라이언트 > 도커 서버 > 도커 이미지 생성

도커 파일에서는 컨테이너가 어떻게 행동해야 하는지 설정해둠

 

도커 파일 만드는 순서

1. 베이스 이미지를 명시(레이어들로 구성되어 있는 이미지에서 가장 근본이 되는 레이어 ex) OS)

2. 추가적 필요 파일 다운 몇가지 명령어

3. 컨테이너 시작시 실행 될 명령어

 

바탕화면에 폴더 생성

폴더 vcs로 오픈 후 Dockerfile 생성

FROM RUN CMD 이용해서 도커 서버에게 무엇을 하라고 알려주는 것

 

FROM 이미지 받으라고 하는 것

RUN 도커이미지가 생성되기 전에 수행할 쉘 명령어

CMD 컨테이너가 시작되었을 때 실행할 실행 파일 또는 셸 스크립트

알파인써서 hello 출력하는 도커 파일 생성

생성했으면 build 해야 함

빌드 된 것

 

이미지로 컨테이너를 만들어야 함

<none> 상태로 아직 이름을 가지지 않은 상태

하지만 실행시킬때 저 ID 값으로 하면 입력하기 어려우니 저걸 바꿔줘야함

build할때 옵션을 넣어주면 됨

 

docker build -t kaki5507(도커아이디)/FlyOn(저장소/프로젝트이름):버전

이런식의 규칙이 있음

소문자로만 작성해야함 안그러면 오류

docker run -it kaki5507/flyon


도커 실습해보기

node package.json 만들어줌 명령어 npm init해서 생성

라이브러리 하나 추가할거임

{
  "name": "nodejs-docker",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "kaki5507"
  ],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": { 
    "express" : "^4.21.1" // express 추가한 것 최신버전
  }
}

 

node js 앱 만들꺼임

server.js 생성해서 해당 코드 삽입

const express = require('express'); // express 가져와줌

const PORT = 8080;

// express 앱 생성
const app = express();
app.get('/', (req,res) => {
    res.send("Hello World");
});

app.listen(PORT); // 설정 된 포트에서 앱실행해야됨

이제 도커파일 생성할 것

FROM RUN CMD ! 위에 예제했던대로 먼저 해야 될 것 작성

# 베이스 이미지 명시
FROM node:10

RUN npm install

CMD ["node", "server.js"]

npm install을 하려면 npm이 가지고 있는 이미지가 필요한데 그게 node 이미지임

node 이미지는 npm도 있고 다른것도 많음

 

npm Node.js 모듈 웹에서 받아서 설치하고 관리해주는 프로그램

npm install은 package,json에 적혀있는 종속성들을 웹에서 자동으로 다운 받아 설치

 

build해서 성공 하지만 실행 시 되지 않았음

 

C:\Users\ckdbi\Desktop\nodejs-docker>docker run ec45af266bd5c5
internal/modules/cjs/loader.js:638
    throw err;
    ^

Error: Cannot find module '/server.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:636:15)
    at Function.Module._load (internal/modules/cjs/loader.js:562:25)    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)

server.js를 찾을 수 없다라고 나온다.

 

Node 베이스 이미지로 node:10을 입력했고 이걸로 임시 컨테이너를 만들려고 하고 , 환경을 하드 디스크에 넣어주는데

npm install을 하려고 할때 package.json이 없다고 나옴 컨테이너안에 있는것이 아니고 package.json이 바깥에 있어서 하드 디스크가 들어있지 않음. package.json을 컨테이너 안으로 넣어줘야 사용이 가능

COPY해서 사용해야 됨.

 

FROM RUN CMD 이렇게 있었는데 COPY 부분을 추가해줘야함

 

# 베이스 이미지 명시
FROM node:10

COPY package.json ./

RUN npm install

CMD ["node", "server.js"]

 

이렇게 설정해두고 다시 빌드해본다.

 

docker build -t kaki5507/nodejs ./

 

$ docker run kaki5507/nodejs
internal/modules/cjs/loader.js:638
    throw err;
    ^

Error: Cannot find module '/server.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:636:15)
    at Function.Module._load (internal/modules/cjs/loader.js:562:25)    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)

 

또 못찾음 .. server.js도 밖에서 있어서 그런 것 안에다 넣어줘야한다.

 

dockerfile 다시 수정

# 베이스 이미지 명시
FROM node:10

COPY ./ ./

RUN npm install

CMD ["node", "server.js"]

실행된 화면을 볼 수 있음

console.log("서버 돌아가고 있음")
app.listen(PORT); // 설정 된 포트에서 앱실해애됨

표시가 안나서 console.log 하나 찍어준다음 다시 빌드해서 도커 실행

그리고 localhost8080해서 들어갔는데 뭐 나오는 건 없음 해당 포트 구문 8080 hello world 찍어주는 화면이 나와야하는데

 

왜 접근을 못하는지 ? - 현재 포트가 매핑되어 있지 않기 때문 

 

우리가 8080을 준다고 해도 컨테이너안에 있는 네트워크 8080에 들어갈 수 있는것이 아님

매핑을 시켜줘야지 들어갈 수 있다.

-p : 포트 매핑 명령어를 이용하여 들어갈 수 있다.

 

docker run -p 5000 : 8080 이미지 이름

 

5000번으로 매핑 후 브라우저에서 접속하는 모습


도커 워크 디렉토리

FROM  WORKDIR COPY RUN CMD

무슨 옵션인지 ? 왜 필요할까 ? 

이미지안에서 어플리케이션 소스 코드를 가지고 있을 디렉토리를 생성하는 것

COPY 한것들이 이미지로 루트로 들어오게 됨

이러면 원래 루트 디렉토리에 있는 파일이 만약 이름이 같을 경우 COPY한 파일이 덮어써질 수 있음

+ 너무 복잡함 

 

이러한 문제를 해결하기 위해서 WORKDIR를 설정해서 깔끔하게 작성한다.

# 베이스 이미지 명시
FROM node:10

WORKDIR /usr/src/app

COPY ./ ./

RUN npm install

CMD ["node", "server.js"]

다시 이미지 빌드

워크디렉토리로 바로 연결돼서 정리된게 보임
찾아서 들어가보면 파일들 잘 정리되어 있음


도커를 계속해서 빌드해야되는건가 ...

docker run -d -p 5000:8080 kaki5507/nodejs

-d 옵션 바로 빠져나오게 하는 것 포트 매핑

const express = require('express'); // express 가져와줌

const PORT = 8080;

// express 앱 생성
const app = express();
app.get('/', (req,res) => {
    res.send("Hello World2222222");
});
console.log("서버 돌아가고 있음")
app.listen(PORT); // 설정 된 포트에서 앱실해애됨

이렇게 Hello World222222가 출력되게 하고 싶음( 또 빌드 해야함 비효율적 ) 

# 베이스 이미지 명시
FROM node:10

WORKDIR /usr/src/app

# package.json COPY 먼저
COPY package.json ./

RUN npm install

# RUN 밑으로 내림
COPY ./ ./ 


CMD ["node", "server.js"]

패키지 부분을 먼저 카피를 해주고 package.json부분을 카피함

지금까지는 npm start하면서 package.json을 계속 다시 받은거였음

RUN 위에 COPY로 먼저 안바뀔 설정들을 받아두는게 좋음

나머지 변경되는 소스들을 아래로 내려서 캐시를 이용해서 받아 빠른 수정사항을 적용시킬 수 있었음.

CACHED 사용 부분


Docker Volumes이란?

COPY는 내 로컬에있는 파일을 도커 컨테이너로 옮기는 것인데

Volume은 도커 컨테이너가 내 로컬을  참조하는 것

 

cmd 에서 명령어

C:\Users\ckdbi\Desktop\nodejs-docker>docker run -d -p 5000:8080 -v /usr/src/app/node_modules -v %cd%:/usr/src/app kaki5507/nodejs

git bash에서는 $(pwd) 이용

docker run -d -p 5000:8080 -v /usr/src/app/node_modules -v $(pwd):/usr/src/app kaki5507/nodejs

 

docker run -d -p 5000:8080 -v /usr/src/app/node_modules -v %cd%:/usr/src/app kaki5507/nodejs

-v /usr/src/app/node_modules : 내 usr/src/app에는 node_modules 없어서 제외

 (호스트 경로 지정안되어 있음 , 이 경우 Docker는 해당 볼륨을 데이터 볼륨으로 처리 , 호스트의 파일 시스템과는 독립적으로 데이터 유지)

-v %cd%:/usr/src/app : 나머지는 내 디렉토리 %cd%에서

(현재 디렉토리 가르킴 %cd% 동기화되어 상호 공유)

근데 윈도우에서 Volume이용하면 바로 바뀌지 않는다 무슨 문제인지 cmd로 빌드했을 경우 잘됨 

CMD 결과

 

  • 경로 호환성 문제: Windows와 Git Bash 간 경로 처리 방식 차이로 Docker가 파일을 제대로 마운트하지 못함.
  • 권한 문제: Git Bash에서 Docker가 파일 시스템 권한을 올바르게 인식하지 못해 동기화 실패.

 

winpty docker run -d -p 5000:8080 -v $(pwd):/usr/src/app kaki5507/nodejs

1. 

 

권한 문제 라고 생각했을 경우 해결 방법으로 winpty를 사용해서 windows 응용 프로그램과의 호환성 문제를 해결할 수 있다고 한다.

바로 적용해본다.

결과 : 빌드전으로 돌아감 Hello World가 나옴

 

2.

docker run -d -p 5000:8080 -v "$(cygpath -w "$(pwd)")":/usr/src/app kaki5507/nodejs

$(cygpath -w "$(pwd)") : 현재 디렉토리 Windows 경로로 변환

이 문제는 아닌 것 같다 해결 안됨. 결과 동일.

 

3.

Nodemon 사용하기 Node.js 애플리케이션이 자동으로 파일 변경을 감지하도록 설정

FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install -g nodemon
RUN npm install
COPY . .
CMD ["nodemon", "server.js"]

해본 결과 안됨.. 재빌드 했는데 일단 CMD를 이용..

1. HTTP 응답분할

 

응답분할이란 악의적인 사용자가 서버에 잘못된 입력을 제공하여 http 응답을 두 개로 분할하여 공격함.

 

헤더 조작해서 서버가 두 개 이상의 응답을 반환하도록 요구하여 정보를 탈취해갈 수 있음.

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class VulnerableServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 사용자 입력을 URL 파라미터로 받음
        String userInput = request.getParameter("user");
        
        // 취약한 응답 헤더 설정 (사용자 입력을 검증 없이 헤더에 포함)
        response.setHeader("Set-Cookie", "user=" + userInput);
        
        // 응답 내용
        response.getWriter().println("Welcome " + userInput);
    }
}

 

 

해결방법은 응답에 escape 처리한다.

// 사용자 입력을 인코딩 처리하여 응답 분할을 방지
String safeInput = StringEscapeUtils.escapeHtml4(userInput);
이런식으로 escape처리하여,HTTP 헤더나 HTML 응답에 안전하게 포함시킨 후 보냄

2. 경로 조작 및 자원 삽입

취약한 코드
외부 입력(name)이 삭제할 파일의 경로설정에 사용되고 있습니다. 
만일 공격자에 의해 name의 값이 ../../../rootFile.txt와 같은 값이면 의도하지 않았던 파일이 삭제되어 시스템에 악영향을 줍니다. 

의도하지 않는 파일이 삭제되면 안됨!

public void bad(Properties request)
{
    //...
    String name = request.getProperty("filename");
    if (name != null)
    {
        // 보안 약점: 외부 입력값이 필터링 없이 파일 생성 인자로 사용
        File file = new File("/usr/local/tmp/" + name);
        if (file != null) file.delete();
    }
    //...
}

 

getCanonicalPath() 메서드를 사용하여 실제 파일 경로를 확인한 후,기본 디렉토리 경로와 비교하여
사용자가 상위 디렉토리로 벗어나지 않도록 방지함.

 

상위 디렉토리를 벗어나지 않게하면 다른 중요한 파일들에 접근 못함!

이러한 코드를 아래와 같이 경로가 벗어나는지 확인하는 코드로 바꿔 수정

        String baseDir = "/var/www/app";  // 기본 경로를 설정
        String strPath = "../etc/passwd"; // 사용자가 입력한 경로 (악의적인 경우)

        // 파일 경로 검증
        File file = new File(baseDir, strPath); // 기본 경로와 사용자 입력 경로를 합침
        String canonicalPath = file.getCanonicalPath(); // 실제 파일 경로 얻기

        // 사용자가 설정한 경로가 baseDir을 벗어나는지 확인
        if (!canonicalPath.startsWith(new File(baseDir).getCanonicalPath())) {
            throw new SecurityException("잘못된 경로입니다!");
        }

        // 경로가 유효한 경우에만 디렉토리 생성
        if (!file.isDirectory()) {
            file.mkdirs();  // 안전하게 디렉토리 생성
        }

 


3. 널 포인터 역참조

 

자바에서 null 값을 참조하는 객체에 접근하려 할 때 발생하는 오류입니다. 
즉, 객체가 생성되지 않았거나 초기화되지 않은 상태에서 해당 객체의 메서드나 변수를 사용하려 할 때 NullPointerException(NPE)이 발생하게 됩니다.

 

public void good(ServletRequest request)
{
String myString = null;
if ((myString != null) && (myString.length() > 0)) { 
	IO.writeLine("The string length is greater than 0"); 
	}
}

else로 대입안하거나 continue로 그냥 넘기고 다음 반복문으로 진행 시킴
null 할당안해도 될 것 같음.. 오류나는지 확인 필요

4. 부적절한 자원 해제 ( IO )

프로그램의 자원, 예를 들면 열린 파일디스크립터(Open File Descriptor), 힙 메모리(Heap Memory), 소켓(Socket), DB 등은 유한한 자원입니다. 
이러한 자원을 할당받아 사용한 후, 더 이상 사용하지 않는 경우에는 적절히 반환하여야 하는데, 프로그램 오류 또는 에러로 사용이 끝난 자원을 반환하지 못하는 경우입니다.

 

예시)
ByteArrayInputStream fileOut = new ByteArrayInputStream(out.toByteArray()); 
여기서는 자원누수가 발생할 수 있음.
적절이 해제 안되면 프로그램이 유한한 시스템 자원을 소진할 수 있다고함.
ByteArrayInputStream 내부적으로 힙 메모리를 사용함.
파일 핸들로 운영 체제에서 한 번에 열수있는 파일의 수를 제한을 둬 Too Many Open Files와 같은 오류가 발생 가능.

 

해결 방법으로는

try (ByteArrayInputStream fileOut = new ByteArrayInputStream(out.toByteArray())) {
    // fileOut 사용
} catch (IOException e) {
    // 예외 처리
}

 

이런식으로 객체의 자동으로 자원을 해제할 필요는 없지만, 다른 자원과의 일관성을 위해서 try-with-resources구문으로 자동으로 닫아주고 코드에서 자원 누수를 방지할 수 있음.


5. 신뢰되지 않는 URL 주소로 자동 접속 연결

public void connectToUrl(String urlString) {
    try {
        // 신뢰되지 않은 외부 URL로 연결
        URL url = new URL(urlString); 
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");

        int responseCode = connection.getResponseCode();
        System.out.println("Response Code: " + responseCode);

        // 연결 후 처리
        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String inputLine;
        StringBuffer content = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            content.append(inputLine);
        }
        in.close();

        System.out.println("Response Content: " + content.toString());

    } catch (Exception e) {
        System.out.println("Error occurred: " + e.getMessage());
    }
}

 

보안 개선 방법은 화이트리스트로 특정 도메인 , ip를 미리 작성한 후 그것과 비교

public void connectToTrustedUrl(String urlString) {
    try {
        // 신뢰할 수 있는 도메인 검증
        if (!urlString.startsWith("https://trusted.com")) {
            throw new IllegalArgumentException("Untrusted URL");
        }

        URL url = new URL(urlString);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");

        int responseCode = connection.getResponseCode();
        System.out.println("Response Code: " + responseCode);

        // 연결 후 처리
        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String inputLine;
        StringBuffer content = new StringBuffer();
        while ((inputLine = in.readLine()) != null) {
            content.append(inputLine);
        }
        in.close();

        System.out.println("Response Content: " + content.toString());

    } catch (Exception e) {
        System.out.println("Error occurred: " + e.getMessage());
    }
}

6. 크로스사이트 스크립트 (XSS Error Message)

{
  response.sendError(404, "param " + data);
}

data값에 악의적인 스크립트 포함시켜 사용자에게 실행을 유도 시킬 수 있으므로 replace를 작성하자 

data.replaceAll("<","")

7.오류 상황 대응 부재

 

try catch나 메소드 반환값에 대한 적절한 예외처리 필요합니다,
catch 블록이 비어있는 문제 해결 catch블록에 들어가는 내용은
 - 오류로깅 : 발생한 예외의 정보를 로그로 남기는 것이 좋음
 - 사용자에게 알리기
 - 복구로직 : 기본값으로 설정하는 로직을 넣거나 다시 시도하는 로직을 넣어줌


8. 오류 상황 미수신

 

예외를 발생시키면 반드시 처리하게 throw new ~~~Exception시에 해당 Exception처리하는 문 필요합니다.


9.주석문 안에 포함된 시스템 주요정보

 

주석안에 원래 로직 또는 변수명 id,pass 같은 값 있으면 제거해야 됨.


10.중요한 자원에 대한 잘못된 권한 설정 (File)

		File file = new File(sPath);
		if (file.exists() == false) {
			file.mkdirs();
		}

해당 코드를

File dir = new File(sPath);

// 1. 디렉토리 존재 여부 체크
if (!dir.exists()) {
    // 2. 먼저 권한 검증 (필요시, 부모 디렉토리나 경로에 대한 접근 권한도 검증)
    if (!dir.getParentFile().canWrite()) {
        throw new SecurityException("Cannot write to the parent directory.");
    }
    
    // 3. 디렉토리 생성
    boolean success = dir.mkdirs();
    
    if (success) {
        // 4. 생성 후 권한 설정
        boolean isExecutableSet = dir.setExecutable(false, true); // 모든 사용자에게 실행 권한 없음
        boolean isReadableSet = dir.setReadable(true); // 모든 사용자에게 읽기 권한 부여
        boolean isWritableSet = dir.setWritable(false, true); // 모든 사용자에게 쓰기 권한 없음

        if (isExecutableSet && isReadableSet && isWritableSet) {
            System.out.println("Directory created and permissions set successfully.");
        } else {
            throw new SecurityException("Failed to set the directory permissions.");
        }
    } else {
        throw new IOException("Failed to create the directory.");
    }
} else {
    System.out.println("Directory already exists.");
}

 

디렉토리 생성 전에 권한 검증을 수행하면 보안적인 위험을 줄일 수 있음.

권한 검증 후 디렉토리를 생성하므로 , 권한이 제대로 설정되지 않은 디렉토리가 생성되거나 사용하는 시스템에 위험을 초래할 가능성 있음.

 

근데 보통은 생성한 후에 한다고함 code-ray에서는 후에 사용하면 계속 잡혀서 해당 변수들을 위로 넣었는데 그건 올바르지 않는 방법

 

생성한 후에 하는 이유는 

 - 권한 설정은 이미 존재하는 디렉토리나 파일에 적용되는 것이 일반적 디렉토리가 없으면 검증할 이유가 없기 때문..


11. 적절하지 않은 난수값 사용

Math.random()은 예측이 가능한다고해서 사용하면 안된다고 한다.

보안 대책으로 난수값 예측이 어려운 함수 사용해야함.

	// 랜덤 인덱스 생성 함수
	function getRandomIndex(length) {
		const array = new Uint32Array(1); 	  // 32비트 정수 배열 생성
		window.crypto.getRandomValues(array); // 보안적인 난수 생성
		return array[0] % length; 			  // 배열 길이에 맞춰 인덱스 반환
	}

 

+ Recent posts