docker run 명령어와 자주 쓰이는 옵션들 정리

우분투 컨테이너 다운로드 및 실행

docker run -d --name myubuntu ubuntu

run을 한 다음 docker ps 를 했을 때 실행 중인 우분투 컨테이너가 보일 줄 알았지만 해당 컨테이너는 실행과 동시에 종료되어 docker ps -a를 해야만 볼 수 있다.

 

우분투 컨테이너가 실행과 동시에 종료되는 이유

https://ilikecoding.tistory.com/78

 

도커 입문 6강. 컨테이너 실행하기

컨테이너 특징(생명주기 관련)컨테이너 내부에 프로그램이 일회성 이라면 컨테이너를 띄우자마자 종료되어버린다.즉, 컨테이너에서 실행될 프로그램이 데몬처럼 백그라운드에 떠있으며 무언

ilikecoding.tistory.com

6강에서도 설명하셨는데.. 컨테이너 내부 프로그램이 일회성이라면 컨테이너를 띄우자마자 종료되어버린다고함.

 

 

우분투 이미지는 내부에 실행될 프로그램이 없기 때문에 바로 종료.. httpd는 내부에 아파치 프로그램이 무한루프로 돌고 있기 때문에 종료되지 않는다.

 

[우분투 이미지 실행]

우분투 ▶ RUN ▶ 죽음(내부에 실행할 프로그램이 없다.)

[httpd 이미지 실행]

httpd ▶ RUN ▶ 내부에 무한루프로 Listener가 돌고있어서 종료되지 않는다.
httpd image = ubuntu + apache 인 것.

 

우분투 이미지 종료를 막는 방법

docker run -dit --name myubuntu ubuntu

바로 -it 옵션을 사용하는 것이다.

-i : interaction 상호작용을 하겠다.

-t : terminal 터미널 모드로 사용 (/bin/bash를 자동으로 실행시켜준다.)

--name : 도커 컴포즈를 할 때 컨테이너끼리 결합을 할 때 --link 옵션의 파라미터로 컨테이너 이름을 넘겨주기 위한 식별값.

(도커 컴포즈 뿐만 아니라 하나의 이미지로 여러 컨테이너를 띄울수도 있는데 각각 컨테이너 마다 붙일 수 있는 식별값으로 생각하면 될것 같다.)

 

 

우분투를 해당 옵션으로 다시 실행하기 위해서는 일단 전에 바로 종료되었던 컨테이너를 삭제해야한다.

docker rm $(docker ps -a -q --filter name=myubuntu)

 

실행중인 컨테이너에 접근하는 명령어

docker attach [CONTAINER ID]

-it 옵션으로 컨테이너를 실행하면 /bin/bash프로그램이 실행되는데 그때 우분투에 접속해서 bash 프로그램과 상호작용할 수 있는 명령어이다.

도커 이미지 삭제 프로세스

실행 중인 컨테이너 중지(stop) ▶ 중지된 컨테이너 삭제(rm) ▶ 이미지 삭제 (rmi)

 

실행중인 컨테이너 확인 후 종료 및 삭제

docker ps
docker stop [CONTAINER ID]
docker rm [CONTAINER ID]

중지된 컨테이너 확인 및 삭제

docker ps -a
docker rm [CONTAINER ID]

이미지 확인 및 삭제

docker images
docker rmi [IMAGE ID]

 

(이미지를 삭제하기 위해선 해당 이미지로 실행중 이거나 중지된 컨테이너가 없어야 한다.)

 

위와 같은 프로세스는 많은 이미지를 지울 때 너무 많은 것을 반복해야한다.

#실행중인 모든 컨테이너 종료
docker stop $(docker ps -q)
#중지된 컨테이너 모두 삭제
docker rm $(docker ps -a -q)
#모든 이미지 삭제
docker rmi -f $(docker images -q)

※ $(docker ps -a -q)

$()로 감싸면 변수의 값으로 취급한다. docker ps -a -q 결과값을 docker stop의 입력값으로 취급한다는 이야기이다.

