Quay lại

GitHub Actions | CI/CD Với Laravel App | Deployer | Slack Noti Chuyên mục Devops    2024-02-16    126 Lượt xem    95 Lượt thích    comment-3 Created with Sketch Beta. 0 Bình luận

GitHub Actions | CI/CD Với Laravel App | Deployer | Slack Noti

Xin chào tất cả mọi người hôm nay mình sẽ hướng dẫn các bạn triển khai CI/CD 1 Laravel application bằng GitHub Actions một tính năng rất hữu ích của github giúp chúng ta tối ưu cũng như là giảm thiểu thời gian trong quá trình deploy 1 ứng dụng, như các bạn đã biết thì chúng ta có rất nhiều công cụ để triển khai CI/CD như GitHub Actions, Jenkin, GitLab CI/CD, Travis CI...Câu hỏi đặt ra là nó nhiều loại như thế thì nên dùng loại nào cho phù hợp và nó được dùng khi nào. Ok mình sẽ lấy 1 so sánh giữa Github Actions và Jenkin và đánh giá ưu và nhược điểm để cho các bạn dễ hiểu hơn nhé !

So sánh Github Actions và Jenkin

Lựa chọn giữa GitHub Actions và Jenkins cho việc triển khai CI/CD phụ thuộc vào nhiều yếu tố, bao gồm kích thước dự án, yêu cầu cụ thể của dự án, mức độ linh hoạt và quen thuộc của bạn với các công nghệ này. Dưới đây là một số yếu tố để cân nhắc:

GitHub Actions:

  1. Dự án tích hợp chặt chẽ với GitHub: Nếu dự án của bạn đã sử dụng GitHub để quản lý mã nguồn và sử dụng các tính năng của GitHub như Issues, Pull Requests, Projects, thì GitHub Actions có thể là một lựa chọn tự nhiên.

  2. Tích hợp dễ dàng: GitHub Actions được tích hợp trực tiếp với GitHub repository của bạn, giúp bạn dễ dàng cấu hình các luồng CI/CD ngay trên giao diện của GitHub.

  3. Chi phí: GitHub Actions cung cấp một số lượng lớn lượt chạy miễn phí cho các dự án mã nguồn mở và có giới hạn cho các dự án tư nhân, điều này có thể giúp giảm chi phí so với việc triển khai Jenkins.

  4. Ưu điểm và nhược điểm:

    Ưu điểm:

    1. Tích hợp sẵn với GitHub: GitHub Actions được tích hợp trực tiếp với GitHub, cho phép bạn cấu hình và quản lý CI/CD workflows trên cùng một nền tảng.
    2. Dễ sử dụng: GitHub Actions sử dụng định dạng YAML đơn giản để định nghĩa các workflows, làm cho việc cấu hình trở nên dễ dàng và linh hoạt.
    3. Miễn phí cho dự án mã nguồn mở: GitHub Actions cung cấp một số lượng lớn các phút chạy miễn phí cho các dự án mã nguồn mở, giúp giảm chi phí cho các dự án nhỏ hoặc cá nhân.
    4. Hỗ trợ nhiều hệ sinh thái: GitHub Actions cung cấp nhiều action có sẵn từ cộng đồng và cũng hỗ trợ Docker để chạy bất kỳ công cụ hoặc script nào bạn cần.

    Nhược điểm:

    1. Hạn chế về customization: Mặc dù GitHub Actions cung cấp một loạt các action có sẵn, nhưng nó có thể hạn chế về tính linh hoạt so với Jenkins trong việc tùy chỉnh các workflow phức tạp.
    2. Quản lý secrets: GitHub Actions có thể làm cho quản lý secrets nhạy cảm như thông tin xác thực trở nên phức tạp hơn so với các công cụ CI/CD khác.

