Tempo Di Valse

[Docker] Nginx + PHP-FPM 이미지를 만들어보자 (1편) 본문

개발/ETC

[Docker] Nginx + PHP-FPM 이미지를 만들어보자 (1편)

TempoDiValse 2022. 6. 27. 15:54

Docker 자체를 처음 사용 해본 지라, 컨테이너를 섞어서 쉽게 구성할 수 있다고 해서 시도해 봤는데 실패했다.

 

게다가 PHP 버전들이 내가 사용하고 싶은 8버전 이상의 FPM 들이 검색을 못 한건지 안 된건지 찾아보기가 어려웠다. 사용 설명도 왜이리 안 써있던지..

 

그래서 직접 이미지를 만들며 Docker 를 파악해 보았고, 순서별로 다시 나열하여 정리해보았다. 포스팅을 읽고 있는 독자들의 환경과는 조금 다를 수 있기 때문에 읽으면서 어떻게 만들어지는구나 파악해도 나쁘지 않을 것이다. 물론 글쓴이도 구조 파악 후 다른 방식으로 사용 중이다


1. Docker 를 작업할 폴더 생성

 

Docker 이미지를 만든다고 해도 따로 내보내지는 파일은 없다. 다만 이미지를 생성할 때 주입할 파일들을 모아놓기에는 하나의 프로젝트 폴더처럼 생성하는 것도 좋다고 생각되어 만들었다.

 

작업 폴더는 다음과 같이 구성을 하였다.

~/docker
    ㄴ ./conf
    ㄴ ./html
    ㄴ  Dockerfile

conf 폴더는 Nginx 의 설정과 관련된 파일을 넣는 곳으로 될 것이다. Nginx 에서는 conf 파일들로 서버를 설정하기 때문에 기본 설정과 site-available 이라고 주로 불리우는 웹 서비스별 설정도 함께 넣을 것이다.

 

html 폴더는 Nginx 가 웹 서비스의 루트 폺더로 지목할 장소를 사용자의 로컬 경로에서 사용 할 수 있도록 준비 하는 폴더라고 생각하면 될 것 같다. Linux 의 mount 기능과 비슷하다고 생각하면 되는데, 사용자의 로컬 폴더가 Docker 컨테이너의 어느 한 폴더로 마운트가 되며, 일반적으로 다른 환경에서 작업할 때 FTP 로 올리는 것이 아니라 로컬에서 작업하는 것 처럼 환경을 만들어 줄 수 있다.

 

Dockerfile 은 Docker 이미지 생성에 대한 로직을 명세해놓은 것이고, 이미지를 만드는데 필수인 파일이다. 내용은 대략 어떤 환경 내에서 이미지를 어떻게 구성할 것인지 적혀있으며, Dockerfile 의 전용 문법에 맞게 명세를 작성하면 된다. 작성방법은 구글에도 다양하게 나와있기 때문에 검색하여 참조하면 될 것이다.

 

2. Dockerfile 작성

 

그러면 이미지 만들기에 필요한 Dockerfile 을 작성해보도록 할 것인데, 일단 시작하기 이전에 '기본 이미지' 라는 것을 알아야 할 것 같다.

 

기본 이미지 라는 것은 해당 이미지의 환경이라고 얘기하면 어려울 것 같고, 서버의 OS 환경이라고 생각하면 될 것 같다. 가장 널리 알고 있는 Linux OS (Ubuntu CentOS 라던지 Red Hat Debian 이런 리눅스 애들) 을 보통 사용하는데, Docker 자체가 쉽게 생각하면 가상화된 서버라고 생각하는게 편할 수 있어서 그런 개념으로 접근한다면 지금 만들려고 하는 Nginx + PHP-FPM 를 올리기 위해 OS 를 먼저 설치한다 라고 생각하면 될 것 같다. Docker Hub 에는 이런 리눅스 OS 공식 이미지들이 많이 있기 때문에 입맛대로 맞는 것을 당겨서 사용하면 된다. 

 

나의 경우에는 기본 이미지로 Alpine Linux 라는 OS 를 선택했는데, 이 OS는 다른 OS 와는 다르게 경량화가 잘 되어있는 OS 래서 이미지를 당겨서 설치해보니 총 용량이 5MB 밖에 차지하지 않았다. 많은 이미지들도 Alpine 을 기본 이미지로 하여 만들고 있어서 선택을 하게 되었다. 다만, 이미지를 다 만들고 보니 900MB 넘더라..

 

