Docker imgproxy - Ultra Image Server

Sau đợt đăng kí thành công gói Oracle VM Standard A1 Flex 4 OCPU 24 GB RAM thì VPS đang dùng của thèng bibica.net khá thừa tài nguyên, nên có ý tìm 1 số giải pháp mới liên quan tới xử lý ảnh 😛

Thành thật mà nói, nếu bạn dùng Wordpress, đã có 1 hệ thống siêu cấp, thiên hạ vô địch là Jetpack Photon, đây là 1 hệ thống khép kín, khi Jetpack sử lý cả phía server và client, người dùng cuối chỉ cần down plugin về, bật chạy là xong

Tất cả giải pháp khác mình xem qua trong khoảng 2-3 tuần nay đều không có cửa so với Photon, phần lớn họ chỉ tích hợp từ phía server, còn xử lý trên hệ thống mỗi khách hàng thế nào thì phải tự xử lý

Sản phẩm đầu tiên mình xem qua là bộ source code của wsrv.nl (tên trước đây là images.weserv.nl), các thứ họ sử dụng rất đơn giản và dễ hiểu

Đầu tiên là dùng libvips để tạo các thumbnail theo nhiều kích cỡ khác nhau -> sau đó dùng nginx để cache các ảnh đã được tạo thumbnail (tránh chuyện xử lý tạo thumbail nhiều lần), tiếp theo là dùng Cloudflare để làm CDN

Cách vận hành này nó hiệu quả tới mức wsrv.nl cung cấp miễn phí cho tất cả mọi người dùng, mà không gặp bất cứ vấn đề gì cơ sở hạ tầng 😀 vì phần nặng nhất là tải ảnh thì Cloudflare gánh cả rồi :]] mình có dùng thử thì thấy chạy rất nhẹ, nén ảnh ra dung lượng nhỏ và chất lượng rất ấn tượng, điểm hơi nhảm là tham số họ thêm vào nó dài và phức tạp một cách không cần thiết, thậm chí thành viên ctohm trên Github còn phải tạo ra 1 phiên bản Edge-Resizer, giúp làm gọn các tùy chỉnh cho đơn giản hơn là hiểu rồi á 😀

Thứ mình phàn nàn về wsrv là không hiểu sao, file cài đặt qua docker của họ không chạy được trên server ARM 😀 làm con VPS 24 GB RAM không tận dụng được 😀

Cách đây ít ngày thì tình cờ mình search ra imgproxy, ấn tượng đầu tiên là họ hỗ trợ rất tốt định dạng AVIF, rất hiếm tool, code, hay dịch vụ nào liên quan tới ảnh hỗ trợ AVIF … dùng thử thì mình thấy không ổn lắm, dù đều dùng libvips để xử lý ảnh, nhưng trên imgproxy mình thấy server khi tạo các thumbnail CPU load cao hơn wsrv.nl (hoặc lý do đơn giản hơn là bản wsrv.nl họ cấu hình chuẩn chỉ cả rồi, còn bản public của imgproxy là bản mặc định, chưa xử lý gì hết)

Tạm vấn đề này không quan trọng, vì nếu chỉ sử lý ảnh, Oracle A1 Flex cân thoải mái 😀

Nếu bạn bật nén AVIF và WEBP, nếu ai đó dùng trình duyệt đời mới, dạng Google Chrome hoặc Firefox, nó sẽ tự load ảnh ở định dạng AVIF, dùng các trình duyệt khác thì sẽ load ảnh WEBP …. cái này thì imgproxy xử lý rất ổn

Vấn đề mình gặp phải khá là củ chuối 😀 nếu bạn dùng Cloudflare miễn phí, Cloudflare sẽ không quan tâm bạn đang dùng AVIF hay WEBP, nó cứ cache tấm ảnh đó, lúc này sẽ nảy sinh tình huống, nếu ai đó dùng Google Chrome vào trang, nó sẽ tự load các ảnh AVIF sau đó cache các file AVIF lên Cloudflare, lúc này, nếu ai đó dùng trình duyệt khác, có thể là Coccoc hay gì đó, chỉ hỗ trợ WEBP, vào xem bài viết cũ đó, sẽ thấy các hình ảnh bị lỗi 😀