Jenkins:

  1. Tính linh hoạt và mở rộng: Jenkins là một công cụ CI/CD mạnh mẽ và linh hoạt, cho phép bạn tùy chỉnh các quy trình triển khai theo nhu cầu cụ thể của dự án. Nó có nhiều plugin sẵn có để hỗ trợ tích hợp với các công cụ khác và mở rộng khả năng.

  2. Quản lý phức tạp: Nếu dự án của bạn có nhiều quy trình triển khai phức tạp, yêu cầu tích hợp với nhiều công cụ và môi trường khác nhau, Jenkins có thể là lựa chọn tốt hơn.

  3. Tích hợp với hệ thống hiện có: Nếu bạn đã sử dụng Jenkins hoặc có các yêu cầu tích hợp với các hệ thống đã tồn tại, việc triển khai Jenkins có thể dễ dàng hơn.

  4. Ưu điểm và nhược điểm:

    Ưu điểm:

    1. Tính linh hoạt và mở rộng: Jenkins là một công cụ CI/CD mạnh mẽ và linh hoạt, cho phép bạn tùy chỉnh và mở rộng các quy trình triển khai theo nhu cầu cụ thể của dự án.
    2. Hỗ trợ nhiều plugin: Jenkins có một hệ sinh thái plugin phong phú, cung cấp hàng trăm plugin để tích hợp với các công cụ và dịch vụ khác.
    3. Quản lý secrets: Jenkins cung cấp cơ chế quản lý secrets tốt hơn, cho phép bạn dễ dàng quản lý thông tin xác thực và các biến môi trường nhạy cảm.

    Nhược điểm:

    1. Cấu hình phức tạp: Việc cấu hình Jenkins có thể phức tạp hơn so với GitHub Actions, đặc biệt là đối với người mới bắt đầu.
    2. Yêu cầu cài đặt và quản lý máy chủ riêng: Jenkins yêu cầu bạn tự cài đặt và quản lý máy chủ của riêng mình, điều này có thể tạo ra chi phí và công sức quản lý.
    3. Hiệu suất: Trong một số trường hợp, hiệu suất của Jenkins có thể không tốt bằng các công cụ CI/CD cloud-native như GitHub Actions, đặc biệt là khi quy mô dự án lớn.

Tóm lại, GitHub Actions thích hợp cho các dự án tích hợp chặt chẽ với GitHub và đòi hỏi sự đơn giản và hiệu quả, trong khi Jenkins thích hợp cho các dự án có yêu cầu tích hợp phức tạp và đa dạng. Quyết định cuối cùng phụ thuộc vào nhu cầu cụ thể của dự án và sự thoải mái của bạn trong việc sử dụng các công nghệ này.

Chuẩn bị môi trường

  • OS: Ubuntu 20.0
  • Development Tool:
    • LEMP Stack (Linux + Nginx+ MySQL + PHP)
      • PHP 8.1 (cli)
      • Nginx version
      • MySql version: 8.0.17
    • Git git version 2.18.1 (Linux)
    • Composer 2
  • Editor: Vim

Tạo Laravel App

Với bài viết này các bạn có thể ứng dụng thực tế vào các dự án của các bạn luôn, nếu bạn nào chỉ muốn học thôi thì có thể tạo ra 1 Laravel app mới như mình nhé! Việc tạo ra một Laravel app bằng composer đã trở nên rất quen thuộc với chúng ta rồi đúng không, mình sẽ không quá đi sâu chi tiết về nó nữa các bạn có thể lên Laravel doc để  install nhé!

Sau khi tạo xong các bạn chạy lệnh sau để cài đặt một số package cần thiết

  1. Deployer package: Để phục vụ cho chúng ta triển khai pull code cũng như là chạy các lệnh cài đặt trong Laravel sau này:
    • composer require deployer/deployer:^7.3
  2. Cài đặt phpcs (optional): Dùng để check các coding convention trước khi merge code vào nhánh chính nếu các bạn tự hỏi tại sao lại chạy với require --dev thì xem bài hiểu về composer.json nhé nhé!
    • composer require --dev "squizlabs/php_codesniffer=^3.7"
    • Hoặc bạn có thể sử dụng:
    • {
          "require-dev": {
              "squizlabs/php_codesniffer": "3.*"
          }
      }
    • Sau khi cài đặt xong ở root folder của bạn, tạo thêm file cấu hình phpcs.xml để tùy chỉnh các thư mục mà bạn muốn check các coding convention File này khá dài nên các bạn vào github của mình để lấy nhé.
    • Sau đó thì thử test bằng lệnh sau:
      • ./vendor/bin/phpcs