나는 기본 이미지를 당겨 받게 된 후에 Dockerfile 을 작성하기 시작했다. Dockerfile 은 이름은 고정이며, 확장자는 없다. 그냥 이름만 맞추면 되는 파일이다.

 

먼저 Base Image 에 대해 명세를 하도록 한다.

FROM alpine:latest

"나는 alpine 이란 이름을 가진 이미지를 사용할 것이며 해당 이미지의 latest 태그 버전을 사용할 것이다." 라는 의미로 해석하면 되겠다. 기본 이미지를 Docker Hub 에서 다운받게 되면 Docker 이미지 항목에 다음이 추가된다.

그래서 "FROM" 에 다음의 항목을 작성한 것이다.

 

다음은, 기본 프로그램을 설치 해보도록 한다. 

RUN apk add --no-cache \ 
    openrc \
    ca-certificates \
    curl \
    tar \
    xz \
    openssl \
    g++ \
    make \
    pkgconf \
    curl-dev \ 
    libsodium-dev \
    libxml2-dev \
    openssl-dev \
    sqlite-dev \
    oniguruma-dev \
    libpng-dev

 

 

여기서 알면 좋을 것 같은 프로그램은 'openrc' 인데, 일반 리눅스에서 사용되는 systemd 로써, service 나 systemctl 과 같은 역할을 하는 프로그램이다. 그 외에는 Nginx 와 PHP-FPM 설치에 필요한 프로그램들이다.

 

맨 앞에 RUN 명령어는 Dockerfile 규칙으로 RUN 다음에는 해당 OS 내에서 사용할 커맨드를 입력하도록 한다.

 

일반 리눅스에서는 apt-get 이나 yum 을 이용한 패키지 설치를 했다면, Alpine 에서는 apk 라는 것으로 패키지를 설치하며 [add, del] 커맨드를 통해서 패키지를 설치/삭제 할 수 있었다.

 

다음 설치가 완료되었다면, OS 내에 사용자를 추가하도록 한다.

RUN adduser -u 82 -D -S -G www-data www-data

adduser 를 통해서 사용자를 추가하는데, PHP 의 경우에는 root 계정으로 서비스 되는 것을 막기 때문에 따로 계정을 생성하여 PHP 에 지정을 해주어야 한다. 사용자명은 가장 유명한 www-data 를 하도록 한다. /etc/group 을 조회해 보니 www-data 가 이미 생성이 되어있었기 때문에 사용자만 만들고 해당 그룹(www-data) 번호를 통해 그룹까지 추가를 하였다. 

 

다음으로는 PHP 를 서버에서 다운로드 할 준비를 한다.

WORKDIR /home/www-data
ADD https://www.php.net/distributions/php-8.1.7.tar.gz ./pkg/php-8.1.7.tar.gz
RUN tar zxvf ./pkg/php-8.1.7.tar.gz && rm -rf ./pkg/php-8.1.7.tar.gz

WORKDIR 는 현재 작업하는 디렉토리를 이동한다. www-data 사용자가 생성이 되었기 때문에 사용자 폴더로 진입을 하였다.

그리고, ADD 명령을 이용하여 URL 을 통해 PHP 패키지를 다운로드 받도록 한다. apk 내에도 PHP 가 존재하지만, 아직 8.1 이상은 지원하지 않고 있기 때문에 PHP 8.1 이상은 소스 컴파일을 통해서 설치해야 한다. 설치하려는 버전은 8.1.7 버전이며, PHP 공식 서버에서 다운로드 받아 바로 이미지 내에 집어 넣는다. ADD 명령어는 로컬에 있는 tar 파일을 이미지로 보낼 때 압축을 해제하는 것 까지 해준다고 한다. 하지만 URL 형식으로 파일을 다운로드 받아넣는 경우에는 그냥 압축파일만 다운받게 된다.

그래서, RUN 명령을 이용하여 tar 파일을 직접 압축을 풀어주도록 한다. 폴고 나면 필요없으니 삭제까지.

 