Giải pháp sử lý cái này thì đơn giản nhất là bạn tắt cache Cloudflare đi là được 😀

Có điều, lúc này ưu điểm của việc file ảnh AVIF nhỏ, nó không đủ bù cho 1 tỷ các khuyết điểm khác, từ tốn băng thông, giảm tốc độ …. do không được Cloudflare cache 😛

Vấn đề cuối cùng, nghiêm trọng nhất, là gần như không có bất cứ dịch vụ hay script nào, họ viết addon cho Wordpress cả, muốn xử lý Responsive Images bạn phải tự làm 😀

Bạn vừa phải tạo 1 server để xử lý ảnh (tốn chi phí), vừa phải viết thêm plugin để xử lý load ảnh trên Wordpress (cần kiến thức), hiệu quả đạt được, thậm chí còn không bằng các giải pháp miễn phí đang có ngoài thị trường như Jetpack Photon nữa 😀

Tiện thể ngồi xem, mình thấy khá nhiều trang lớn, tới cực lớn, họ thậm chí hoàn toàn không quan tâm tới xử lý ảnh luôn, các ảnh họ upload lên là ảnh to nhất, không có tạo thumbnail vẹo gì 😀 thậm chí định dạng ảnh họ cũng chẳng thèm nén, dùng jpg hay png mặc định luôn 😀 đúng kiểu khi có Cloudflare CDN rồi, mọi giải pháp đều là vẽ vời, làm màu cả 😛

Mỗi cái vẫn tiếc con VPS Oracle 😀 để không thì phí quá, nên cũng ráng vác ra tận dụng để dùng 😀 google thêm 1 hồi thì sực nhớ tới phiên bản Ultra Image Server, viết trên imgproxy của 1 thành viên Việt Nam 😀

Đợt này xem lại thì thấy tác giả Mai Nhựt Tân viết kinh thật 😀 từ link gốc imgproxy chẳng chịt các thứ, ảnh sửa lại gọn gàng, xuống mức gần như tối thiểu

Tất cả các bước cấu hình, tinh chỉnh, imgproxy đều đã được tác giả viết toàn bộ, bạn chỉ cần chạy 2-3 dòng code, là đã tự tạo thành 1 server để xử lý ảnh, có domain, ssl các thứ đầy đủ

Thứ đáng tiếc duy nhất là ảnh không có tích hợp vào Wordpress hay 1 hệ thống cụ thể nào đó, bạn vẫn phải tự xử lý trên một chút trên trang

Phiên bản mình dùng trong bài này, là phiên bản sửa lại từ Ultra Image Server, thay đổi quan trọng nhất, là mình đã tắt chuyển đổi sang định dạng AVIF và WEBP

Lý do là vì các ảnh thèng bibica.net đang sử dụng, phần lớn đều là ảnh mình tự cắt từ desktop, lưu ở định dạng png, mỗi cái không hiểu sao, nếu từ chuyển png -> WEBP (AVIF), chất lượng bị suy giảm rất nhiều, ngồi xem mãi vẫn không rõ lý do tại sao, duy trì định dạng mặc định PNG theo ảnh thì chất lượng rất tốt???

imgproxy có cơ chế tạo watermark rất tuyệt vời, tùy chỉnh tỷ lệ logo watermark dựa vào kích thước ảnh gốc, nên sẽ không gây khó chịu cho người xem, có điều mình cũng tắt đi, vì không có nhu cầu sử dụng

Các thay đổi cụ thể như sau

1. Không sử dụng WEBP, AVIF
2. Tăng giới hạn kích thước và dung lượng file
3. Đổi query width -> w (cho ngắn và dễ nhớ, tương tự Photon)
4. Bổ sung IMGPROXY_SET_CANONICAL_HEADER
5. Tăng thời gian timeout read, write, download, alive ... lên cao hơn 1 chút
6. Tăng thời gian cache mặc định lên 1 năm
7. Tắt sử dụng watermark như mặc định
8. Tăng các file extensions có thể xử lý lên jpe?g|png|gif|tiff?|webp|avif|svg|bmp
9. Có thể sửa chỗ nào đó mà không nhớ :D