Cấu hình PHP Deployer

Sau khi các bạn cài đặt xong package deployer thì các cần phải tạo thêm 1 file deploy.php ở thư mục gốc trong dự án của các bạn, cùng cấp với file composer.json, sau đó copy đoạn code mình đã chuẩn bị cho các bạn ở trên Github của mình và paste vào file deploy.php để hiểu được từng câu lệnh trong file này mình đã tách ra một phần riêng các bạn có thể đọc để hiểu ở bài Giải thích PHP Deployer trong Laravel.

Tạo Repository Laravel App trên github

Sau khi các bạn đã tạo xong Laravel App của chúng ta thì chúng ta cần phải đẩy lên github đúng không? Việc này cũng rất là quen thuộc rồi bạn nào chưa biết thì có thể xem 1 bài hướng dẫn đẩy code lên github của mình, các bạn nên để private repo nha cho nó đúng với dự án thực tế của chúng ta.

Dựng môi trường triển khai

Để có thể triển khai được ứng dụng của chúng ta lên môi trường Staging hay Production thì chúng ta cần phải có 1 con server (máy chủ), tùy vào công ty của các bạn, có thể thuê một máy chủ EC2 trong AWS hoặc có thể mua một bên thứ 3 họ cung cấp VPS cho các bạn.

Ở bài viết này mình sẽ sử dụng một server từ EC2, bạn nào chưa biết về EC2 hoặc cách tạo một máy chủ từ EC2 thì có thể tham khảo bài AWS EC2 Service - Phần 1. Mình sẽ cùng nhau đi từng bước cài đặt và triển khai nhé ! LET GOOO.

Bước 1: SSH vào server

Sau khi các bạn có 1 instance, aws đã cung cấp cho chúng ta 1 DNS VÀ Public IP rồi các bạn nhấn vào xem chi tiết của instance để biết thêm thông tin nhé, để SSH được các bạn xem các bước dưới đây. Nếu các bạn không sử dụng AWS thì bỏ qua bước này chỉ cần SSH vào server là OK, chuyển sáng Bước 2 luôn.

Lưu ý: Các bạn cần phải thay đổi thông tin thành EC2 server của các bạn nhé, bên dưới là thông tin máy chủ của mình.

  1. Open an SSH client hoặc ternminal dưới máy host của các bạn.
  2. Xác định vị trí file pem của bạn. File pem dùng để khởi chạy instance, trong demo này là son-pem.pem
  3. Chạy lệnh này, nếu cần, để đảm bảo khóa của bạn không thể xem được công khai.
    • chmod 400 "son-pem.pem"
  4. Connect đến Public DNS (Không cần chạy cái này, nó chỉ là DNS name):
    •  ec2-52-199-2-74.ap-northeast-1.compute.amazonaws.com
  5. Thực thi lệnh sau để SSH vào server (nhớ thay thông tin của các bạn):
    •  ssh -i "son-pem.pem" ubuntu@ec2-52-199-2-74.ap-northeast-1.compute.amazonaws.com

Các bạn nhìn SSH và và nhìn thấy được như hình dưới là OK.