다운로드도 했으니 이제 PHP 컴파일을 준비한다.

WORKDIR /home/www-data/php-8.1.7
RUN ./configure \ 
    --prefix=/usr/local/php \
    --enable-bcmath \
    --enable-mbstring \
    --enable-mysqlnd \
    --enable-dba \
    --enable-ftp \
    --enable-gd \
    --with-sodium \
    --with-curl \
    --with-iconv \
    --with-openssl \
    --with-zlib \
    --enable-fpm \
    --with-fpm-user=www-data \
    --with-fpm-group=www-data \
    && make && make install

컴파일에서 필요한 요소는 찾아서 추가하거나 빼면 될 것이다. 여기서 FPM 을 사용할 것이기 때문에 --enable-fpm 을 필수로 추가하며, --with-fpm-user 와 --with-fpm-group 을 www-data 로 설정하여 PHP 의 소유권을 등록하도록 한다. configure 준비가 완료된 후에는 make 을 통해 install 을 한다.

 

다음은, PHP 사용에 필요한 설정 파일들을 옮기거나 사용할 수 있도록 변경하는 작업을 준비한다.

RUN cp php.ini-production /usr/local/php/php.ini && ln -s /usr/local/php/php.ini /etc

WORKDIR /home/www-data/php-8.1.7/sapi/fpm
RUN cp init.d.php-fpm /etc/init.d/php-fpm && chmod 755 /etc/init.d/php-fpm

PHP 를 설치하면 끝나는 게 아니라, 일부 설정 파일들은 PHP 압축 해제된 폴더에서 설정 파일을 밖으로 내보내줘야 한다. 내보낼 파일들은 다음과 같다.

압축 해제된 폴더 기준
- ./php.ini-production : 기본적인 PHP 설정이 담겨 있다.
- ./sapi/fpm/init.d.php-fpm : 서비스용 스크립트로 php-fpm 을 작동/재시작/종료 등을 할 수 있도록 한다.

일단, 첫 번째 파일은 /usr/local/php 폴더로 이동하도록 한다. 이동 할 때에는 -production 을 제거해야 사용할 수 있다. /usr/local/php 로 이동하는 이유는, 소스 컴파일을 할 때 PREFIX 를 /usr/local/php 로 설정했기 때문에 설치가 되면 거의 모든 PHP 관련 리소스들이 /usr/local/php 안에 담기기 때문이다. 옮긴 후에는 php.ini 를 PHP 가 인식 할 수 있도록 심볼릭 링크를 통해 /etc 폴더에 생성 하도록 한다. php.ini 파일은 /etc 폴더있어야 인식 할 수 있다.

 

두 번째 파일은 /etc/init.d 폴더로 이동을 하도록 한다. 모든 서비스 스크립트들은 /etc/init.d 폴더 안에서 관리되기 때문에 이동을 하면 되고, 이동 할 때에는 이름을 init.d.php-fpm 이 아닌 그냥 'php-fpm' 으로 변경해주면 되겠다. 그 다음에는 시스템에서 실행할 수 있도록 권한을 755로 변경해준다.

 

다음은, 파일의 이름을 변경만 하도록 한다.

WORKDIR /usr/local/php
RUN mv ./etc/php-fpm.conf.default ./etc/php-fpm.conf && mv ./etc/php-fpm.d/www.conf.default ./etc/php-fpm.d/www.conf

 

PHP-FPM 을 사용하기 위해서 설정파일의 이름을 변경해주어야 하는데, 이름을 변경하는 파일은 다음 두파일이다.

/usr/local/php 기준
- ./etc/php-fpm.conf.default 
- ./etc/php-fpm.d/www.conf.default

 

 

 

두 파일은 뒤에 .default 만 제거 하면 된다.

 

다음은, 환경 변수를 등록 한다.

ENV PATH /usr/local/bin:/usr/local/php/bin:/usr/local/php/sbin:$PATH

보통 패키지를 설치하게 되면 bin 폴더에 들어가서 환경변수를 등록하지 않아도 자동으로 사용할 수 있지만, 소스 컴파일 옵션에 prefix를 사용했기 때문에 /usr/local/php 내에 있는 bin 과 sbin 폴더를 따로 추가해주어야 한다. 환경변수 추가는 ENV 명령어를 사용한다.

 