-q옵션 : IMAGE나 CONTAINER의 ID만을 반환하는 옵션

--filter name 옵션 --filter name=jenkins 처럼 --filter name 옵션을 활용하면 내가 원하는 값만 필터링할 수 있으니 잘 사용해보자.

useEffect : 2번째 파람이 빈 배열 == componentDidMout와 동일

배열에 요소가 있다면 ? componentDidMout, componentDidUpdate 둘 다 수행

 

timerRef (강의 내용에서의 timeouts.current) 가 바뀌는 시점을 헷갈렸다.

왜 강의에서 timeouts.current를 useEffect의 2번째 파라미터로 넣었는지 헷갈렸는데... 다시 생각해보니 내가 헷갈렸던 것...

 

timerRef.current는 배열이기 때문에 push, shift 또는 배열 인덱스로 직접 접근하는 것은 배열 참조(배열의 주소)가 바뀌는 것이 아니기 때문에 바뀌는 것으로 간주하지 않는다.

React에서 배열이나 객체로 된 state나 ref, prorps가 바뀌는 것은 배열의 참조가 바뀔 때 이므로

timerRef.current = []

이렇게나

 tirmerRef.current = [...prev, 0];

이렇게 spread 연산자를 사용하여 새로운 참조를 만들어 줄 때 바뀌는 것이다.

 

 tirmerRef.current[7] = 100; //이건 변경된것으로 간주하지 못한다. 그냥 같은 배열주소에 하나 더 추가된것일뿐
 //변경된 것으로 간주되려면 배열참조가 바뀌어야함!

 

포트포워딩하기

docker run -d #detach의 약자로 백그라운드에서 작동하도록 하는 옵션 (daemon으로 외워도 될듯하다)
docker run -p #port를 지정해주는 옵션으로 "HOST:CONTAINER" 형식으로 나열
docker run -d -p 8080:80 httpd #호스트의 8080으로 접근하면 컨테이너 80포트로 전달해준다

 

현재 나의 구성도

[인터넷] - KT공유기 - DMZ - IPTIME공유기 - H2서버

 

Apache 컨테이너 실행하기

docker run -d -p 9096:80 httpd

이제 호스트OS로 9096으로 요청하면 아파치 컨테이너의 80포트로 연결해주게 된다.

원격지에서 실험해보니 정말 잘동작한다.

원격지:9096 ▶ 인터넷 ▶ KT공유기 ▶ DMZ ▶ IPTIME공유기 ▶ 9096포트를 H2:9096으로포트포워딩 
▶ H2서버9096 ▶ httpd컨테이너

이렇게 되어있는 건데 iptime공유기에서 외부의 9096을 내부의 9096으로 설정해놔서 가능하다.

KT공유기와 iptime공유기는 DMZ설정이 되어있어서 모든 요청을 IPTIME공유기로 통과하도록 설정해놨다.

 

Apache 컨테이너 실행하기

docker run -d -p 9096:80 httpd

이제 호스트OS로 9096으로 요청하면 아파치 컨테이너의 80포트로 연결해주게 된다.

 

Nginx 컨테이너 실행하기

docker run -d -p 9097:80 nginx

apache나 nginx나 같은 80포트를 사용하지만 HostOS에서 포트포워딩을 해주니까 상관없다는 점이 정말 좋은거 같다.

9096으로 접근하면 httpd 80

9097로 접근하면 nginx 80

 

 

※ docker run을 할 때 로컬에 저장되지 않은 이미지를 지정하면 도커허브에서 자동으로 pull해서 run을 해준다.
즉, docker run nginx를 하였을 때, 로컬에 nginx이미지가 있으면 있는것을 사용하고 없으면 자동으로 도커허브에서 다운로드받아서 run해준다.

 

 

 

 

 

 

 

 

 

컨테이너 특징(생명주기 관련)

컨테이너 내부에 프로그램이 일회성 이라면 컨테이너를 띄우자마자 종료되어버린다.