Nói chung mình đã cố gắng sửa lại, để nó có thể chạy một cách ổn định hiệu quả nhất trên hệ thống WordPress, mọi người chỉ cần cài vào, sau đó đổi sang domain của mình là được 😛

Cài đặt

Ở bài này mình sẽ cài đặt imgproxy trên VPS Oracle VM.Standard.E2.1.Micro, dù cấu hình này CPU rất yếu, nhưng theo mình cũng đủ dùng 😀

Cấu hình sẵn cho domain img.bibica.net, cài đặt xong chỉ cần đổi DNS về IP này là hết chuyện

Login vào tài khoản root, sau đó chạy các dòng code bên dưới

Cài đặt docker và docker-compose

curl -sSL https://get.docker.com | sh
sudo usermod -aG docker $(whoami)
sudo systemctl start docker
sudo systemctl enable docker
apt install docker-compose -y

Cài đặt imgproxy cho domain img.bibica.net

git clone https://github.com/bibicadotnet/docker-imgproxy-img-bibica-net.git
cd docker-imgproxy-img-bibica-net
docker-compose up -d --build --remove-orphans --force-recreate

Nếu bạn muốn cài đặt cho subdomain khác, và ảnh gốc từ trang của bạn

Mở file nginx.conf, sửa lại dòng 104, 115, 116

Thay đổi custom domain cho imgproxy, .pem và .key là ssl cho custom domain này (đường dẫn tới các thư mục mình đã tạo sẵn bên trong thư mục gốc docker-imgproxy-img-bibica-net)

# dòng 104
        server_name                          img.bibica.net;
# dòng 115,116
         ssl_certificate                      /etc/nginx/certs/bibica.net.pem;
         ssl_certificate_key                  /etc/nginx/certs/bibica.net.key;

Mở file imgproxy.conf, dòng 103, đổi domain bibica.net sang domain của bạn

#dòng 103 
   default         'https://bibica.net';

Sau đó, xóa cache các ảnh cũ, chạy lại docker-compose để tạo lại theo cấu hình mới

cd docker-imgproxy-img-bibica-net
rm -rf cache/
docker-compose up -d --build --remove-orphans --force-recreate

2023-07-31_13-37-50

Cài đặt xong, đổi DNS, sau ít phút gõ img.bibica.net là sẽ thấy mọi thứ hoạt động bình thường 😀

Add domain vào Wordpress

Dùng plugin CDN Enabler của KeyCDN

2023-07-31_13-41-04

Tạo URL parameters cho ảnh

Sử dụng Code Snippets tạo 1 file php với nội dung:

function jetpack_photon_custom($attr, $attachment, $size) {
   
    if ($size === 'thumbnail') {
       $attr['src'] .= '?w=300';
   } 
   else if ($size === 'medium') {
       $attr['src'] .= '?w=768';
   }
   else if ($size === 'full') {
       $attr['src'] .= '?w=768';
   }	
   else if ($size === 'medium_large') {
       $attr['src'] .= '?w=768';
   }	
   else if ($size === 'large') {
       $attr['src'] .= '?w=768';
   }	
   else if ($size === '1536x1536') {
       $attr['src'] .= '?w=768';
   }
   else if ($size === '2048x2048') {
       $attr['src'] .= '?w=768';
   }

   return $attr;
}

add_filter('wp_get_attachment_image_attributes', 'jetpack_photon_custom', 999 , 3);

function replace_text($text) {
   $text = str_replace('_jpg" alt', '.jpg?w=768" alt', $text);
   $text = str_replace('_jpeg" alt', '.jpeg?w=768" alt', $text);
   $text = str_replace('_png" alt', '.png?w=768" alt', $text);
   $text = str_replace('_gif" alt', '.gif?w=768" alt', $text);
   $text = str_replace('_webp" alt', '.webp?w=768" alt', $text);
   $text = str_replace('_avif" alt', '.avif?w=768" alt', $text);
   $text = str_replace('_bmp" alt', '.bmp?w=768" alt', $text);
   
   return $text;
}
add_filter('the_content', 'replace_text');

