iT邦幫忙

2023 iThome 鐵人賽

DAY 1
0
DevOps

AWS ECS + Gitlab + Laravel + Terraform 從入門到摔坑系列 第 1

Day 1 在本機執行 Laravel container

  • 分享至 

  • xImage
  •  

開賽啦~開賽啦~這 30 天會帶大家從地面飛上雲端!呃不是,是從本機建立 Laravel 的 Docker image 開始,一步步透過 Gitlab Pipeline 建立將 image deploy 上 AWS 的 CD 流程,再利用 AWS 的 container 服務 ECS 來運行 Laravel 的 web application。

另外,作為工程師,懶是最大的美德,能電腦做的事情就不要自己做,能用程式跑的東西就不要自己手動按,所以我們也會使用 infrastructure as code 的工具 Terraform 來維護以及持續進化各項基礎建設。(重點是雲裡那麼多複雜的設定,沒有程式碼筆者根本記不起來)

這 30 天會使用 Linux 作為主要操作平台,各種指令與工具等等都以 Linux 為主。使用到的主要工具都是跨平台的,Windows 跟 MAC 原則上都能使用,只是文章會以 Linux 為主說明,用其他平台的話可能需要自己摸索工具如何安裝、設定檔在哪裡等等。

我們會從本機 build docker image、啟動 container 開始,一路設定 Gitlab、使用 AWS web console 以及 command line 工具建立各項基礎建設,再到使用 terraform 並進一步讓我們的雲端架構達到 high availability。因為起點是 Linux、Gitlab 跟 Docker,讀者需要對 Linux、Git 以及 Docker 有基本了解,像是 Docker 知道有 image、image 可以拿來跑 container 以及 Docker Hub 是什麼就夠了。

開始前的提醒:這 30 天內會使用 AWS 的服務,如果讀者沒有用過 AWS,可以申請帳號、利用一年的免費使用時間。如果讀者已經超過一年的免費使用期,請注意:照著本系列文章操作,是會有費用產生的,筆者在範例中盡可能用費用較低的資源,但是讀者須自行注意資源的使用情形與花費。(講到 AWS 會再提醒一次並說明如何使用 AWS Budgets 功能)

行前最後提醒:這個旅程……不會很順利,各位會看到 DevOps 充滿青春與汗水(??)的日常生活。

好!那就讓我們從在本機 build 出 Laravel 的 Docker image 開始吧~

Base Docker Image 介紹

我們這裡要使用的是 Ernest Chiangnginx + php-fpm docker image 作為 base image(感謝大大!)這是一個把 nginx 跟 php-fpm 結合起來的 docker image,我們只要把 Laravel 的程式碼跟一些相關設定放進去,就是個可以運行的 image 了!

這個 image 是以 supervisor 來啟動及監控 nginx、php-fpm worker 等 process。supervisor 會維持設定的 process 數量,發現 process 被(各種原因)關掉的話會重新啟動它。這麼做才能確保我們的 container 一直有執行著需要的 process,不會因為 process 當掉之類的問題就整個無法正常提供服務。

啟動 MySQL container

有了 Web Server 跟 PHP 後,絕大多數情況都需要的還有 Database。為了讓 Laravel 可以順利執行,我們先隨便用 docker 準備個 MySQL database 給它連:

$ sudo docker run -d \
  -e MYSQL_ALLOW_EMPTY_PASSWORD=true \
  -e MYSQL_DATABASE=laravel \
  --name dbserv \
  mysql:8.0

這個指令翻成白話文:在背景啟動一個 MySQL 8.0 的 container 叫做 dbserv,並且以環境變數允許帳號的密碼是空的、預設 database 叫做 laravel。

允許空密碼、使用 default database 作為 laravel 的 database 只是為了實驗而簡化的作法,一般環境不建議這麼使用!(好孩子不要偷懶

啟動 container 後可以用 sudo docker ps 看到執行中的 container 們:

CONTAINER ID   IMAGE                         COMMAND                  CREATED          STATUS          PORTS                 NAMES
f335009dde55   mysql:8.0                     "docker-entrypoint.s…"   43 minutes ago   Up 43 minutes   3306/tcp, 33060/tcp   dbserv

為了設定 Laravel 的 database 連線資訊並且降低範例的麻煩複雜度,我們直接用 container 的 IP 來連線,所以用 sudo docker inspect dbserv 來查 dbserv 這個 container 的 IP address。

$ docker inspect dbserv
...
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "014262341ff329e4be2a7c0c87249540164cbb19b3c2a81770007cab657c261f",
                    "EndpointID": "a85ad67a24a8d24043ee1fb909139cf5faa406fe771fc213f31199915925faff",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.3",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:03",
                    "DriverOpts": null
                }
