項目已上傳至GitHub
1. 安裝Docker Compose
參考官方教程
sudo curl -L "https://github.com/docker/compose/releases/download/1.25.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # 下載安裝
# 這一步不FQ比較慢, 可以去直接github下載好拷貝到/usr/local/bin/docker-compose目錄下
sudo chmod +x /usr/local/bin/docker-compose # 應用權限
docker-compose --version # 測試是否安裝成功
2. Dockerfile編寫
考慮到樹莓派無法使用MySql
,選擇了Nginx
+MariaDb
+PHP
+Pdo
。
Dockerfile-php
參考官方readme:
FROM php:7.4-fpm
LABEL author=qyanzh
RUN apt-get update && apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libpng-dev \
&& docker-php-ext-install pdo pdo_mysql \ # 新增行,安裝pdo
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd
5/7更新
如果發現apt-get update
過程很慢, 可以參考docker php-fpm 鏡像修改apt源 - 簡書
Dockerfile-nginx
只聲明了一個端口號,配置文件的掛載放在yml
中進行。
FROM nginx
LABEL author=qyanzh
EXPOSE 2420 # 聲明暴露端口號2420
nginx配置文件
由於不熟悉nginx
,在這一步花了很長時間,遇到了很多錯誤:
- file not found
- 404 Not Found
- 502 Bad Gateway
- 訪問網頁變成直接下載,從下面這張圖可以看出我試了多少次Orz
以上錯誤基本都是由於路徑配置不正確。
正確的配置:
server {
listen 2420;
server_name localhost;
location / {
root /usr/share/my_web/html; # nginx容器中web文件存放目錄,和yml對應
index index.html index.htm;
}
location ~ \.php$ {
root /usr/share/my_web/php; # php容器中web文件存放目錄,和yml對應
fastcgi_pass cphp:9000; # php服務器默認端口9000,后面會解釋為什么是cphp
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
訪問localhost
時,首先nginx
會根據index
指定的順序依次檢查web目錄下的對應文件,如果是html文件
,由nginx
直接展示。如果訪問到php文件
,nginx
會發送一個請求到cphp:9000
,cphp
將其web目錄下對應的php文件
解析為html文件
后返回給nginx
,再由nginx
返回給瀏覽器,nginx
在這里充當反向代理。(具體的原理和location匹配規則在文末的參考資料給出。)
訪問兩個頁面的響應頭:
Dockerfile-mariadb
較簡單,直接在docker-compose.yml
中給出。
Dockerfile-phpmyadmin
FROM phpmyadmin/phpmyadmin
LABEL author=qyanzh
EXPOSE 8080
3. 使用Compose實現多容器運行機制
項目結構
docker-compose.yml
參考官方教程以及twang2218/docker-lnmp(強烈推薦看一看):
version: "3"
services:
php:
image: my-php # 鏡像名
container_name: cphp # 容器名
build:
context: .
dockerfile: Dockerfile-php # 指定自定義的Dockerfile
environment:
MYSQL_PASSWORD: 1234 # 方便直接在php代碼中引用
volumes:
- ./my_web_dir:/usr/share/my_web/php # 掛載到本機web目錄,相當於-v
networks:
- front_end
- back_end
nginx:
image: my-nginx
container_name: cngx
build:
context: .
dockerfile: Dockerfile-nginx
ports:
- "80:2420" # 暴露80端口,相當於 -p 80:2420
volumes:
- ./my_web_dir:/usr/share/my_web/html
- ./default.conf:/etc/nginx/conf.d/default.conf # 掛載配置文件
networks:
- front_end
mariadb:
image: mariadb
container_name: cdb
restart: always
volumes:
- mariadb-data:/bitnami/mariadb # 掛載volumn實現數據持久化
environment:
MYSQL_ROOT_PASSWORD: 1234
networks:
- back_end
phpmyadmin:
image: my-phpmyadmin
container_name: cadm
build:
context: .
dockerfile: Dockerfile-phpmyadmin
ports:
- "8080:80" # phpmyadmin默認監聽80
environment:
PMA_HOST: cdb # 指定mysql服務所在的host
networks:
- back_end
volumes:
mariadb-data:
# 自定義網絡的原因參見下方php訪問數據庫測試
networks:
front_end:
back_end:
通過volumn
將容器目錄掛載本地,在本地的修改可以直接被容器讀取,非常方便。:
前面的表示要掛載的本地目錄/文件,:
后面表示對應的容器內目錄/文件。
構建運行
docker-compose down # 移除之前運行的
docker-compose up -d --build # 這也是個坑點,修改過后要加--build否則用的還是之前的鏡像
4. 服務測試
nginx+php測試
mariadb測試
mysql -h localhost -u root -p
然而報錯
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
經查是因為mariadb
是運行在cdb
中的,而cdb
作為獨立的容器有自己的IP,所以就無法通過宿主機IPlocalhost
來訪問,需要先查一下容器IP。
docker inspect cdb
mysql -h 172.20.0.2 -u root -p
php對數據庫操作測試
參考菜鳥教程的代碼,做一些必要的修改:
由於容器間互相隔離,localhost
需要改掉。根據mariadb測試,應該把$host
的值改為cdb的IP
,但是每次容器重啟后這個IP是會變化的,就需要每次去改測試代碼。容易想到的方式是給其分配靜態IP,但這樣太不優雅了。經過查閱資料,發現位於同一網絡下的容器可以通過容器名相互訪問,為了隔離性,應把不同職責的容器分散在不同網絡中。cdb
和cphp
同屬於back_end
網絡,所以將$host
的值改為容器名cdb
即可。上面的nginx配置文件
中fastcgi_pass
的取值同理。
<?php
$dbms = 'mysql'; //數據庫類型
$host = 'cdb'; //數據庫主機名,修改為容器名
$user = 'root'; //數據庫連接用戶名
$pass = $_ENV["MYSQL_PASSWORD"]; //通過yml中定義的環境變量獲得對應的密碼
$dsn = "$dbms:host=$host;"; // "mysql:host=cdb"
try {
$dbh = new PDO($dsn, $user, $pass); //初始化一個PDO對象
echo "連接成功<br/>";
$dbh = null;
} catch (PDOException $e) {
die("Error!: " . $e->getMessage() . "<br/>");
}
//默認這個不是長連接,如果需要數據庫長連接,需要最后加一個參數:array(PDO::ATTR_PERSISTENT => true) 變成這樣:
$db = new PDO($dsn, $user, $pass, array(PDO::ATTR_PERSISTENT => true));
$db->exec("create database if not exists test_db;");
$db->exec("use test_db;");
$db->exec("create table if not exists t(num integer);");
$db->exec("delete from t;");
// 增
$db->exec("insert into t(num) values(2420);");
showDb($db, "insert:\n");
// 改
$db->exec("update t set num=031702420 where num=2420;");
showDb($db, "update:\n");
// 刪
$db->exec("delete from t;");
showDb($db, "delete:\n");
// 查
function showDb($db, $op)
{
print_r($op);
$sql = "select * from t";
$result = $db->query($sql);
while ($arr = $result->fetch()) {
print_r($arr);
}
echo "<br/>";
}
如果出現Connection Refused
,可能是因為數據庫未啟動完畢,大約需要10秒。可以將up
指令中的-d
去掉來看容器的狀態(或者docker logs
),直到出現:
cdb | Version: '10.4.12-MariaDB-1:10.4.12+maria~bionic' socket: '/var/run/mysqld/mysqld.sock' port: 3306
5. PhpMyAdmin
參考通過Docker為MySQL安裝phpMyAdmin管理界面_運維_嘿客的技術閑筆-CSDN博客,具體已在yml
中給出。
第一次沒設置PMA_HOST
環境變量,導致無法訪問。錯誤信息也說明是主機名找不到,在yml
中設置為cdb
即可。
附錄:一鍵刪除命令
# 刪除所有容器
docker rm -f $(docker ps -aq)
# 刪除所有鏡像
docker rmi $(docker images -q)
# 刪除所有<none>鏡像
docker rmi $(docker images | grep "none" | awk '{print $3}')
用時和體會
加上博客約14小時。做完這次實踐對Docker的理解深了不少,相比前兩次蜻蜓點水般的使用,這次更有完整項目的感覺。面對沒學過的東西,搜集資料和debug的過程是冗長且痛苦的,但這正是鍛煉自學能力的機會。
參考
Nginx+Php-fpm運行原理詳解_運維_一路向前ylc-CSDN博客
Nginx location 匹配順序整理 - python修行路 - 博客園
Get started with Docker Compose | Docker Documentation
twang2218/docker-lnmp: Docker example of LNMP setup (Compose, Swarm)
docker-compose 安裝 mariadb數據庫_運維_jiangbenchu的博客-CSDN博客
PDO 基本使用(簡)_PHP_jia_1418422386的博客-CSDN博客
docker 如何刪除none鏡像_運維_李留白-CSDN博客
phpmyadmin/phpmyadmin - Docker Hub