Bản này mình set trên theme GeneratePress nên cũng không chắc theme khác có hiệu quả không 😀

Về mặt hoạt động thì nó tương tự Jetpack Photon, ở lần truy cập đầu tiên vào tấm ảnh, sẽ hơi chậm, do imgproxy phải tạo các kích thước theo URL parameters phía sau ảnh ?width=300, ?width=768, sau đó nén lại ảnh, chuyển đổi sang các định dạng khác …. bản này mình chỉ dùng trên định dạng gốc, không đổi sang AVIF và WEBP nên khá nhẹ 😀

2023-07-31_13-55-43

Sau lần truy cập đầu tiên nó sẽ được Nginx cache lại, thời gian là 1 năm 😀 từ lần thứ 2 trở đi, ảnh được load trực tiếp từ Nginx, sẽ không bị chậm nữa

2023-07-31_13-57-56

Ở điều kiện sử dụng thực tế, thì sau 1 thời gian, bạn sẽ thấy status của Nginx và Cloudflare trên tấm ảnh đều sẽ là HIT 😀

Nếu bạn thích nén sang AVIF và WEBP thì có thể mở file docker-compose.yml

  ### Add thêm
#  IMGPROXY_ENFORCE_AVIF: "true"
#  IMGPROXY_ENFORCE_WEBP: "true"
  IMGPROXY_SET_CANONICAL_HEADER: "true"
  IMGPROXY_USE_LAST_MODIFIED: "true"

Ở dòng 97 mình đã để sẵn 2 giá trị IMGPROXY_ENFORCE_WEBP và IMGPROXY_ENFORCE_AVIF, bỏ dấu # đằng trước đi, sau đó chạy recreate lại

cd docker-imgproxy-img-bibica-net
rm -rf cache/
docker-compose up -d --build --remove-orphans --force-recreate

2023-07-31_14-09-18

imgproxy khi chạy thì mình thấy chủ yếu ăn CPU lúc tạo thumbnail, còn lại gần như không đáng kể

Dpr = 2

Sử dụng trên trang bibica.net thì mình duy trì giá trị dpr = 2

{
    default '';

    # only w
    ~^(?<w>[0-9]+):$ '/size:${w}:0:0:0/bg:ffffff/dpr:2/resizing_type:auto';

    # only h
    ~^:(?<h>[0-9]+)$ '/size:0:${h}:0:0/bg:ffffff/dpr:2/resizing_type:auto';

    # both w and h
    ~^(?<w>[0-9]+):(?<h>[0-9]+)$ '/size:${w}:${h}:0:0/bg:ffffff/dpr:2/resizing_type:auto';
}

Giá trị này đơn giản thì nó cứ x2 kích thước bạn set 😀

Dễ hình dung thì vị trí thumbnail, nếu set ?w=300 thì kích thước ảnh thực là w=600, nó sẽ tăng dung lượng ảnh lên khá nhiều, bù lại khi xem bài, chúng ta sẽ thấy các ảnh sắc nét rõ ràng hơn

Tất nhiên bạn cũng có thể hoàn toàn bỏ qua giá trị này, tăng thủ công các giá trị lên w=500 hoặc 400 tùy sở thích 😛

Lý do không dùng AVIF và WEBP trên imgproxy

WEBP và AVIF luôn cho chất lượng và dung lượng nén tốt nhất, mình hoàn toàn đồng tình, tuy thế, ở một số trường hợp hơi đặc thù, cụ thể là các ảnh tại trang bibica.net, câu chuyện nó hơi quái 😀

2023-07-31_20-01-07