즉, 컨테이너에서 실행될 프로그램이 데몬처럼 백그라운드에 떠있으며 무언가를 처리하는 종류가 아니라 어떤 일을 끝마치고 종료되는 프로그램이라면 컨테이너도 그 프로그램이 종료되면 같이 종료되어버린다고 한다.

 

명령어

이미지 다운로드 (테스트를 위해 tomcat)

docker pull tomcat

컨테이너 실행하기

docker run tomcat # 포그라운드로 컨테이너가 실행된다.

 

컨테이너 백그라운드로 실행하기

일반적으로 docker run tomcat같은 명령어로 실행하면 컨테이너가 포그라운드로 실행되어 해당 터미널에서는 어떤한 작업도 할 수 없게 된다. (또한 해당 터미널 접속이 끊어지면 실행중이던 컨테이너도 중지되므로 매우 불편하다.) 그래서 대부분 컨테이너를 실행할 때는 백그라운드 모드로 실행한다.

docker run -d [IMAGE ID]

 

컨테이너가 정상적으로 실행되고 있는지 확인하기 (실행중인 컨테이너, 중지된 컨테이너 두 개다 확인하는 방법)

docker ps #현재 실행중인 컨테이너 확인
docker ps -a #실행중이거나 중지된 컨테이너까지 모두 확인

컨테이너를 중지시키는 방법

docker stop [CONTAINER ID]

도커에서 실행중인 톰캣으로 접근이 안되는 이유

도커에 톰캣을 띄워놓고 호스트OS나 다른 곳에서 localhost:8080 을 해도 도커에 실행중인 톰캣에 접근이 안되는 이유는 도커와 호스트OS는 다른 네트워크 대역을 사용하고 있기 때문이다.

만약에 도커에 있는 톰캣으로 접근하고 싶다면 호스트OS에서 도커로 접근하는 요청에 대해 포트포워딩을 해줘야 한다.

 

가위바위보에서 굳이 useEffect나 componentDidMount 에서만 setInterval을 사용하는건 아니고, 필요할 때 setInterval을 쓰고 componentWillUnMount에서 정리만 잘하면 된다.

 

Hooks는 함수 컴포넌트를 말하는 것이 아니라 useEffect, useState 같은 것들을 말하는 것이다.

 

Lotto.jsx (내가 만든 코드 + 강의코드)

import React, {Component} from "react";
import Ball from "./Ball";

const winBall = () => {
    const balls = Array(45).fill(0).map((v,i) => i+1);
    const result = [];
    while(balls.length > 0){
        const randIndex = Math.floor(Math.random() * balls.length); // <= 0 x < length
        result.push(balls.splice(randIndex, 1)[0]);
    }
    const bonus = result[result.length - 1]; //맨 마지막
    const wins = result.slice(0,6).sort((a,b)=>a-b); // 0~5
    console.log(wins, bonus);
    return [...wins, bonus];
}

class Lotto extends Component{
    state = {
        winNumbers: winBall(),
        winBalls:[],
        bonus:null,
        redo:false,
    }
    timeouts=[];

    componentDidMount() {
        console.log('componentDidMount')
        this.startLotto();
    }

    onClickRedo = () => {
        this.setState({
            winNumbers: winBall(),
            winBalls:[],
            bonus:null,
            redo:false
        });
        this.timeouts = [];
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        if(this.state.winBalls.length === 0){
            this.startLotto();
        }
    }

    componentWillUnmount() {
        this.timeouts.forEach(v => clearTimeout(v));
    }

    startLotto = () => {
        const length = this.state.winNumbers.length - 1;
        for(let i=0; i<length; i++){
            const timerId = setTimeout(()=>{
                this.setState((prev)=>{
                    const winBall = prev.winNumbers.slice(i,i+1)[0];

                    return {
                        winNumbers:[...prev.winNumbers],
                        winBalls:[...prev.winBalls, winBall],
                        bonus:null,
                        redo:false
                    }
                });
            },1000 * (i+1));
            this.timeouts.push(timerId);
        }

        this.timeouts[6] = setTimeout(()=>{
            this.setState({bonus: this.state.winNumbers[length],redo:true});
        }, 1000 * this.state.winNumbers.length);
    }