Bước 2: Cài đặt môi trường cho dự án

  1. Cập nhật hệ thống Ubuntu: Cập nhật hệ thống Ubuntu để bắt đầu quá trình cài đặt PHP 8.1 trên Ubuntu 20.04 bằng cách đảm bảo rằng hệ thống đã được cập nhật và nâng cấp.
    • sudo apt update && sudo apt -y upgrade
  2. Thêm kho lưu trữ PPA của Ondřej Surý: Đối với Ubuntu 20.04, các gói nhị phân PHP 8.1 có sẵn trong kho lưu trữ PPA của Ondřej Surý. Kho lưu trữ này cần được thêm vào hệ thống một cách thủ công bằng cmd dưới đây:
    • sudo apt update
      sudo apt install lsb-release ca-certificates apt-transport-https software-properties-common -y
      sudo add-apt-repository ppa:ondrej/php
      sudo apt update
  3. Install PHP 8.1 on Ubuntu 20.04: 
    1. Chúng ta giờ đã có thể install PHP 8.1 trên máy Linux Ubuntu 20.04. Chạy lệnh sau:
      • Lưu ý: Khi cài lệnh dưới nó có hỏi muốn cài Apache không thì các bạn chọn No nhé, vì chúng ta sẽ sử dụng Nginx, Nếu các bạn nhập Yes, nó sẽ hiện thị giao diện của Apache đó, trong khi lại đang dùng Nginx, để không bị hiểu nhầm nên các bạn chú ý chỗ cài này.
      • sudo apt install php8.1
    2. Kiểm tra version:
      • php --version
  4. Cài đặt các package đi kèm PHP 8.1:
    1. sudo apt install php8.1-{bcmath,xml,fpm,mysql,zip,intl,ldap,gd,cli,bz2,curl,mbstring,pgsql,opcache,soap,cgi}
    2. php --modules
  5. Kiểm tra php-fpm đã được chạy chưa: Nếu sử dụng PHP với máy chủ web Nginx, đảm bảo dịch vụ php-fpm đã được bật và chạy hay chưa:
    • systemctl status php*-fpm.service
    • Cấu hình mặc định của PHP-FPM để thiết lập socket listener, user và thông tin khác được đặt trong:
      • $ ls  -1 /etc/php/8.1/fpm/
        conf.d
        php-fpm.conf
        php.ini
        pool.d
        $ sudo vim /etc/php/8.1/fpm/pool.d/www.conf
        $ sudo vim /etc/php/8.1/fpm/php-fpm.conf
  6. Setting Up Nginx:
    • sudo apt update
      sudo apt install nginx
      sudo systemctl enable nginx
      sudo systemctl enable  php8.1-fpm
    • systemctl status nginx
    • Sau khi các bạn cài đặt xong và copy IP lên browser mà chạy được như hình bên dưới là thành công
    • Trong trường hợp bạn DÙNG EC2 instance thì cần phải vào security group và thêm port 80 nhé!
    • Còn 1 trường hợp nữa nếu bạn không ra ngoài được internet thì cần phải check lại Firewall theo các bước dưới đây:
      • sudo ufw app list
      • sudo ufw allow 'Nginx HTTP'
      • sudo ufw status
  7. Cài đặt Git:
    1. sudo apt update
    2. sudo apt install git
    3. git --version
    4. git config --global user.name "Your Name"
      git config --global user.email "youremail@domain.com"
      git config --list
  8. Cài đặt composer:
    1. sudo apt update
      cd ~
    2. curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php
    3. HASH=`curl -sS https://composer.github.io/installer.sig`
    4. echo $HASH
    5. php -r "if (hash_file('SHA384', '/tmp/composer-setup.php') === '$HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
    6. sudo php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
    7. Output
      All settings correct for using Composer
      Downloading...
      
      Composer (version 2.3.5) successfully installed to: /usr/local/bin/composer
      Use it: php /usr/local/bin/composer
    8. composer
  9. Tạo thư mục cho Laravel app trên server:
    1. sudo su -
      cd /var/www/html
      
    2. Tạo thư mục cho dự án của bạn. Ở đây mình đặt là laravel-app, các bạn có thể đặt theo dự án của các bạn. Sau đó mình sẽ clone dự án của mình vào trong folder này ở bước sau.
      • sudo mkdir laravel-app
  10. Cài một số packages cần thiết (Bắt buộc):
    • sudo apt-get install acl
  11. Tạo user deploy:
    Trước khi các bạn làm theo các bước bên dưới hãy kiếm tra giúp mình hiện tại Nginx của các bạn đang sử dụng user nàonhóm nào bằng lệnh sau:
    sudo vim /etc/php/8.1/fpm/pool.d/www.conf​

    Các bạn sẽ thấy ở dòng này:

    listen.owner = www-data //user của Nginx
    listen.group = www-data // group của Nginx
    ;listen.mode = 0660
    

    1. Tạo ra một user deployer để chuyên phục vụ deploy code cho dự án.
      • sudo adduser deployer
    2. Laravel cần quyền đọc ghi dữ liệu để lưu cache file và upload file. Do đó, các thư mục tạo bởi deployer phải có quyền đọc ghi qua Nginx web server. Thêm nhóm quyền www-data của Nginx vào user deployer bằng command sau:
      • sudo usermod -aG www-data deployer
    3. Tiếp theo, cần cấu hình mặc định quyền khi tạo mới file, folder bằng user deployer là 644 (file) và 755 (folder). Điều này giúp cho deployer có thể đọc và ghi file, trong khi các nhóm hoặc user khác có thể đọc được. Thực hiện cấu hình này bằng lệnh umask =022 như sau:
      • sudo chfn -o umask=022 deployer
    4. Source code upload lên server sẽ lưu ở /var/www/html/laravel-app, nên chúng ta cần đổi ownership của folder cho user deployer và nhóm người dùng của Nginx là www-data.
      • sudo chown deployer:www-data /var/www/html/laravel-app
    5. User deployer cần quyền sửa các file và folder trong /var/www/html/laravel-app ngay cả khi được tạo sau này. Như vậy mọi file, folder, sub-folder được tạo trong folder đều kế thừa permission của folder cha. Thực hiện câu lệnh sau để set group id cho folder:
      • sudo chmod g+s /var/www/html/laravel-app
  12. Thiết lập GitHub SSH connections:
    User deployer sẽ clone Git repo về máy server qua SSH, vì vậy ở bước này sẽ hướng dẫn các bạn generate SSH key để cấu hình trên Github.
    1. cd /home
    2. su - deployer​
    3. Tiếp theo, generate SSH key pair với deployer user. Bước này bạn có thể cần accept tên file, password mặc định. (Chỉ cần nhấn enter k làm gì cả).
      • ssh-keygen -o -t rsa -C 'example@gmail.com'
    4. Chạy lệnh dưới để hiện thị public key và sau đó copy:
      • cat ~/.ssh/id_rsa.pub
    5. Thêm SSH key vào Github: 
      1. Link: https://github.com/settings/ssh/new
      2. Config SSH key
      3. Test connect:
        • ssh -T git@github.com
  13. Clone code về server:
    1. Sang user deployer và vào thư mục chứa dự án của chúng ta:
      • cd /var/www/html/laravel-app
    2. Clone code:
      1.  
      2. git clone your_SSH_repo
      3. cd /your_project
    3. Cài đặt:
      1. cp .env.example .env
        chmod 644 .env
        php artisan key:generate
        chmod -Rf 775 storage/
      2. composer install
      3. Nếu dự án của bạn cần chạy thêm lệnh gì. Thì cứ chạy như bình thường nhé! Ví dụ:
        • php artisan migrate
          php artisan db:seed
    4. Config .env: Cấu hình này các bạn sẽ phải tự cấu hình cho phù hợp với dự án của bạn.
  14. Cài đặt node trong user deployer(optional) : Sang user deployer và cài node, nếu trong dự án của bạn có sử dụng node thì hãy làm theo bước này.
    1. curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh
    2. curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
    3. source ~/.bashrc
    4. nvm list-remote
    5. Chọn version node mà bạn muốn install.

      nvm install v14.10.0
  15. Cấu hình Nginx cho dự án:
    1. Tạo nginx config: Hãy thay example.com.confg thành domain của các bạn trong demo này mình sẽ dùng với tên là laravel-app.com.conf
      • sudo nano /etc/nginx/sites-available/example.com.conf 
    2. Sau khi tạo xong thì các bạn copy đoạn config nginx dưới đây và thay thế root path folder đến dự án của bạn:
      • server {
            listen 80;
            server_name server_domain_or_IP;
            root /var/www/html/laravel-app/your_project/public;
        
            add_header X-Frame-Options "SAMEORIGIN";
            add_header X-XSS-Protection "1; mode=block";
            add_header X-Content-Type-Options "nosniff";
        
            index index.html index.htm 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 unix:/var/run/php/php8.1-fpm.sock;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
                include fastcgi_params;
            }
        
            location ~ /\.(?!well-known).* {
                deny all;
            }
        }
    3. Tạo symbolic link đến folder sites-enabled:
      • sudo ln -s /etc/nginx/sites-available/laravel-app.com.conf /etc/nginx/sites-enabled/
    4. Kiểm tra xem cấu hình:
      •  sudo nginx -t
    5. Restart Nginx:
      • sudo systemctl restart nginx
    6. Lưu ý:
      1. Khi cài Nginx mặc định 1 file default đc tạo trong /sites-available và nó đang sử dụng cổng 80, khi các bạn tạo 1 file mới như trên mà cũng sử dụng cổng 80 thì nó sẽ bị conflict, Vì thế bạn có thể xóa file default đó đi.
      2. Trong trường hợp mà bị lỗi như dưới: Ờ well, reboot lại server giúp mình nhé!
        1. kex_exchange_identification: Connection closed by remote host
  16. Cài đặt và cấu hình Mysql:
    1. Cài đặt:
      • sudo apt install mysql-server
        sudo mysql_secure_installation
    2. Vào Mysql console:
      • sudo mysql
      • Hoặc:
      • mysql -u root -p
    3. Tạo mới database:
      • mysql> CREATE DATABASE laravel_database DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    4. Tạo mới database user:
      • mysql> CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'password';
    5. Gán quyền cho user tương ứng với database vừa tạo:
      • mysql> GRANT ALL ON laravel_database.* TO 'laravel_user'@'localhost';
    6. Reload quyền:
      • mysql> FLUSH PRIVILEGES;
    7. Thoát SQL console:
      • mysql> EXIT;
  17. Kết quả:
  18. Bước cuối: Thiết lập Actions secrets và variables cho user deployer
    Bước này chúng ta sẽ cấu hình cho workflow của chúng ta, để nó có thể SSH vào máy chủ của chúng ta và thực thi trên đó, để nó có thể làm được điều này các bạn cần phải cấu hình theo các bước dưới đây:

    1. Vào repo trên github dự án của bạn rồi click Settings > secrets and variables > tab secrets > click new repository secrets, như hình bên dưới:
    2.  Cấu hình secrets:
      1. Thêm private key của user deployer vào secret:
        1. su - deployer​
        2. cat ~/.ssh/id_rsa
        3. Copy private key khi dùng lệnh trên.
        4. Thêm vào secret như hình sau: Trong demo này mình muốn triển khai cho môi trường staging chảng hạn nên mình đặt tên secret là STG_PRIVATE_KEY. Các bạn có thể đặt tên theo loại môi trường mà bạn muốn.
      2. Thêm HostName: HostName ở đây chính là địa chỉ IP or domain server của bạn. Mình sẽ đặt là STG_HOST
      3. Thêm User: User ở đấy chính là user deployer mà chúng ta đã tạo. Mình sẽ đặt tên là STG_USER
      4. Tạo Host config:
        1. Vẫn ở user deployer hãy cd vào thư mục .ssh/ và tạo file config:
          • cd ~/.ssh
            nano config​
          • Copy đoạn dưới đây vào file config của bạn và nhớ thay thế user và domain của bạn:
            • Host app.dev
                  HostName ec2-52-199-2-74.ap-northeast-1.compute.amazonaws.com // domain
                  User deployer // user
                  StrictHostKeyChecking no
        2. Thêm public key để thư viện github deployphp/action@v1 trong workflow có thể truy cập vào được server:
          1. Tạo file authorized_keys 
            • touch authorized_keys
          2. Copy public key vào authorized_keys
            • cat id_rsa.pub
            • Các bạn chạy lệnh cat id_rsa.pub nó sẽ hiện ra mã hash RSA các bạn copy và paste vào file authorized_keys và lưu lại
          3. Nếu bị lỗi Permission denied (publickey) có thể chạy lệnh bên dưới
            • chmod 600 id_rsa

Vì bài hướng dẫn này khá là dài nên các bạn xem thêm phần 2 nhé!

Bình luận (0)

Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough
Michael Gough

Bài viết liên quan

Learning English Everyday