...

在 Networks block 看到 IP address 是172.17.0.3 ,所以我們在 .env 的 DATABASE 相關設定要設成:

DB_CONNECTION=mysql
DB_HOST=172.17.0.3
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

Dockerfile 及其快樂夥伴們

接下來看看 docker image 的靈魂:Dockerfile!每行指令的意思直接標注在 comment,完整程式碼在 Gitlab

# 使用 Ernest Chiang 的 nginx + php-fpm docker image 作為 base image
# 選擇 bullseye flavor 的 image 在使用上比較簡單,但產生出來的 image 可能比較大
FROM dwchiang/nginx-php-fpm:8.2.5-fpm-bullseye-nginx-1.24.0

# 工作目錄在 /var/www/html,也是 web server 預設的目錄
WORKDIR /var/www/html

# 告訴社會大眾(?)這個 image 會使用 port 80
EXPOSE 80

# 在 image 內以 apt 安裝各種需要的 package
RUN apt-get --allow-releaseinfo-change update && \
    apt-get install --no-install-recommends --no-install-suggests -y \
    cron \
    ssh \
    wget \
    libzip-dev && \
    docker-php-ext-install zip pdo_mysql && \
    apt-get remove --purge --auto-remove -y && apt-get autoclean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/partial/*

# 複製 nginx 設定
COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf

# 安裝 composer
RUN wget https://getcomposer.org/installer -O - -q | \
    php -- --install-dir=/usr/local/bin --filename=composer --2 --quiet

# 設定 Laravel Schedule 需要的 cron job
RUN echo "* * * * * root /usr/local/bin/php /var/www/html/artisan schedule:run >> /var/log/cron.log 2>&1" >> /etc/crontab

# Laravel 相關
# 複製 host 上目前目錄所有檔案到 image 的 /var/www/html/ 底下
COPY ./ /var/www/html
# 我們要以 supervisor 執行 Laravel 的 worker,所以要複製 worker 的 supervisor 設定進 image
COPY docker/laravel-worker.conf /etc/supervisor/conf.d/laravel-worker.conf
# composer install 並且設定 Laravel 相關 directory 的權限
RUN composer install --no-dev && \
  chown -R www-data:www-data storage/ &&  \
  chown -R www-data:www-data bootstrap/cache

# container 跑起來時預設會執行的 script,把它複製進去並且加上執行權限
COPY docker/docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh

# container 跑起來時預設執行的指令,會執行我們複製進去的 entry script
CMD /docker-entrypoint.sh

Docker image 的 flavor

很多 docker image 後面會接像是 -buster-bullseye-slim-alpine 的 suffix,這些 suffix 表示不同的 image flavor(口味?),也就是它們基於什麼 Linux distribution 而來或者有什麼特性。

常見的口味(?)有:

  • 沒有 suffix 的 full official image:建立在最新的 stable Debian 上

  • buster/bullseye 等等 Debian codename:建立在某個版本的 Debian 上。

  • slim :從 full image 瘦身而來的 image。

  • alpine :以 Alpine Linux 為基礎的 image,優點是 image 小,但可能需要處理相容性問題。

docker/nginx/default.conf

docker/nginx/default.conf 是 nginx 的設定,使用 Laravel 官方設定 做兩處修改:

  • root /var/www/html/public; 改到 image 放 Laravel code 的路徑

  • fastcgi_pass 127.0.0.1:9000;

    因為我們以 php-fpm 預設方式啟動,它會聽 localhost 的 port 9000 來收 request (ref),所以我們要在 nginx 把 request 傳過去。

server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    root /var/www/html/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

docker/laravel-worker.conf

前面說到 image 是用 supervisor 把各種 process 執行起來並且監控它們,所以我們也用 supervisor 把 Laravel worker 執行起來,以下是 worker 用的 supervisor 設定:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/var/www/html/worker.log
stopwaitsecs=3600

大致來說是以 user www-data 來執行 8 個 process,會把 log 記錄在 /var/www/html/worker.log ,會自動重啟等等。

docker/docker-entrypoint.sh

entry script 是從 source 複製來的,後面加入跟 Laravel 有關的啟動 crontab、執行 migration、做各種 cache 的指令。

#!/bin/sh
# vim:sw=4:ts=4:et

set -e

if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then
    exec 3>&1
else
    exec 3>/dev/null
fi

if [ "$1" = "nginx" -o "$1" = "nginx-debug" ]; then
    if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
        echo >&3 "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration"

        echo >&3 "$0: Looking for shell scripts in /docker-entrypoint.d/"
        find "/docker-entrypoint.d/" -follow -type f -print | sort -n | while read -r f; do
            case "$f" in
                *.sh)
                    if [ -x "$f" ]; then
                        echo >&3 "$0: Launching $f";
                        "$f"
                    else
                        # warn on shell scripts without exec bit
                        echo >&3 "$0: Ignoring $f, not executable";
                    fi
                    ;;
                *) echo >&3 "$0: Ignoring $f";;
            esac
        done

        echo >&3 "$0: Configuration complete; ready for start up"
    else
        echo >&3 "$0: No files found in /docker-entrypoint.d/, skipping configuration"
    fi
fi

# start cron
/etc/init.d/cron start

# app
php artisan migrate --force
php artisan cache:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache

# exec "$@"
supervisord -n -c /etc/supervisord.conf

Build image

寫好 Dockerfile 就是要編 image 啦~我們以目前目錄作為 context 來 build image,context 可以想成建立 image 的環境,通常會是 Dockerfile 的所在地。用 -t 給 image 上 my-app 的 tag,之後可以用來在操作中指定 image (docker build 指令 ref):

$ docker build . -t my-app

Run container

build 好 image 後,終於要啟動 container 看看能不能成功執行 Laravel:

$ sudo docker run -it -p 8000:80 --name app my-app

這裡我們以 tag 為 my-app 的 docker image 建立並啟動 container,將 container 命名為 app,並且讓我們可以透過 console 直接操作 container。另外,把本機的 port 8000 對應到 container 的 port 80,如此對應後存取本機 port 8000 等同存取 container 的 port 80,也就是一般 web server http 使用的 port。(docker run 指令 ref)

啟動 container 後會看到 entry script 執行的 output:

https://ithelp.ithome.com.tw/upload/images/20230911/20160671nWzy3syT6r.png

如果中間有任何錯誤也會顯示出來並且(大多數狀況下)container 會停止執行,最常遇到的是各種 Laravel 相關的錯誤,諸如連不上 database、沒有 app key 等等。我們的 container 看起來挺正常的,用瀏覽器連上 http://127.0.0.1:8000 看看吧!

https://ithelp.ithome.com.tw/upload/images/20230911/201606714DSy4H47P1.png

是 Laravel 的歡迎畫面!成功啦!

停止 & 清除 container

執行完 container 要停止有幾個方法:

  1. 在上面 container 的 console 畫面直接 ctrl + c 結束它

  2. 使用指令 docker stop app 這裡的 app 是 container 的名稱,也可以用 container id

已經關閉的 container 要再啟動執行可以用 docker start [CONTAINER NAME or ID] 。如果想刪除一個 container,關閉(stop)它後用 docker rm [CONTAINER NAME or ID]


下一篇
Day 2 Docker image 推上 ECR
系列文
AWS ECS + Gitlab + Laravel + Terraform 從入門到摔坑30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
chacoteeth
iT邦新手 3 級 ‧ 2023-09-13 00:09:43

要不要讓container來個美美的prompt呀XD

RUN echo 'export PS1="\[\e[0;33m\]\u@\h\[\e[m\]-\[\e[0;34m\]god-cjw\[\e[m\] \w\$ "' >> ~/.bashrc

不過我會需要是因為我切了好幾個container分別跑http server, php-fpm, php-worker
orz

蚊子 iT邦新手 5 級 ‧ 2023-09-13 00:20:11 檢舉

開發用的 container 是蠻需要的XD
話說 nginx + php-fpm 很多都是切好幾個 container 來做的

我要留言

立即登入留言