    render() {
        const {winNumbers, winBalls, bonus, redo} = this.state;
        return (
            <>
                {winBalls.map(v=><Ball number={v} key={v+1}/>)}

                {
                    bonus && <><div className="bonus__comment">보너스!</div><Ball number={bonus} /></>
                }
                {
                    redo && <button onClick={this.onClickRedo}>한번더!</button>
                }
            </>
        )
    }
}

export default Lotto;

 

 

Ball.jsx

import React, {memo} from 'react';
const Ball = memo(({number})=>{
    let background, color = null;
    if(number >= 40){
        background = 'red';
    }else if(number >= 30){
        background = 'green';
    }else if(number >= 20){
        background = 'yellow';
    }else if(number >= 10){
        background = 'blue';
        color = 'white';
    }else{
        background = 'aqua';
    }

    return (
        <>
            <div className="ball" style={{background, color}}>
                {number}
            </div>
        </>
    )
});

export default Ball;

 

 

LottoHooks.jsx

import React, {useEffect, useRef, useState} from "react";
import Ball from "./Ball";

const LottoHooks = () => {
    const winBall = () => {
        const balls = Array(45).fill(0).map((v,i) => i+1);
        const result = [];
        while(balls.length > 0){
            const randIndex = Math.floor(Math.random() * balls.length); // <= 0 x < length
            result.push(balls.splice(randIndex, 1)[0]);
        }
        const bonus = result[result.length - 1]; //맨 마지막
        const wins = result.slice(0,6).sort((a,b)=>a-b); // 0~5
        console.log(wins, bonus);
        return [...wins, bonus];
    }

    const [winNumbers, setWinNumbers] = useState(winBall);
    const [winBalls, setWinBalls] = useState([]);
    const [bonus, setBonus] = useState(null);
    const [redo,setRedo] = useState(false);
    const timerRef = useRef([]);


    useEffect(()=>{
        for(let i=0; i<6; i++){
            const timerId = setTimeout(()=>{
                setWinBalls((prev)=>{
                    return [...prev, winNumbers[i]];
                });
            },(i+1) * 1000);
            timerRef.current.push(timerId);
        }
        timerRef.current[6] = setTimeout(()=>{
            setBonus(winNumbers[6]);
            setRedo(true);
        },7000);
    },[winNumbers]);

    const onClickRedo = (param) => (e) => {
        console.log(param);
        console.log(e.target);
        setWinBalls([]);
        setWinNumbers(winBall);
        setBonus(null);
        setRedo(false);
        timerRef.current = [];
    }

    return (
        <>
            {
                winBalls.map((v,i) => <Ball number={v} key={v+i}/>)
            }
            {
                bonus && <><div>보너스!!</div><Ball number={bonus} /></>
            }
            {
                redo && <button onClick={onClickRedo(1)}>한번더</button>
            }
        </>
    );
}

export default LottoHooks;

이미지 다운로드

docker pull [이미지:태그]

(보통 태그는 버전을 명시하는 것 같다. 태그를 입력하지 않으면 최신버전을 다운로드한다.)

 

이미지 확인

docker images

 

현재 실행중인 컨테이너 확인

docker ps // 중지된 것은 보이지 않고, 실제로 실행중 인것만 보인다.

docker ps -a // 중지된 컨테이너도 모두 보인다.

 

이미지 삭제하기 

현재 실행중인 컨테이너 혹은 중지된 컨테이너인 경우 삭제할 수 없다.

docker rmi [IMAGE ID] 

docker rmi [REPOSITORY]

 

이미지 실행시키기 (컨테이너로 띄운다)

docker run 

컨테이너 중지하기 (실행중인 것을 중지)

docker stop [CONTAINER ID]

컨테이너 삭제하기 (종료된 것을 삭제)

docker rm [CONTAINER ID]

 

 