다음은, 여기까지 하면 PHP 설정은 어느정도 완료 된 것이기 때문에 압축해제했던 폴더는 필요없으니 삭제하도록 한다.

WORKDIR /home/www-data
RUN rm -rf ./*

 

다음은, NGINX 를 설치해보도록 한다.

RUN apk --no-cache add nginx

다행히 Nginx 는 소스컴파일을 하지 않아도 apk 에 존재하여 사용할 수 있었다.

 

다음은, NGINX 설정하는 파일을 복사해보도록 한다.

COPY ./conf/nginx.conf /etc/nginx
COPY ./conf/conf.d /etc/nginx/conf.d
COPY ./conf/default.d /etc/nginx/default.d
COPY ./conf/site-available /etc/nginx/site-available

로컬에서 작업한 후에 복/붙하는 방식으로 했다. 처음에는 mount 하는 방식으로 설정을 해봤는데, 로컬에 있는 conf 파일 이외에 이미지에 있는 파일들과 같이 사용할 수 없어서 복사하는 방식으로 변경을 했다. 파일들은 ./conf 파일에 담겨져 있으며, 사용하는 환경에 따라 알맞게 구성하여 conf 파일을 이미지 내부로 옮기도록 하면 될 것 같다. 필요한 conf 파일은 다음 포스팅에서 다뤄보도록 하겠다.

 

다음은, 외부에서 NGINX 서버에 접근할 수 있도록 포트를 열어주는 것이다.

EXPOSE 80
EXPOSE 443

EXPOSE 명령어를 사용하게 되면, 외부에서 해당 포트로 접근을 할 수 있다. NGINX 서버이기 때문에 로컬호스트를 통해 접속을 할 수 있도록 만들어준다. 다만, 내부에서 포트를 열어준다고 명시를 해주는 것이기 때문에 외부에서 접근할 때에는 80,443 포트를 사용할 수도 있고 다른 포트를 통해 프록시 할 수도 있다.

 

여기까지 NGINX 설치 하는 부분이었고, 다음은 NGINX 와 PHP-FPM 을 설치했으니 서비스를 실행하도록 한다.

COPY ./bash/run-script.sh /usr/local/bin
RUN chmod 755 /usr/local/bin/run-script.sh

CMD [ "/bin/sh", "/usr/local/bin/run-script.sh" ]

서비스를 실행하는 부분을 스크립트를 실행하는 것으로 처리를 했다. 다른 이미지들을 확인해보니 단일 패키지로 구성을 했다면,

CMD ["nginx", "-g", "daemon off;"] // 이거나
CMD ["php-fpm"]

이런식으로 작성을 하던데, 일단 CMD 라는 것 자체가 Dockerfile 내에서는 한번만 사용할 수 있는 것이기 때문에 두 개를 돌릴 수 없었다. 그래서 생각해낸 방법이 스크립트를 만들어서 한번에 이미지에서 돌리도록 하는 방법이었다. 내용은 별 내용이없다.

#!/bin/sh

set -eux

rc-service php-fpm start
nginx

 

단, 이것만 있냐... 아니다. 이 외에 입력할 것들이 많았기 때문에 스크립트로 빼놓은 것을 참 잘했다고 생각한다. 나머지 내용들은 다음 포스팅에서 확인해보도록 한다.

 

마지막으로 웹페이지를 로컬에서 작업하기 위해 마운트 시킬 공간을 열어주도록 한다.

VOLUME [ "/usr/share/nginx/html" ]

꽤나 나름대로 디테일 하게 작성하느라 내용이 조금 길어졌는데, 한번에 합쳐본다면 다음과 같이 구성이 되어있다.

# Dockerfile

FROM alpine:latest

# Alpine 에서 사용할 기본 프로그램 및 NGINX, PHP 설치를 위한 기본 라이브러리들을 설치한다.
RUN apk add --no-cache \ 
    openrc \
    ca-certificates \
    curl \
    tar \
    xz \
    openssl \
    g++ \
    make \
    pkgconf \
    curl-dev \ 
    libsodium-dev \
    libxml2-dev \
    openssl-dev \
    sqlite-dev \
    oniguruma-dev \
    libpng-dev

# PHP 를 작동시킬 User 를 정의한다.
# User 이름은 www-data 이며 www-data 그룹으로 넣어주도록 한다.
RUN adduser -u 82 -D -S -G www-data www-data

# PHP 소스를 URL 을 이용하여 다운 받는다.
# ADD 는 로컬 tar 파일을 리모트로 옮길 떄에는 압축해제까지 자동으로 된다지만 URL 형식으로 넣어주는 경우에는
# 추가적으로 tar 압축을 풀어주도록 한다.
WORKDIR /home/www-data
ADD https://www.php.net/distributions/php-8.1.7.tar.gz ./pkg/php-8.1.7.tar.gz
RUN tar zxvf ./pkg/php-8.1.7.tar.gz && rm -rf ./pkg/php-8.1.7.tar.gz

# PHP 컴파일을 준비 한다. 
# prefix 를 지정했기 때문에 PHP 컴파일 후에는 PHP 관련된 모든 설정 파일들이 해당 폴더 안으로 들어간다.
# 필요한 extension 은 추가하거나 삭제하거나 한다. 단, FPM 관련 extension 은 꼭 사용해야 한다.
# FPM 은 user 와 group 을 www-data 로 넣어준다. root 계정은 사용할 수 없다.
# Configure 하고 make 하는 시간이 약 10분 이상 소요가 된다.
WORKDIR /home/www-data/php-8.1.7
RUN ./configure \ 
    --prefix=/usr/local/php \
    --enable-bcmath \
    --enable-mbstring \
    --enable-mysqlnd \
    --enable-dba \
    --enable-ftp \
    --enable-gd \
    --with-sodium \
    --with-curl \
    --with-iconv \
    --with-openssl \
    --with-zlib \
    --enable-fpm \
    --with-fpm-user=www-data \
    --with-fpm-group=www-data \
    && make && make install

# php.ini 파일은 자동으로 설치할 수 없기 때문에 압축 해제된 폴더에서 복사하도록 한다.
RUN cp php.ini-production /usr/local/php/php.ini && ln -s /usr/local/php/php.ini /etc

# 서비스 스크립트를 init.d 에 복사 하도록 한다.
WORKDIR /home/www-data/php-8.1.7/sapi/fpm
RUN cp init.d.php-fpm /etc/init.d/php-fpm && chmod 755 /etc/init.d/php-fpm

# FPM 을 사용하기 위해 default 형으로 변환된 conf 파일을 사용가능 하도록 변경시켜준다.
WORKDIR /usr/local/php
RUN mv ./etc/php-fpm.conf.default ./etc/php-fpm.conf && mv ./etc/php-fpm.d/www.conf.default ./etc/php-fpm.d/www.conf

# PREFIX 를 변경했기 때문에 환경 변수를 추가해주어야 한다.
ENV PATH /usr/local/bin:/usr/local/php/bin:/usr/local/php/sbin:$PATH

# PHP 설치는 끝났으니 압축해제 폴더는 지우도록 한다.
WORKDIR /home/www-data
RUN rm -rf ./*

# NGINX 를 설치한다.
RUN apk --no-cache add nginx

# NGINX 설정 파일들을 복사하여 옮긴다
COPY ./conf/nginx.conf /etc/nginx
COPY ./conf/conf.d /etc/nginx/conf.d
COPY ./conf/default.d /etc/nginx/default.d
COPY ./conf/site-available /etc/nginx/site-available

# 포트를 열어준다
EXPOSE 80
EXPOSE 443

# 서비스 및 기타 필요한 작업들을 실행할 스크립트를 작성한다
# 스크립트를 실행하기 위해 권한도 설정한다.
COPY ./bash/run-script.sh /usr/local/bin
RUN chmod 755 /usr/local/bin/run-script.sh

# 커맨드를 통해 스크립트를 실행해준다.
CMD [ "/bin/sh", "/usr/local/bin/run-script.sh" ]

# NGINX 의 ROOT 디렉토리에 로컬 폴더를 마운트할 준비를 한다.
VOLUME [ "/usr/share/nginx/html" ]

나머지 NGINX 와 PHP 세부설정은 다음 포스팅으로 넘어가 본다.

반응형
Comments