Đầu tiên bạn có thể thấy, ở trường hợp 1, file gốc kích thước ~108kb, sau khi nén lại bởi imgproxy hoặc Jetpack Photo, dung lượng còn 33-24kb, nhưng ảnh thumbnail, dù chiều ngang chỉ 300px nhưng lại tới 31-51kb, nặng hơn cả ảnh gốc 😀 tình huống này sẽ khá dễ gặp ở các hình ảnh chủ yếu màu sắc đơn lẻ, chụp các dòng text văn bản

Trường hợp 2, file gốc 1122kb, sau khi nén lại bởi imgproxy hoặc Jetpack Photo, dung lượng còn 324-708 kb, ảnh thumbnail xuống 41 và 93kb

Như bạn thấy, ở trường hợp 2, dùng Jetpack Photo nén về WEBP, không rõ tại sao, dung lượng của nó lại rất lớn, hơn rất nhiều so với thuần túy chỉ nén file png thông thường

-> các hình ảnh trên bibica.net, nén thuần túy ở định dạng png hoặc jpg cho ra dung lượng tối ưu hơn

Đây là lý do quan trọng nhất, khiến mình không bật AVIF hay WEBP khi chạy imgproxy trên bibica.net

Lý do thứ 2 là tạo các file AVIF cực nặng, nó ăn phần cứng kinh người, trong trường hợp bạn nén khoảng 20-50 tấm ảnh 1 lúc, CPU 100% load luôn, sau khi trải nghiệm thì mình đã hiểu tại sao các dịch vụ hiện tại chưa hỗ trợ AVIF, ngoài chuyện nó cần phần cứng mạnh hơn, nó cũng chưa thông dụng ở nhiều trình duyệt, 1 tấm ảnh phải nén sang cả AVIF và WEBP tốn thêm dung lượng 1 cách phi lý 😀 chưa kể Cloudflare cũng chưa hỗ trợ AVIF ở tài khoản miễn phí nữa

png hoặc jpg thì nén khá nhẹ nhàng, thử nén cùng lúc 20-50 tấm ảnh, CPU hú ~20-50%, còn AVIF là 100% 😀

-> Từ 2 lý do trên, mình quyết định tắt AVIF và WEBP trên imgproxy, ở 1 số trường hợp, vài tấm ảnh nó sẽ nặng hơn  5-10kb, còn đa số ở các tấm ảnh khác, nó sẽ giảm được 200-500kb 😀

Một số vấn đề khác

imgproxy có 1 tham số khá tốt là IMGPROXY_ENABLE_CLIENT_HINTS, nếu bật tính năng này lên, có thể lấy các thông tin thiết bị máy khách, như chiều rộng hoặc DPR

2023-08-02_13-40-50

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">

Ở các dịch vụ xử lý ảnh khác như ImageKit mình xem qua, lúc này bạn chỉ cần thêm 1 giá trị, kiểu dpr-auto, w-auto vào link ảnh, trình duyệt sẽ tự biết lấy cái ảnh kích thước tối đa mà thiết bị có thể hiển trị để đưa vào, chúng ta không cần làm các bước thủ công như tạo URL parameters ở trên, mỗi cái xem ở imgproxy thì không thấy họ hướng dẫn cụ thể phần này lắm

Kết luận

Bài này mình thấy tác giả Mai Nhựt Tân code tâm huyết quá, nên cố gắng giới thiệu tới mọi người là chính 😀 vì nếu bạn đang dùng Wordpress, hiệu quả thực tế imgproxy mang lại không đáng kể, thậm chí còn tệ hơn các dịch vụ miễn phí khác như Jetpack Photon

Nếu tác giả có thể viết thêm 1 plugin nhỏ (có thể dựa vào code public của Photon), tích hợp thẳng vào Wordpress, thì mình nghĩ sức ảnh hưởng và hiệu quả mang lại sẽ mạnh hơn

Comment policy: We love comments and appreciate the time that readers spend to share ideas and give feedback.
Notes: However, those deemed to be spam or solely promotional will be deleted.

You can create a Gravatar account, add avatar, then use that email to comment here, your account will have a more beautiful Avatar, easier to recognize with other members.

Please use real emails, you can receive notifications when comments are replied