기억할 것

  • 컨테이너의 상태는 2가지가 있다. 
    • 실행중 : docker ps 로 확인 가능
    • 중지중 : docker ps -a 를 해야 비로소 확인 가능
  • 컨테이너라는 의미는 메모리에 할당된 프로세스 상태라는 상태인데 docker stop 명령어로 중지 했는데 계속 프로세스 상태인것일까? 라는 의문에는 나중에 설명해주신다고 한다.
  • 이미지를 삭제할 때는 컨테이너로 띄워져 있을 때는 삭제할 수 없다. (중지된 컨테이너라도 docker rm 으로 아예 컨테이너에서 지워줘야 이미지를 삭제할 수 있다.)

 

커스텀 훅

  • 어떤 특정한 Hook 2개 이상이 반복된다면 커스텀 훅을 만들어낸다.
  • 커스텀 훅은 react의 기본 hooks의 조합으로 이루어진다. (기본 훅 여러 개가 세트로 움직인다면? 3개를 세트로 묶어서 커스텀 훅 1개로 퉁 쳐버리면 된다.)
import {useEffect, useRef} from "react";

// 기능
// useInterval(()=>{
//  console.log('hello');
// }, isRunning ? 1000 : null );
function useInterval(callback, delay){
    const savedCallback = useRef();

    useEffect(() => {
        savedCallback.current = callback;
    });

    useEffect(()=>{

        //왜 tick이라는 함수로 감싸서 실행했을까?
        function tick(){
            savedCallback.current();
        }

        if(delay !== null){
            let id = setInterval(tick, delay);
            return () => clearInterval(id);
        }
    },[delay]);

    return savedCallback.current;
}

export default useInterval;

강의에서 만들어 본 useInterval이라는 (useRef + setInterval) 커스텀 훅이다.

 

왜 tick이라는 내부함수가 필요한지에 대해서도 설명해주는데 이해하기 좀 난해하다.

 

따로 기억할것.

※useRef는 항상 최신의 객체를 참조할 수 있게 해준다 라는 것을 기억하자.

가상화 vs 컨테이너(도커)

가상머신과 도커는 리얼 머신이 아니라는 점에서 비슷하지만 작동 방식에서 차이를 보인다.
가상머신은 편하긴 하지만 성능이 좋지 못하다. (CPU에 가상화를 위한 기능들이 많이 들어갔지만 아직도 리얼머신에 비해 많이 느리다.)

 

전가상화와 반가상화

전가상화의 성능 개선을 위해 개발된 반가상화가 널리 쓰이고 있다.

 

가상화 계층도

가상 머신 자체는 완전한 컴퓨터이기 때문에 게스트 OS를 항상 설치해야한다. 그래서 이미지 용량이 커진다.

 

도커 계층도

가상화와 달리 도커는 게스트 OS를 설치하지 않고 OS자원은 호스트와 공유하는 방식이다. 이미지 용량이 크게 줄어들었으며 HW 가상화 계층이 없기 때문에 메모리 접근, 파일시스템, 네트워크 속도가 가상 머신에 비해 월등히 빠르다.

 

도커는 이미지 버전을 관리할 수 있고 저장소(docker hub)에 이미지를 업로드하거나 다른 이미지를 다운로드 받을 수 있는 등 배포에 특화된 기능을 제공해준다.

 

리눅스 컨테이너

LXC는 컴퓨터를 통째로 가상화하여 OS를 실행하는 것이 아닌 리눅스 커널 레벨에서 제공하는 일종의 격리된 가상 공간이다. 이 기상공간은 OS가 설치되지 않기 때문에 가상머신이 아닌 컨테이너라고 부른다.

 

리눅스 커널의 ControlGroups(cgroups)는 CPU, 메모리, 디스크, 네트워크 자원을 할당하여 완전한 형태의 가상공간을 제공한다. 또한 프로세스 트리, 사용자 계정, 파일시스템, IPC등을 격리시켜 호스트와 별개의 공간을 만든다. (chroot 명령어처럼) 이것을 Namespace isolation(namespaces) 라고 한다.

 

LXC는 리눅스 커널의 cgroups와 namespaces기능을 활영하여 가상 공간을 제공한다.

 

LXC의 구조

LXC는 격리된 공간만 제공할 뿐 개발 및 서버 운영에 필요한 부가 기능이 부족한데, docker는 리눅스 커널의 cgroups와 namespaces를 기반으로 하여 이미지, 컨테이너 생성 및 관리 기능과 부가기능을 제공한다.

 

따라서 컨테이너는 리눅스 커널을 기반으로 하기 때문에 윈도우에서 도커같은 컨테이너 기술을 사용하기 위해서는 리눅스 하위시스템이 필요하다. (wsl2나 따로 설정이 필요)

 

docker 명령어 전송방식

  1. Host OS에서 docker container에 명령어 전송
  2. docker daemon이 명령어를 받아서 container에게 전달해준다.

컨테이너 vs 이미지

이미지 

도커에서 실행되지 않고 있는 서버운영에 필요한 소스코드, 라이브러리 등이 셋팅된 상태의 일종의 프로그램 상태
(프로그램은 하드디스크에 저장되어있는 실행되고 있지 않은 상태를 의미하기도하니까)
(이미지는 특정 프로세스를 실행하기 위한 모든 파일과 설정, 의존성을 해결한 상태로 Ubuntu이미지는 Ubuntu를 실행하기 위한 모든 파일을 가지고있다.)

 

컨테이너

이미지를 실행시켜 메모리에 로딩한 상태

비교

state가 바뀌었을 때 

  • class의 경우 componentDidMount나 componentDidUpdate에서 모든 state를 조건문으로 분기처리한다.
  • hooks의 경우 useEffect의 2번째 파라미터인 의존배열만 다르게 하여 useEffect를 여러 개 사용하면 된다. (배열에는 꼭 useEffect를 다시 실행할 값만 넣으세요)

state를 동시에 담당하는 법(?)

  • componentDidMount는 [result, imgCoords, score] 이러한 state들을 한번에 담당한다.  (하나의 componentDidMount 에서 state별로 조건분기를 하든 어떻게 하든 하나의 클래스에는 하나의 라이프사이클 메소드만 존재한다고 말하고 싶은거같다.) --> (componentDidUpdate,componentWillUnmount 도 마찬가지이다.)
  • hooks에서는 result용 useEffect, imgCoords용 useEffect 이렇게 useEffect를 state별로 하나씩 만들 수 있다. 하지만 하나의 useEffect에서 동시에 result, imgCoords 2개의 state를 담당할 수 도 있긴함.
  •  
//클래스에서는 하나의 라이프사이클 메서드에서 state여러 개를 한번에 처리
componentDidMount(){
	this.setState({
    	imgCoord: 3,
        score: 1,
        result: 2,
    })
}


//hooks의 useEffect에서는 각각 처리하거나, 묶어서 처리 가능
useEffect(()=>{
	setImgCoord(2);
	setScore(0);
},[imgCoord, score]);
useEffect(()=>{
	setResult('');
},[result]);

 

useEffect vs useLayoutEffect

  • useEffect는 렌더링이 실행되고 나서 실행된다.
  • useLayoutEffect는 렌더링 되기 전에 실행돼서 화면 바뀌는것을 감지하는 Effect이다. (자주 사용안한다고 하심)

 

복습

  • 부모 컴포넌트에서 자식 컴포넌트를 가지고 있을 때 부모 컴포넌트가 리렌더링 되면 자식 컴포넌트도 무조건 리렌더링 되는데 부모 컴포넌트가 리렌더링 될 때 굳이 자식 컴포넌트를 리렌더링 할 필요가 없을 때 React.memo를 사용하면 자식 컴포넌트는 리렌더링 되지 않는다.
  • useEffect 하나에서 componentDidUpdate,componentWillUnmount, componentDidMount 세 개를 동시에 처리할 수 있다고 생각하면 편리할 듯!

 

 

 

+ Recent posts