tag:blogger.com,1999:blog-71888130815997195372024-03-05T11:12:57.569+07:00FAMILUGget your hands dirty, get your feet wet \m/Anonymoushttp://www.blogger.com/profile/08856674198501587219noreply@blogger.comBlogger812125tag:blogger.com,1999:blog-7188813081599719537.post-67977278132865751782021-06-06T11:05:00.002+07:002021-06-06T11:05:09.731+07:00Gửi tặng tác giả 1 cốc cà phê/bia <div><a href="https://grab.onelink.me/2695613898?af_dp=grab%3A%2F%2Fopen%3FscreenType%3DPEERTRANSFER%26method%3DQRCode%26pairingInfo%3DGPTransferc272371d85704492880a8770842e4d9d">Tặng qua Grab bấm vào link này</a><br /></div><div><br /></div><div>Hay quét Momo</div><div><br /></div><div><img height="160" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0kCwltx1TlB8oATM2G2iOX7KVkp8r2E3RNgCwGIyikOXj1Y_bZ_RdLET6g6jrJSBXZW4LTQq0p4vxbfXHT1m2rfgPWreUmgymOTIg-jbqrbjqVfPtUqBqS_37nFj4khKqUXsHPe17UZ8/s761/photo_2021-06-06_10-56-50.jpg" width="160" /></div><div><br /></div><div>Sự ủng hộ của các bạn không làm tác giả trở nên giàu, nhưng sẽ là động lực không nhỏ để tiếp tục viết nhiều bài hay hơn 😚<br /></div>
hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-59132310120536617382021-01-31T16:09:00.010+07:002021-01-31T20:24:58.353+07:00 Chạy OpenBSD trên Laptop ASUS ZenBook 14 2021<h3 style="text-align: left;">## Giới thiệu - Quảng cáo</h3><p>OpenBSD là hệ điều hành lừng danh về chuyện "bảo mật" nhất trái đất.</p><p>Trong suốt hơn 25 năm, chỉ có 2 lỗ hổng lớn xảy ra.</p><div style="text-align: center;"><a href="https://www.openbsd.org/" target="_blank"><img border="0" data-original-height="198" data-original-width="599" height="133" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigxHhhV17L2D0jPppTHGlM2zWKbCPZV0LmftBmwQKQQqpK-SFjPUPRHCSMCcGs_DwOkgRAVk_vcBu88y92WWFPKn5KFsBEaUU6Ms5iZ6ePnQjWoLPJLBO_tCA1K-4SyCWbYYatPDjpIZQ/w400-h133/puffy68.gif" title="OpenBSD https://www.openbsd.org/" width="400" /></a></div><br /><p>Khi các hệ điều hành khác chạy theo tính năng, thì OpenBSD tập trung vào:<a href="https://www.openbsd.org/faq/faq1.html#WhatIs" target="_blank"> security (bảo mật), và code đơn giản, chính xác.</a></p><p>Bảo mật không phải lý do duy nhất, OpenBSD vốn nổi tiếng với việc hệ thống đơn giản (không dùng Systemd), tài liệu (manpage) đầy đủ, dễ đọc, dễ hiểu.</p><p>Để thấy rõ hơn sự kiên quyết của OpenBSD đối với bảo mật ra sao, đây là vài gạch đầu dòng: </p><p>- Sau lỗ hổng bảo mật to nhất nhì thập kỷ của Intel mang tên Spectre, OpenBSD quyết định tắt tính năng Hyper-threading đi https://www.mail-archive.com/source-changes@openbsd.org/msg99141.html<span></span></p><a name='more'></a><p></p><p>Việc này có nghĩa là nếu trên Windows/Linux thấy máy có 8 CPUs thì trên OpenBSD chỉ có 4 CPUs/Core. (thường các máy sẽ tính mỗi CPU nếu có 2 threads thì sẽ hiển thị số core là CPU x 2).</p><p>Người dùng có thể tự bật lên nếu muốn (set giá trị = 1 thay vì 0):</p><p></p><blockquote><p>$ sysctl hw.smt </p><p>hw.smt=0</p></blockquote><p></p><p>- Năm 2021, thế giới qua cơn hoảng loạn COVID-19 thì lỗi bảo mật </p><p>ở câu lệnh sudo khiến mọi hệ điều hành dùng câu lệnh này phải tìm </p><p>cách vá nhanh lỗ hổng. https://blog.qualys.com/vulnerabilities-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit</p><p>OpenBSD từ lâu đã không dùng sudo do lo ngại về sự phức tạp của nó, </p><p>thay vào đó, đã tự viết `doas` với ít chức năng hơn, an toàn hơn.</p><p>- OpenBSD không hỗ trợ các thiết bị bluetooth do sự đa dạng của chuẩn này dẫn tới rắc rối để bảo mật + không có nhân lực (PS: bình tĩnh nếu bạn cần dùng thiết bị Bluetooth, đọc phần Bluetooth bên dưới). </p><p><a href="https://www.familug.org/search/label/BSD" target="_blank">Không phải tới 2021 mình mới nghe tới OpenBSD</a>, cách đây lâu năm đã từng tìm hiểu về các hệ điều hành BSD và từng nghịch DragonFlyBSD rồi. Nhưng một sự cố đã thúc đẩy việc chuyển sang dùng hẳn OpenBSD trên laptop...</p><h3 style="text-align: left;">## Sự cố</h3><p>Làn sóng quay lại "<a href="https://hn.algolia.com/?q=boring" target="_blank">boring tech" trên HackerNews</a> đã đẩy các bài viết về OpenBSD lên frontpage mỗi tuần.</p><p><a href="https://news.ycombinator.com/item?id=24818533">https://news.ycombinator.com/item?id=24818533</a></p><p><a href="https://news.ycombinator.com/item?id=25949784">https://news.ycombinator.com/item?id=25949784</a></p><p>Sau vài ngày tập cài OpenBSD trên máy ảo trên VirtualBox chạy trên Ubuntu, việc cài đặt rất trơn tru và mọi việc có vẻ ổn.</p><p>Nhưng sự "ổn" trên máy ảo này thực ra rất ... "ánh trăng lừa dối".</p><p>Những vấn đề khi thử máy ảo không thấy được:</p><p>- Máy ảo không có card wifi/card sound thật, khi chạy dễ dàng cấu hình mạng và âm thanh, dựa trên driver "giả".</p><p>- Khi cài máy ảo, không phải quan tâm tới việc phân vùng ổ đĩa nếu muốn chạy DualBoot với Ubuntu (cứ auto chọn full disk rồi next next)</p><p>Và sự thật đau đớn chỉ đến khi làm thật. </p><p>Tạo USB để boot OpenBSD rồi chỉ vào để xem "thử" các setting chứ không cài. Nghe rất an toàn cho tới khi nó format luôn ổ cứng.</p><h3 style="text-align: left;">## Luôn luôn backup</h3><p>Dù bạn là Linux SuperUser, thì các câu lệnh trên OpenBSD không hoàn toàn giống với Linux-based OS/tools.</p><p>Việc phân vùng ổ cứng với fdisk (khác fdisk trên GNU/Linux) và "phân vùng con" với disklabel là một chân trời hoàn toàn mới.</p><p>Sự siêu tiện dụng và đơn giản của bộ cài OpenBSD (chỉ cần gõ enter là tự động set mọi thứ tới giá trị default đơn giản) lại vô tình giúp tự động format ổ cứng sau vài câu yes, hay bấm q (phải bấm x để KHÔNG lưu thay đổiphân vùng).</p><p><span style="color: red;"><b>Bài học: luôn luôn backup dữ liệu quan trọng</b></span></p><p> trước khi động gì tới format. </p><h3 style="text-align: left;">## Backup </h3><p>không cần phải phức tạp, mua 1 chiếc ổ cứng di động ngày nay giá chỉ còn vài trăm ngàn,</p><p>một chiếc USB 16GB thậm chí giờ cũng dưới 100.000 VND. Dữ liệu của bạn có đáng giá hơn không?</p><p>Có thể là không, cũng có thể là có nếu đó là file password của ví điện tử chứa hàng chục BTC :haha:</p><p>Bạn không cần phải lo backup đống film tải trên mạng hay code đã lưu trên GitHub làm gì, hãy xác định đâu là dữ liệu mà mình không muốn mất (và có giá trị qui ra tiền mặt?).</p><h3 style="text-align: left;">## Cài đặt</h3><p>Sau phút hoảng loạn vì đã format mất ổ cứng cài Ubuntu đang dùng. Mọi chuyện trở nên đơn giản khi không còn gì để mất. </p>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/BkUSfsfGmfM" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p> </p><p><a href="https://www.youtube.com/watch?v=BkUSfsfGmfM" target="_blank"><b><span style="color: #6aa84f;"><span class="aCOpRe"><span>“<i>Khi đã ở dưới đáy con đường duy nhất chính là đường lên</i>”</span></span><span>. - </span><span><span class="aCOpRe"><span>rapper Phúc Du</span></span></span></span></b></a></p><p>Nhờ đồng bọn ship ngay 1 cái USB boot để cài Ubuntu 20.04, cài lại Ubuntu trên 1/2 ổ cứng.</p><p>Xong xuôi, nhét USB cài OpenBSD và chọn phân vùng còn lại để cài OpenBSD.</p><p>Việc cài đặt, như đã mô tả, sau khi phân vùng ổ cứng với fdisk xong thì cứ thế mà next.</p><p>Sau vài phút copy file xong, OpenBSD sẽ báo cài xong và giờ thì khởi động lại. </p><p>Nhớ chọn tắt SSHD, chọn cài xenodm/X nếu được hỏi.</p><p>Sau khi reboot với màn hình xanh xanh rồi đen đen, OpenBSD hiện ra giao diện login, nhập user password rồi login vào.</p><p> Đây sẽ là cú sốc, khi bạn nhìn thấy giao diện mặc định rất không đẹp, sử dụng chương trình quản lý window tên FVWM.</p><p>Xấu, nhưng vẫn dùng được, chuột phải sẽ hiện ra menu chọn các chương trình, cứ thế mà bật xterm lên.</p><p>Và nhận ra... không có mạng!!!</p><p>Nếu laptop bạn có mạng dây, chỉ cần cắm vào, mọi chuyện sẽ đơn giản.</p><p>Nhưng thời điểm này, những laptop đời mới đều đã bỏ cổng Ethernet đi, chỉ có wifi. </p><p style="text-align: center;"><b><span style="color: red;">Làm sao để cài driver wifi khi không có mạng để tải driver wifi?</span></b></p><p>OpenBSD có thể có driver wifi cho laptop của bạn, nhưng nó sẽ không cài sẵn với lý do bản quyền.</p><p>Vì vậy, bạn phải tự lo chuyện tải và cài driver này. Nếu có mạng dây, chỉ cần gõ `fw_update`, OpenBSD sẽ tự tìm và tự tải/cài các driver cần thiết. Nếu không, hãy kiếm 1 máy tính khác (hoặc boot sang hệ điều hành khác để tải file driver wireless về). Thật ra phần này đã ghi rõ trong phần chuẩn bị cài đặt của OpenBSD tại đây <a href="https://www.openbsd.org/faq/faq4.html#Checkli">https://www.openbsd.org/faq/faq4.html#Checkli</a>st</p><p>Sau khi đã cài xong driver wifi và bắt mạng qua wifi, mọi chuyện còn lại đơn giản là cài đặt/cấu hình phần mềm.</p><p>Cài đặt kết nối wifi:</p><p>Gõ <span style="color: #3d85c6;"><b>ifconfig</b></span> để xem các interface hiện có, ví dụ máy này dùng card của Intel sẽ thấy hiện ra </p><p>iwm0</p><p>Sửa file /etc/hostname.iwm0 với nội dung chứa tên/password wifi</p><p>$ cat /etc/hostname.iwm0 </p><p><span style="color: #3d85c6;">nwid "The Coffee House" wpakey thecoffeehouse</span></p><p><span style="color: #3d85c6;">dhcp</span></p><p>Sau đó chạy với quyền root:</p><p><span style="color: #3d85c6;"># sh -x /etc/netstart</span></p><p>Nếu từng cấu hình wifi bằng câu lệnh trên Ubuntu, bạn sẽ thấy bộ tool của OpenBSD đơn giản,</p><p>hoàn thiệt hơn nhiều lần thay vì phải chạy đi khắp nơi tìm các phần mềm khác nhau, các câu </p><p>lệnh khác nhau đề cài wpa_supplicant, dùng ip command.</p><p>Mọi thông tin về phần cứng đều có trong câu lệnh: dmesg, </p><p><span style="color: #3d85c6;">$ dmesg | grep -i wire</span></p><p><span style="color: #3d85c6;">iwm0 at pci0 dev 20 function 3 "Intel Dual Band Wireless AC 9560" rev 0x30, msix</span></p><p>Một điểm cộng khác nữa, để xem nhiệt độ các sensors, chỉ cần gõ:</p><p><span style="color: #3d85c6;">$ sysctl hw.sensors </span></p><p><span style="color: #3d85c6;">hw.sensors.cpu0.temp0=45.00 degC</span></p><p><span style="color: #3d85c6;">...</span></p><p><span style="color: #3d85c6;">hw.sensors.acpitz0.temp0=51.00 degC (zone temperature)</span></p><p><span style="color: #3d85c6;">hw.sensors.pchtemp0.temp0=45.00 degC</span></p><p><br /></p><p>không cần cài thêm bất cứ gì.</p><p><br /></p><p>Cài một server đơn giản hơn desktop do không cần lo chuyện giao diện,</p><p>cài đặt 1 desktop đơn giản hơn laptop do không cần lo về driver wifi, quản lý pin, sleep khi gập màn hình...</p><h3 style="text-align: left;">## Bluetooth on OpenBSD</h3><p>Các thiết bị bluetooth của mình đều có thêm kết nối qua USB receiver/dongle (1 cục nhỏ nhỏ cắm vào cổng USB), cái này thì hoạt động bình thường, nếu bạn có tai nghe chỉ có cổng Bluetooth thì sẽ không được hỗ trợ.<br />2 thiết bị mình đang dùng đều hỗ trợ 2 kiểu kết nối - qua bluetooth và qua USB:<br />- Bàn phím Logitech ERGO K860</p><p>- Chuột Logitech MX3 Master<br />- Chuột Logitech M212<br /></p><h3 style="text-align: left;">## Cài phần mềm trên OpenBSD</h3><p>Cài phần mềm trên OpenBSD 6.8 thời nay không hề khó hơn cài trên Ubuntu. Chỉ cần chạy với quyền root lệnh:</p><p><span style="color: #3d85c6;">pkg_add -v git tig </span></p><p>để cài git và tig.</p><p>Để tìm package vim, gõ:</p><p><span style="color: #3d85c6;">pkg_info -Q vim<br /></span></p><p>Liệt kê các file cài bởi package vim:</p><p><span style="color: #3d85c6;">pkg_info -L vim </span></p><p>Xem các packages đã cài:</p><p><span style="color: #3d85c6;">pkg_info </span></p><p>Đơn giản, chỉ có vậy.</p><h3 style="text-align: left;">## Cấu hình</h3><p>Sau khi có mạng, gõ syspatch để vá các bản vá bảo mật. <br /></p><p>Với những người dùng không chơi hệ <a href="https://www.familug.org/search/label/i3" target="_blank">tiling-WM như i3</a> hay awesome, không quen việc cấu hình X tools (xterm, ...) thì tốt nhất nên cài XFCE, một Desktop Environment loại nhẹ (So với GNOME3 - mặc định của Ubuntu, KDE).</p><p><span style="color: #3d85c6;">pkg_add -v xfce</span></p><p>sau đó thêm dòng sau vào file ~/.xsession</p><p><span style="color: #3d85c6;">startxfce4</span></p><p>Xong.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM-T54Su5edPseLRwc6haN2_e31jwEAe1d13RkSdcwuo2NU4q70iY0O-AKv6hzBkPrBHy-kjLe_KUczvK18WNIgXJnTcQphRUOIjWs4DO0iz4PPVmOn1806Bww0GLd_KwSVogL7kToNT8/s1920/2021-01-31-143942_1920x1080_scrot.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1080" data-original-width="1920" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgM-T54Su5edPseLRwc6haN2_e31jwEAe1d13RkSdcwuo2NU4q70iY0O-AKv6hzBkPrBHy-kjLe_KUczvK18WNIgXJnTcQphRUOIjWs4DO0iz4PPVmOn1806Bww0GLd_KwSVogL7kToNT8/w400-h225/2021-01-31-143942_1920x1080_scrot.png" width="400" /></a></div><br /> <p></p><p>Reboot lại rồi vào bật terminal lên và cài các phần mềm khác như: firefox, chromium (không có Google Chrome), libreoffice, vlc, keepassxc, calibre...</p><h4 style="text-align: left;">Gõ Tiếng Việt?</h4><p>Hiện tại, mình chưa tìm ra giải pháp gõ tiếng Việt trên OpenBSD, các phần mềm như ibus, fcitx, scim đều không có sẵn package hỗ trợ</p><p>tiếng Việt. Nhưng có thể dùng 1 giải pháp online gõ trên trang https://vntyping.com/</p><p>Tất nhiên nếu lo lắng về mạng hay bảo mật, thì tải cả trang về local mà dùng.</p><h3 style="text-align: left;">## Các vấn đề</h3><p>- Card sound trên máy này hiện không hoạt động, mọi thứ đều trông ổn nhưng không nghe tiếng gì.</p><p>Chuyện này tạm thời cũng chả sao, mình không xem film/ nghe nhạc trên máy này.</p><p>- Máy có vẻ nóng hơn Ubuntu vài độ C</p><p>- Các chương trình chạy chậm hơn Ubuntu, ví dụ theo đo đạc thì Python3 chạy bằng 1/4 tốc độ trên Ubuntu. (cũng có thể do bật chế độ </p><p>tiết kiệm pin trên laptop nên không chạy full khả năng)</p><p><span style="color: #3d85c6;">$ apm </span></p><p><span style="color: #3d85c6;">Battery state: low, 50% remaining, 147 minutes life estimate</span></p><p><span style="color: #3d85c6;">A/C adapter state: not connected</span></p><p><span style="color: #3d85c6;">Performance adjustment mode: auto (400 MHz)</span></p><p>- Các phần mềm không ở phiên bản mới nhất: FireFox đang chạy bản 82.0 khi upstream đã ra 85.0 mấy hôm trước.</p><h3 style="text-align: left;">## Môi trường dev</h3><p>Các phần mềm như vim, emacs, tmux (có sẵn), python3, golang, elixir đều chạy ngon lành và chưa thấy vấn đề gì.</p><h3 style="text-align: left;"><span style="color: #ffa400;"><u>CHÚ Ý: không có Docker <br /></u></span></h3><h3 style="text-align: left;">### Tham khảo</h3><p>- https://www.openbsd.org/faq/index.html</p><p>- https://www.c0ffee.net/blog/openbsd-on-a-laptop/#x11</p><p>- https://paedubucher.ch/articles/2020-09-12-openbsd-on-the-desktop-part-ii.html</p><h2 style="text-align: left;">### Kết luận</h2><p>OpenBSD chạy khá ổn trên laptop ASUS Zenbook 14, hãy thử để biết.</p><p>Nhớ backup trước khi cài!</p><p> </p><p>Hết.</p><p><span style="color: #3d85c6;">$ uname -a</span></p><p><span style="color: #3d85c6;">OpenBSD obsd.fml.vn 6.8 GENERIC.MP#4 amd64</span></p><p>HVN at "học python tại PyMi" <a href="https://pymi.vn">https://pymi.vn</a> and https://www.familug.org </p>hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-37420493484175420142020-11-07T15:05:00.012+07:002021-03-27T17:46:55.041+07:00Kiến trúc Docker, phỏng vấn, best practice<p>Bài này không <a href="https://www.familug.org/search/label/Docker" target="_blank">giới thiệu về docker,</a> "lúc nào rảnh" chắc sẽ có bài đó. </p><p>Bài này nói về kiến trúc của docker, các công nghệ liên quan phía dưới mà docker sử dụng - thường có tác dụng lớn khi 1) chém gió lên mặt 2) phỏng vấn.<br /></p><p>Ngoài ra có kèm theo một số best-practice khi build docker image để có size nhỏ/build nhanh hơn.</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2CVMmAWpkVQr2l_Vju9TPM_LxsklXUHnkWte2Cz6dFDea5F3EY_Ri-YY_HLlEaCN-jkvM6anWRoXZ0NSMCRDy0hKjrn7jVulKMoW67r11ZqajwOd4EmCKq6vZEbkeyiD12XWc8kcCXlk/s640/andy-li-CpsTAUPoScw-unsplash.jpg" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="427" data-original-width="640" height="268" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2CVMmAWpkVQr2l_Vju9TPM_LxsklXUHnkWte2Cz6dFDea5F3EY_Ri-YY_HLlEaCN-jkvM6anWRoXZ0NSMCRDy0hKjrn7jVulKMoW67r11ZqajwOd4EmCKq6vZEbkeyiD12XWc8kcCXlk/w400-h268/andy-li-CpsTAUPoScw-unsplash.jpg" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><span>Photo by <a href="https://unsplash.com/@andasta?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Andy Li</a> on <a href="https://unsplash.com/s/photos/port?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></span></td></tr></tbody></table><br /><h3 style="text-align: left;">Kiến trúc của Docker</h3><p>Docker là một phần mềm viết bằng Golang, theo kiến trúc client-server.</p><p>Nghe hình thức thì vậy, mô hình này nham nhảm khắp nơi khi dùng các database: mysql sẽ có mysqld và mysql client (cli, GUI) ... redis có redis-server và redis-cli...</p><p>Docker có dockerd và docker-cli.<span></span></p><a name='more'></a><p></p><p>docker-cli tương tác với dockerd (docker daemon) qua network socket, khi chạy trên cùng 1 máy, nó dùng UNIX socket để giao tiếp </p><div style="text-align: left;"></div><blockquote><div style="text-align: left;"># ps xau | grep docker[d]<br />root 2640 0.0 4.4 937168 91652 ? Ssl 12:14 0:01 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock<br /></div><div style="text-align: left;"># file /run/containerd/containerd.sock<br />/run/containerd/containerd.sock: socket<br /></div><div style="text-align: left;">root@buster:~# docker run -d alpine sh -c "sleep 100"</div><div style="text-align: left;">c9ebf8e668e4bc354244d5289c480821f6182564dbbb85bde57b216f6889ef66</div><div style="text-align: left;">root@buster:~# strace -e connect docker ps</div><div style="text-align: left;">connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)</div><div style="text-align: left;">connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)</div><div style="text-align: left;">connect(3, {sa_family=AF_UNIX, sun_path="/var/run/docker.sock"}, 23) = 0</div><div style="text-align: left;">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES</div><div style="text-align: left;">c9ebf8e668e4 alpine "sh -c 'sleep 100'" 17 seconds ago Up 16 seconds compassionate_noyce</div><div style="text-align: left;">+++ exited with 0 +++</div></blockquote><div style="text-align: left;"></div><p><br /></p><p>Ở đây dùng strace để thấy docker-cli (câu lệnh docker ps), kết nối vào socket để giao tiếp với dockerd.</p>Mô hình này cũng đồng nghĩa với việc, docker-cli chỉ là bộ điều khiển, ra lệnh, còn dockerd sẽ thực hiện hầu hết các công việc (pull image, build image, run container...). docker-cli có thể kết nối đến các dockerd khác qua network, option -H để chọn địa chỉ IP để connect tới: <div style="text-align: left;"><blockquote># docker -H 127.0.0.1 ps<br />Cannot connect to the Docker daemon at tcp://127.0.0.1:2375. Is the docker daemon running? </blockquote></div><p>Port 2375 là port mặc định của dockerd khi listen trên network (thay vì localhost).</p><h3 style="text-align: left;">Các công nghệ bên dưới docker</h3><p>Docker phụ thuộc vào nhiều tính năng của Linux Kernel. Tính tới thời điểm này (COVID năm thứ 2 - 2020), docker vẫn chỉ chạy trên Linux.</p><p>Khi cài docker trên MacOS/Windows, thực ra bộ cài sẽ cài 1 cái máy ảo linux rồi chạy docker trên đó, vậy nên gặp rất nhiều vấn đề, hiệu năng kém hơn nhiều so với chạy trên Linux-based OS như Ubuntu/Debian/Fedora/RHEL...Câu chuyện cũng dần sang trang mới cho Windows khi có WSL2, khiến chạy docker trên Windows sẽ dần ngon như trên Linux-based OS <a href="https://docs.docker.com/docker-for-windows/wsl/">https://docs.docker.com/docker-for-windows/wsl/</a><br /></p><h4 style="text-align: left;"> Namespaces</h4><p>Namespaces là tính năng của Linux kernel phiên bản mới (3.x trở đi), nó cho phép tạo ra các "namespace" riêng biệt. Ví dụ trước đây, chỉ có thể có 1 PID 1 trên mỗi OS đang chạy, thì giờ có thể có PID 1 ở mỗi namespace khác nhau. </p><ul><li><b>The <code class="highlighter-rouge">pid</code> namespace:</b> Process isolation (PID: Process ID).</li><li><b>The <code class="highlighter-rouge">net</code> namespace:</b> Managing network interfaces (NET:
Networking).</li><li><b>The <code class="highlighter-rouge">ipc</code> namespace:</b> Managing access to IPC
resources (IPC: InterProcess Communication).</li><li><b>The <code class="highlighter-rouge">mnt</code> namespace:</b> Managing filesystem mount points (MNT: Mount).</li><li><b>The <code class="highlighter-rouge">uts</code> namespace:</b> Isolating kernel and version identifiers. (UTS: Unix
Timesharing System).</li></ul><p>mỗi container sẽ dùng namespace của riêng mình, khiến chúng hoàn toàn độc lập và không bị lẫn với các container khác.<br /></p><h4 style="text-align: left;">Control groups (cgroups)</h4><p>Theo man 7 cgroups <br /></p><div style="text-align: left;"></div><blockquote><div style="text-align: left;"> Control cgroups, usually referred to as cgroups, are a Linux kernel feature which allow processes to be organized into hierarchical</div><div style="text-align: left;"> groups whose usage of various types of resources can then be limited and monitored. The kernel's cgroup interface is provided through</div><div style="text-align: left;"> a pseudo-filesystem called cgroupfs. Grouping is implemented in the core cgroup kernel code, while resource tracking and limits are</div><div style="text-align: left;"> implemented in a set of per-resource-type subsystems (memory, CPU, and so on).</div></blockquote><div style="text-align: left;"></div><p><br />cgroup là 1 tính năng của Linux Kernel, mỗi cgroup sẽ là một bộ các process được monitor và gán giới hạn về tài nguyên (CPU/Memory...). </p><p>Nhờ tính năng này, người dùng có thể giới hạn mỗi container dùng bao nhiêu CPU/RAM. </p><h4 style="text-align: left;">Union Filesystem (UnionFS/OverlayFS)</h4><p>Một loại filesystem có khả năng tạo các layer, và merge các layer lại với nhau. Khi layer1 chứa file a và b, layer 2 chứa file b và c, thì khi merge lại, ta sẽ thấy 3 file a b c. Đây là tính năng giúp tạo các docker layer.</p><p> Các filesystem có tính năng của UnionFS: AUFS, btrfs, vfs, and DeviceMapper.</p><p>Lý thuyết chém gió/ phỏng vấn chỉ có vậy, vì thường cũng chỉ thuộc từ khóa đề hù nhau, chứ hỏi thêm nữa nó hỏi lại lại không biết nói gì :))</p><p>Về mặt thuần công nghệ, docker không được đánh giá cao khi mới xuất hiện, do nó chỉ tạo ra 1 sản phẩm dựa trên các công nghệ có sẵn nói trên, hay nói cách khác, 1 sysadmin hoàn toàn có thể làm được điều tương tự qua các câu lệnh mà không cần dùng "docker". Nó cũng không mới, khi trên FreeBSD tồn tại một công nghệ có tên <a href="https://www.freebsd.org/doc/handbook/jails.html" target="_blank">jails</a> từ rất lâu rồi. <br /></p><p>Sự thành công của docker nằm ở chỗ nó chuẩn hóa công việc trên, tạo 1 hệ sinh thái (ecosystem) mà các lập trình viên có thể chia sẻ các image (docker hub), tạo 1 bộ sản phẩm giúp chạy code như nhau trên 3 hệ điều hành phổ biến Ubuntu/Windows/MacOS. Docker giải quyết được 1 vấn đề kinh điển trong ngành phần mềm: "code này chạy trên máy tôi mà" (nhưng mang sang máy khác thì lại không chạy).<br /></p><p>Nghe giống như máy ảo virtualbox/VMWare, nhưng docker nhẹ hơn nhiều do không thực hiện giả lập phần cứng như các hệ thống ảo hóa này.</p><p>Tuy không ấn tượng về mặt công nghệ, nhưng docker lại rất thành công về mặt phổ biến, khiến ngày nay các hệ
thống mới đều mặc định là phải dùng container/kubernetes. Một điều chua
cay đáng chú ý, là công ty Docker, thì lại thất bại về mặt tài chính, do
không tìm ra được cách kiếm NHIỀU tiền dựa trên docker. Ván bài chính
là docker-swarm đã thất bại thảm hại trước Kubernetes khi các ông lớn
cloud lần lượt nhảy vào làm GKE, EKS, AKS. <br /></p><h3 style="text-align: left;">Docker objects</h3><p>Docker objects là tên gọi chung cho các khái niệm trong docker: images, containers, networks,
volumes, plugins,...</p><h4 style="text-align: left;">image</h4><p>Mỗi image là 1 read-only template với các câu lệnh để chạy container. Thông thường các image sẽ base trên các image khác, ví dụ python:ubuntu image được tạo ra bằng cách chạy thêm các câu lệnh cài đặt trên image ubuntu.</p><p>Để tạo 1 image mới, người dùng tạo 1 file tên là Dockerfile rồi viết vào đó các câu lệnh:</p><div style="text-align: left;"><blockquote># cat Dockerfile <br />FROM ubuntu<br />RUN apt-get update && apt-get install -y --no-install-recommends python3 && rm -rf /var/lib/apt</blockquote></div><p>Chạy lệnh sau để build image <br /></p><div style="text-align: left;"><blockquote>root@buster:~# cat Dockerfile | docker build -t mypython -<br />Sending build context to Docker daemon 2.048kB<br />Step 1/2 : FROM ubuntu<br /> ---> d70eaf7277ea<br />Step 2/2 : RUN apt-get update && apt-get install -y --no-install-recommends python3 && rm -rf /var/lib/apt<br /> ---> Using cache<br /> ---> 4b5492751a0a<br />Successfully built 4b5492751a0a<br />Successfully tagged mypython:latest</blockquote></div><p>Nhìn chung, các "step" khi build sẽ tạo ra 1 layer mới, chồng lên các layer trước, và việc tạo layer mới này chỉ thực hiện khi có thay đổi, khiến cho quy trình build image trở nên nhanh/nhẹ hơn nhiều so với build 1 máy ảo truyền thống (VMWare/Virtualbox).<br /></p><h4 style="text-align: left;"> Container </h4><p>Khi mang image đi chạy, ta có 1 container, mọi thứ sinh ra khi chạy container sẽ bị mất đi khi tắt container, trừ khi ta lưu dữ liệu thay đổi lên các "volume" hay database bên ngoài/ hoặc gõ docker commit để tạo image mới từ container đang dùng.</p><p>Các object khác nằm ngoài phạm vi bài viết này, tham khảo tại doc của docker và<a href="https://www.familug.org/search/label/Docker" target="_blank"> các bài viết khác trên trang này</a>. </p><h3 style="text-align: left;">Các best-practice khi build image nhanh/nhẹ</h3><h4 style="text-align: left;">Gộp nhiều câu lệnh làm một<br /></h4><p>Mỗi câu lệnh RUN/COPY/ADD trong Dockerfile sẽ tạo ra 1 layer mới, vậy nên cần chú ý gộp các câu lệnh thành 1 câu (dùng && trong shell) để thu được ít layer nhất.</p><p>Ví dụ trích từ <a href="https://github.com/docker-library/python/blob/ae68254c4e7b25cb9dc131b1bafafb00717cd904/3.9/buster/Dockerfile#L17" target="_blank">file Dockefile chính thức của Python trên Debian buster</a>:</p><p>
</p><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC17"><br /></td></tr></tbody></table><blockquote><blockquote><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC17"><span class="pl-k">RUN</span> apt-get update && apt-get install -y --no-install-recommends \</td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC18"> libbluetooth-dev \</td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC19"> tk-dev \</td>
</tr>
<tr>
</tr></tbody></table><table class="highlight tab-size js-file-line-container" data-paste-markdown-skip="" data-tab-size="8"><tbody><tr><td class="blob-code blob-code-inner js-file-line" id="LC20"> uuid-dev \</td>
</tr>
<tr>
</tr></tbody></table><p> && rm -rf /var/lib/apt/lists/*</p></blockquote></blockquote><h4 style="text-align: left;">Tận dụng tính năng cache để tránh build lại</h4><p>Docker sẽ không build lại layer nếu câu lệnh không thay đổi. Sau khi 1 layer phải build lại do có thay đổi, tất cả các câu lệnh theo sau nó sẽ phải build lại hết.</p><p>Hai câu lệnh COPY và ADD hay thay đổi nhất. Vì vậy, hãy đặt COPY hay ADD ở phần cuối cùng của file. </p><p>COPY/ADD dùng để copy 1 file/thư mục từ máy vào docker image, nó dựa trên checksum của các file để tính xem có thay đổi gì không. Nếu không có file nào thay đổi, 2 lệnh này cũng sẽ không gây build lại layer. <br /></p><p>Khi có 1 file thay đổi hay xuất hiện file mới trong thư mục, chúng sẽ build lại layer.</p><h4 style="text-align: left;">Tách riêng file ít khi thay đổi ra 1 câu lệnh COPY/ADD</h4><p>Trong code Python, khi file requirements.txt thay đổi, ta sẽ muốn chạy lại bước pip install -r requirements.txt để update dependency mới. Nhưng file này không thay đổi hàng ngày, nên tránh việc phải chạy pip install mỗi lần build bằng cách tách riêng file này ra 1 dòng:</p><div style="text-align: left;"></div><blockquote><div style="text-align: left;">COPY requirements.txt /app/requirements.txt</div><div style="text-align: left;">RUN pip install -r requirements.txt</div><div style="text-align: left;">COPY . /app</div></blockquote><h4 style="text-align: left;"><a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#add-or-copy" target="_blank">COPY hay ADD</a></h4><p>COPY</p><p>ADD ngoài tính năng như COPY thì còn có 1 số tính năng khác như: giải nén file nén, tải URL. Vậy nên khi muốn COPY thì dùng COPY, và nếu muốn giải nén/ tải file hãy dùng ADD.</p><h4 style="text-align: left;"><a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#minimize-the-number-of-layers" target="_blank">COPY, ADD và RUN sẽ tạo layer</a></h4><p>nên dùng ít các câu lệnh này để giảm kích thước image.</p><h4 style="text-align: left;"><a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache" target="_blank">Tận dụng cache</a></h4><p>COPY, ADD sẽ dựa trên nội dung (dùng checksum, không tính last-accessed time, last-modifed time) các file/thư mục để xem có thực hiện lại lệnh COPY/ADD hay dùng lại cache của lần build trước.</p><p>Các câu lệnh khác chỉ chạy lại khi có thay đổi, kể cả RUN, chỉ dựa vào string của dòng đó.<br /></p><h4 style="text-align: left;"><a href="https://docs.docker.com/engine/reference/builder/#dockerignore-file">Sử dụng .dockerignore</a> </h4><p>để Docker bỏ qua các file nhất định khi tính checksum<br /></p><p>Ví dụ </p><div style="text-align: left;"><blockquote>**/*.pyc </blockquote></div><p>sẽ bỏ qua các file .pyc trong thư mục hiện tại (và cả các thư mục con).</p><p style="text-align: left;"><br /></p><h4 style="text-align: left;">Dùng Multistage builds </h4><p style="text-align: left;"></p><p>Tính năng mới từ Docker 17, để build với 1 Dockerfile thay vì 1 file để build, 1 file để chạy</p><p><a href="https://docs.docker.com/develop/develop-images/multistage-build/">https://docs.docker.com/develop/develop-images/multistage-build/</a></p><h4 style="text-align: left;">Dùng FROM scratch</h4><p>để tối ưu về dung lượng và bảo mật - đặc biệt hữu ích với static-binary như Golang.</p><p><a href="https://docs.docker.com/develop/develop-images/baseimages/">https://docs.docker.com/develop/develop-images/baseimages/</a><br /></p><h4 style="text-align: left;">Đề phòng với alpine</h4><p>alpine là một Linux distro rất nhỏ, tối ưu về kích thước cho các container, thường chỉ nặng 5-7MB so với Ubuntu 70MB</p><div style="text-align: left;"></div><blockquote><div style="text-align: left;"># docker images<br />REPOSITORY TAG IMAGE ID CREATED SIZE<br />mypython latest 4b5492751a0a 38 minutes ago 106MB<br />ubuntu latest d70eaf7277ea 2 weeks ago 72.9MB<br />alpine latest d6e46aa2470d 2 weeks ago 5.57MB<br /></div><p></p></blockquote><p> Nhưng nhắm mắt dùng alpine sẽ nhiều khi dẫn tới thảm họa.</p><p style="text-align: left;"></p><p>Alpine dùng musl libc thay vì glibc lâu đời của Linux, thư viện này chưa được "thực chiến" nhiều nên nhiều khi gặp các bug đau đầu để xử lý.</p><p>Riêng với Python, dùng Alpine khiến tốc độ cài pip install thường chậm hơn, do các thư viện Python có sẵn wheel cho libc, chỉ việc tải về, thì hầu như không có wheel cho musl, khiến phải compile mỗi lần cài đặt.</p><h3 style="text-align: left;">Tham khảo<br /></h3><p><a href="https://pythonspeed.com/articles/base-image-python-docker-images/">https://pythonspeed.com/articles/base-image-python-docker-images/</a><br /></p><p></p><p></p><p><a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/">https://docs.docker.com/develop/develop-images/dockerfile_best-practices/</a></p><p></p><p></p><p></p><p><a href="https://docs.docker.com/get-started/overview/">https://docs.docker.com/get-started/overview/</a> <br /></p><p></p><p><a href="https://jvns.ca/blog/2019/11/18/how-containers-work--overlayfs/">https://jvns.ca/blog/2019/11/18/how-containers-work--overlayfs/</a></p><p><a href="https://www.redhat.com/sysadmin/cgroups-part-one">https://www.redhat.com/sysadmin/cgroups-part-one</a> <br /></p><p>HVN at "học python tại PyMi" <a href="https://pymi.vn">https://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a> <br /></p>hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com1tag:blogger.com,1999:blog-7188813081599719537.post-31504064689195753032020-11-06T22:32:00.004+07:002020-11-07T12:30:49.998+07:00bash, shopt và =, **<p>bash là UNIX shell được dùng nhiều nhất hành tinh này.
bash có ở khắp mọi hệ điều hành Linux based, hay kể cả OSX/MacOS cho <a href="https://support.apple.com/en-us/HT208050" target="_blank">tới tháng 10 năm 2019 (khi Apple quyết định
thay zsh làm shell mặc định trên MacOS)</a>. </p><p>Sự thay đổi này của Apple khiến cho bạn nếu vẫn "chày cối" dùng bash sẽ gặp phải một số bất lợi nhất định (tất nhiên là khắc phục được).
Trên MacOS, phiên bản của bash có sẵn rất cũ, vẫn là bash 3.X. Trong khi Ubuntu 18.04 đã dùng 4.4, còn Ubuntu 20.04 thì dùng hẳn bash 5.0. </p><p> <table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdjfANwxeTsNiiNBl4uAbgYpjx8AV0fe_1Sbs1Kdeaa-cDOCD9kOBzeBhua2ZLcij5l8Vm8pSnEP2q2BeZbya0SLtCch6q4w_wE9JvOqKG-w9eNg-Scr0_gfErYSPj-MDcTXyanICqtoQ/s640/clement-chai-24lSJNnkGhA-unsplash.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="427" data-original-width="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdjfANwxeTsNiiNBl4uAbgYpjx8AV0fe_1Sbs1Kdeaa-cDOCD9kOBzeBhua2ZLcij5l8Vm8pSnEP2q2BeZbya0SLtCch6q4w_wE9JvOqKG-w9eNg-Scr0_gfErYSPj-MDcTXyanICqtoQ/s320/clement-chai-24lSJNnkGhA-unsplash.jpg" width="320" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><span>Photo by <a href="https://unsplash.com/@clementchai?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Clement Chai</a> on <a href="https://unsplash.com/s/photos/viet-nam?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></span></td></tr></tbody></table><br /></p><p>Chuyện này hoàn toàn không phải vấn đề nếu người dùng macOS chủ động cài phiên bản bash mới nhất bằng <a href="https://brew.sh/" target="_blank">brew</a>.
Cho đến một ngày có người hỏi: câu lệnh <b>ls</b> nào trên bash để liệt kê ra tất cả file .pyc trong tất cả các thư mục con hiện tại?<br />
Việc này hoàn toàn làm được với `<span style="color: #3d85c6;"><b>find . -name '*.pyc' -type f</b></span>`, nhưng với ls thì??? <span></span></p><a name='more'></a><p></p><h3 style="text-align: left;">glob là gì </h3><p>Trên các shell, dấu * được gọi là "glob", glob thay cho "bất cứ string nào", ví dụ muốn liệt kê các file .py trong thư mục hiện tại, gõ:
</p><blockquote>ls *.py
</blockquote>
Kết quả sẽ in ra mọi file có đuôi .py trong thư mục.
<blockquote>ls /etc/*.conf </blockquote><p>
sẽ in ra tất cả các tên file có đuôi .conf trong thư mục /etc/ </p><h3 style="text-align: left;">globstar là gì? recursive glob là gì?</h3><p>globstar là tính năng chỉ có từ bản bash 4.0 trở đi, vậy nên nếu bạn thuộc hội "sysadmin" đã có tuổi, lại không theo sát đọc <a href="https://tldp.org/LDP/abs/html/bashver4.html" target="_blank">các tính năng khi bash ra bản 4</a>, thì hoàn toàn không biết tính năng này.
</p><blockquote>> The new ** globbing operator matches filenames and directories recursively.</blockquote>
<blockquote> globstar
If set, the pattern ** used in a pathname expansion context will match all files and zero or more directories
and subdirectories. If the pattern is followed by a /, only directories and subdirectories match.</blockquote><p>
sau khi bật tính năng globstar bằng lệnh <span style="color: #3d85c6;"><b>shopt -s globstar</b></span>
câu lệnh sau sẽ liệt kê tất cả các file có tên .py trong thư mục hiện tại, bao gồm cả thư mục con:</p><div style="text-align: left;"><p style="text-align: left;"></p><blockquote>vagrant@buster:~$ mkdir -p a/b/c/d/<br />vagrant@buster:~$ touch a/b/c/d/hocpython.py<br />vagrant@buster:~$ cd a<br />vagrant@buster:~/a$ ls **/*.py<br />ls: cannot access '**/*.py': No such file or directory<br />vagrant@buster:~/a$ shopt -s globstar<br />vagrant@buster:~/a$ ls **/*.py<br />b/c/d/hocpython.py<br />vagrant@buster:~/a$ echo $BASH_VERSION<br />5.0.3(1)-release</blockquote><p></p></div>
zsh 5.4.2 mặc định hỗ trợ tính năng này, không cần bật gì, dưới cái tên "Recursive Globbing" (trong man 1 zshexpn)
<h3 style="text-align: left;">shopt là gì? </h3><p></p><p>shopt là một câu lệnh built-in của bash, dùng để bật tắt các tính năng ít được dùng tới </p><p></p><blockquote><p>$ type shopt<br />shopt is a shell builtin</p><p>$ man 1 bash</p><p>... <br /></p><p>shopt [-pqsu] [-o] [optname ...]<br /> Toggle the values of settings controlling optional shell behavior.</p></blockquote><p><br /></p><p>Liệt kê các tính năng đang bật:</p><p></p><blockquote>$ shopt -p | grep -- -s<br />shopt -s checkwinsize<br />shopt -s cmdhist<br />shopt -s complete_fullquote<br />shopt -s expand_aliases<br />shopt -s extglob<br />shopt -s extquote<br />shopt -s force_fignore<br />shopt -s globasciiranges<br />shopt -s globstar<br />shopt -s histappend<br />shopt -s interactive_comments<br />shopt -s login_shell<br />shopt -s progcomp<br />shopt -s promptvars<br />shopt -s sourcepath</blockquote><br /><p></p><p>Các tính năng đang tắt:</p><p></p><blockquote><p>$ shopt -p | grep -- -u | head<br />shopt -u autocd<br />shopt -u assoc_expand_once<br />shopt -u cdable_vars<br />shopt -u cdspell<br />shopt -u checkhash<br />shopt -u checkjobs<br />shopt -u compat31<br /></p><p>...<br /></p><p> </p></blockquote><p></p><h3 style="text-align: left;">Bonus: so sánh string = trong test trên shell </h3><p>Có lẽ hơi ngại khi thừa nhận rằng sau 10 năm dùng bash, mình luôn nghĩ so sánh string khi test dùng == như Python. Tất nhiên code vẫn chạy, do bash hỗ trợ 2 cú pháp: = và ==.</p><p></p><blockquote> string1 == string2<br /> string1 = string2<br /> True if the strings are equal. = should be used with the test command for POSIX conformance. When used with the [[<br /> command, this performs pattern matching as described above (Compound Commands).</blockquote>Ví dụ:<p></p><p></p><blockquote><p>x="PyMI.vn"</p><p>if [ "$x" == "PyMI.vn" ]; then</p><p> echo Python</p><p>fi</p></blockquote><p>Thì cùng có thể viết </p><p></p><blockquote><p>x="PyMI.vn" <br /></p><p>if [ "$x" = "PyMI.vn" ]; then</p><p> echo Python</p>fi</blockquote><p>Thậm chí đoạn code sau còn chạy trên nhiều shell khác nữa do nó "chuẩn POSIX".<br /></p><p>Hết.</p>HVN at "học python tại PyMi" <a href="https://pymi.vn">https://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a> hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com1tag:blogger.com,1999:blog-7188813081599719537.post-74508687796950839312020-11-01T12:46:00.000+07:002020-11-01T12:46:09.282+07:00Không cần jq nếu đã có python/ruby <p>jq trở thành công cụ "làm tất cả" với các sysadmin. Trong khi jq được dùng để "query JSON" - truy cập dữ liệu trong 1 đoạn JSON thì nó cũng được dùng để kiểm tra syntax của 1 đoạn JSON.</p><p>JQ là gì</p><p></p><blockquote><p>$ whatis jq</p><p>jq (1) - Command-line JSON processor<br /> </p></blockquote><p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAq5ACIxUl2mtl9JwCr9XemPua6xEi4dMdUAfc4xoquvujvM3Foyn-tRuxsrvORtEK39YgqyJC1mBoyUkoZCyHUUunPTeWt27ChZzls3FKW2a5oXpXQJVKdsyaZPQon916AO2q414AbkY/s2048/thanh-soledas-XGuZ4HlC5qU-unsplash.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1365" data-original-width="2048" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAq5ACIxUl2mtl9JwCr9XemPua6xEi4dMdUAfc4xoquvujvM3Foyn-tRuxsrvORtEK39YgqyJC1mBoyUkoZCyHUUunPTeWt27ChZzls3FKW2a5oXpXQJVKdsyaZPQon916AO2q414AbkY/w400-h266/thanh-soledas-XGuZ4HlC5qU-unsplash.jpg" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><span>Photo by <a href="https://unsplash.com/@thanhsoledas?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Thanh Soledas</a> on <a href="https://unsplash.com/s/photos/viet-nam?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></span></td></tr></tbody></table> </p><p>Nhưng nếu máy đã có python/ruby, hay các ngôn ngữ trang bị sẵn stdlib JSON, sẽ không cần phải cài gì thêm cả:</p><p>File bad.json ví dụ, thử dùng mắt thường tìm xem nó sai ở đâu:</p><blockquote><p>{<br /> "name": "@exercism/typescript",<br /> "description": "Exercism exercises in Typescript.",<br /> "private": true,<br /> "repository": {<br /> "type": "git",<br /> "url": "https://github.com/exercism/typescript"<br /> },<br /> "devDependencies": {<br /> },<br />}<br /></p><span><a name='more'></a></span></blockquote><p>Cả 3 công cụ sau đều trả về exit code khác 0 (tức không thành công, hay "bị lỗi")<br /></p><blockquote>$ cat bad.json | jq ; echo $?<br />parse error: Expected another key-value pair at line 11, column 1<br />4</blockquote><blockquote>$ cat bad.json | python3 -m json.tool ; echo $?<br />Expecting property name enclosed in double quotes: line 11 column 1 (char 231)<br />1</blockquote><blockquote>$ cat bad.json | ruby -rjson -e 'JSON.load(STDIN)' ; echo $?<br />Traceback (most recent call last):<br /> 3: from -e:1:in `<main>'<br /> 2: from /home/vagrant/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/json/common.rb:335:in `load'<br /> 1: from /home/vagrant/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/json/common.rb:156:in `parse'<br />/home/vagrant/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/json/common.rb:156:in `parse': 783: unexpected token at '{ (JSON::ParserError)<br /> "name": "@exercism/typescript",<br /> "description": "Exercism exercises in Typescript.",<br /> "private": true,<br /> "repository": {<br /> "type": "git",<br /> "url": "https://github.com/exercism/typescript"<br /> },<br /> "devDependencies": {<br /> },<br />}<br />'<br />1</blockquote><p><br />Khi có trong tay những ngôn ngữ lập trình hạng xịn, trang bị tận răng như Python hay Ruby, bạn có thể làm rất rất nhiều thứ, trong 1 2 dòng, mà không cần cài thêm gì. Kiểm tra syntax JSON cũng không phải ngoại lệ.</p><p><br /></p><p>Happy coding.</p><p>Tham khảo </p><p><a href="https://pp.pymi.vn/article/pycli/">https://pp.pymi.vn/article/pycli/</a></p><p>Hết.</p><p>HVN at "học python tại PyMi" <a href="https://pymi.vn">https://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a> <span class="post-comment-link"></span></p>hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-47681077885261836752020-10-31T11:57:00.002+07:002020-10-31T12:00:20.690+07:00grep không hỗ trợ \d và 3 mode regex trong grep <p><i><b>grep</b></i> là công cụ thiết yếu của sysadmin, tác dụng cơ bản là tìm một mẫu string trong 1 file text. </p><p>Cái tên grep bắt nguồn từ câu lệnh g/re/p của chương trình <i><b>ed</b></i></p><p>> Its <b>name</b> comes from the ed command <b>g/re/p</b> (globally search for a regular expression and print matching lines) </p><p>Biết thêm một chút regex sẽ tăng thêm sức mạnh. Ví dụ in ra shell của các user trên máy:</p><p></p><blockquote><p>$ grep -o '/bin/.*sh' /etc/passwd</p><p>/bin/bash<br />/bin/sh</p></blockquote><p></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji6UHw90iHZbtL0H-vx6bj4CgZDcUx6d7A8Y-OoYmBsogwjqA4CcKO3oClwYBAy44b1ll0qGVIOc-GbHiAGjSt92EqlJpK52vLqYY5gixisdH48EHmJEX4V-LRT3QRbavbojc0Qnur1mo/s640/percy-pham-8502DXwY-7U-unsplash.jpg" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="427" data-original-width="640" height="268" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji6UHw90iHZbtL0H-vx6bj4CgZDcUx6d7A8Y-OoYmBsogwjqA4CcKO3oClwYBAy44b1ll0qGVIOc-GbHiAGjSt92EqlJpK52vLqYY5gixisdH48EHmJEX4V-LRT3QRbavbojc0Qnur1mo/w400-h268/percy-pham-8502DXwY-7U-unsplash.jpg" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><span>Photo by <a href="https://unsplash.com/@percythaipham?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Percy Pham</a> on <a href="https://unsplash.com/s/photos/viet-nam?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></span></td></tr></tbody></table><br /><p>Một "pattern" phổ biến là tìm số, nếu đã từng lập trình dùng regex, thì \d không còn lạ gì để tìm số - <a href="https://pp.pymi.vn/article/pycli/" target="_blank">code Python ngay trên CLI</a>:<br /></p><p></p><blockquote>$ echo '123ab45' | python3 -c '<br />> import re, sys<br />> print(re.findall("\d+", sys.stdin.read()))'<br />['123', '45']</blockquote><p></p><p>Nhưng thật bất ngờ, grep trên Ubuntu 18.04 không hỗ trợ pattern này:</p><p></p><blockquote><p>$ echo '123ab45' | grep -o '\d+'</p><p># không in ra gì <span></span></p><a name='more'></a><p></p></blockquote><p></p><p>Và nếu như bạn "có vẻ" là dân chuyên, "dùng egrep đi", thì kết quả cũng vẫn vậy:<br /></p><p></p><blockquote><p>$ echo '123ab45' | grep -oE '\d+'</p><p># nguyễn y vân - vẫn y nguyên </p></blockquote><p><br /></p><p>Có thể bạn đã biết, trên Linux thường có thêm 2 câu lệnh fgrep và egrep, nhưng thực ra chúng chỉ là grep -F và grep -E <br /></p><p></p><blockquote><p>$ cat `which fgrep`<br />#!/bin/sh<br />exec grep -F "$@"</p><p>$ cat `which egrep`<br />#!/bin/sh<br />exec grep -E "$@"</p></blockquote><p>Nếu thực sự muốn thấy kết quả, phải dùng Perl Regex -P:</p><p></p><blockquote>$ echo '123ab45' | grep -oP '\d+'<br />123<br />45</blockquote><p></p><h3 style="text-align: left;">grep không chỉ là một chương trình</h3><p>grep là tên của 1 loại chương trình, nó có nhiều "bản" khác nhau và các bản sẽ có vài phần giống và nhiều phần khác nhau.</p><p>Gõ grep --version để xem mình đang dùng gì:</p><p></p><blockquote>$ grep --version<br />grep (GNU grep) 3.1<br />Copyright (C) 2017 Free Software Foundation, Inc.</blockquote><p></p><p>Đây là <a href="https://www.gnu.org/software/grep/manual/grep.html" target="_blank">GNU grep</a>, mặc định trên các hệ điều hành Linux based.</p><p>Tren OSX/MacOS hay trên BSD, sẽ sử dụng bản grep khác </p><p></p><blockquote>obsd# uname -a; grep --version<br />OpenBSD obsd.hvnbsd.com 6.8 GENERIC#97 amd64<br />grep version 0.9<br /><br /></blockquote><p></p><h3 style="text-align: left;">Ba loại Regex mà grep hỗ trợ</h3><p>khi gõ grep 'pattern' files, grep sẽ dùng <span style="color: red;"><b>Basic Regular Expression (BRE)</b></span><br /></p><p></p><blockquote>$ grep '/bin/.*sh$' /etc/passwd</blockquote><p></p><p>Khi gõ <span style="color: red;"><b>egrep hay grep -E 'pattern' files, grep sẽ dùng Extended Regular Expression (ERE)</b></span>. BRE và ERE trong GNU grep về tính năng nhìn chung là giống nhau. Điểm khác biệt duy nhất là trong BRE, các ký tự đặc biệt phải escape với backslash \, ví dụ: () -> \(\)<br /></p><p> grep understands three different versions of regular<br /> expression syntax: “basic” (BRE), “extended” (ERE) and<br /> “perl” (PCRE). In GNU grep there is no difference in<br /> available functionality between basic and extended<br /> syntaxes. In other implementations, basic regular<br /> expressions are less powerful. <br /></p><p> Perl-compatible regular expressions give additional<br /> functionality, and are documented in pcresyntax(3) and<br /> pcrepattern(3), but work only if PCRE is available in the<br /> system.<br /></p><p>Perl regex vốn nổi tiếng là nhiều tính năng, có thể dùng với option -P nếu trên máy có cài lib PCRE:</p><p></p><blockquote>$ ldd `which grep` | grep pcre<br /> libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f92565f4000)</blockquote><p></p><h3 style="text-align: left;">\d là cái gì? </h3><p>Theo <span style="color: #3d85c6;"><b>man 1 grep</b></span>, bachslash (\) theo sau nó là 1 ký tự, thường là cách viết tắt (shortcut) cho các pattern regex thường dùng nào đó.Xem </p><p>bảng đầy đủ tại <a href="https://en.wikipedia.org/wiki/Regular_expression#Character_classes">https://en.wikipedia.org/wiki/Regular_expression#Character_classes</a></p><p>trong grep, chỉ có \b \B \< \> \w - KHÔNG CÓ \d</p><p><br /> The Backslash Character and Special Expressions<br /> The symbols \< and \> respectively match the empty string<br /> at the beginning and end of a word. The symbol \b matches<br /> the empty string at the edge of a word, and \B matches the<br /> empty string provided it's not at the edge of a word. The<br /> symbol \w is a synonym for [_[:alnum:]] and \W is a<br /> synonym for [^_[:alnum:]].<br /> </p><p>\d trong các chương trình khác có nghĩa là sẽ match 1 chữ số, trên grep phải viết rõ character class ra [0-9]<br /></p><p></p><blockquote>$ echo '123ab45' | grep -o '[0-9]\+' <br />123<br />45</blockquote><br /><p></p><p>Hay ERE không cần escape meta character +:</p><p></p><blockquote>$ echo '123ab45' | grep -oE '[0-9]+' <br />123<br />45</blockquote><br /><p></p><p>Các character class định nghĩa sẵn theo <span style="color: #3d85c6;"><b>man 7 regex</b></span>:<br /></p><p> Within a bracket expression, the name of a character class enclosed in "[:" and ":]" stands for the list of all characters<br /> belonging to that class. Standard character class names are:<br /><br /> alnum digit punct<br /> alpha graph space<br /> blank lower upper<br /> cntrl print xdigit<br /><br /></p><p>Regex là một vấn đề phức tạp, biết một chút cũng được nhưng nhớ nắm rõ các phiên bản, và tránh lạm dụng:</p><p><b></b></p><blockquote><b>
Some people, when confronted with <span class="nobr">a problem,</span> think
<br />“I know, I'll use regular expressions.”
Now they have two problems.</b></blockquote><p></p><p>Tham khảo</p><p><a href="https://docs.python.org/3/library/re.html">https://docs.python.org/3/library/re.html</a></p><p><a href="https://blog.codinghorror.com/regular-expressions-now-you-have-two-problems/">https://blog.codinghorror.com/regular-expressions-now-you-have-two-problems/</a> <br /></p><p>Hết.</p><p>HVN at "học python tại PyMi" <a href="https://pymi.vn">https://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a> <br /></p>hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-41825870886495166162020-05-25T20:01:00.000+07:002020-05-25T20:04:31.334+07:00[CLI/Python] find prune path - cắt giảm bài toán tìm kiếm 10 lần `find` là câu lệnh dùng để tìm file dựa theo tên, vốn không kém tiếng "khó dùng", nhưng vẫn được dùng, do nó được cài sẵn ở mọi hệ điều hành Unixoid (Unix/Linux).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNsKQLtZL-TgApslIlcYc7U_W2XIlA7Icy8rsKkdKKH-1uWQauqpwjtBM7vnxDSWzImzgLCk8OwLs_y-IGdxJ3aQyqTb0ptETvWbJ_pL6WRsRHbM83GryJMAe8dU3n7eGQSFAhTCeVF4Q/s1600/splash.jpeg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1350" data-original-width="1080" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNsKQLtZL-TgApslIlcYc7U_W2XIlA7Icy8rsKkdKKH-1uWQauqpwjtBM7vnxDSWzImzgLCk8OwLs_y-IGdxJ3aQyqTb0ptETvWbJ_pL6WRsRHbM83GryJMAe8dU3n7eGQSFAhTCeVF4Q/s400/splash.jpeg" width="320" /></a></div>
<br />
<h4>
find - huyền thoại</h4>
Một lệnh find đơn giản để tìm các file .py trong thư mục /usr<br />
<br />
<blockquote class="tr_bq">
$ time -p find /usr/ -name '*.py' | wc -l<br />
11013<br />
real 0,39<br />
user 0,15<br />
sys 0,24</blockquote>
<br />
11013 file này gồm file từ nhiều thư mục khác nhau.<br />
<br />
<blockquote class="tr_bq">
$ time -p find /usr/ -name '*.py' | cut -f-3 -d '/' | sort | uniq -c | sort -nr | head<br />
9286 /usr/lib<br />
1644 /usr/share<br />
78 /usr/src<br />
5 /usr/local<br />
real 0,39<br />
user 0,17<br />
sys 0,24<br />
<a name='more'></a><br /></blockquote>
Cấu trúc thư mục: <br />
<blockquote>
$ time -p find /usr/ | cut -f-3 -d '/' | sort | uniq -c | sort -nr | head<br />
150407 /usr/share<br />
59798 /usr/src<br />
44392 /usr/lib<br />
29949 /usr/local<br />
4168 /usr/include<br />
2355 /usr/bin<br />
260 /usr/sbin<br />
260 /usr/lib32<br />
9 /usr/libexec<br />
7 /usr/games<br />
real 0,42<br />
user 0,29<br />
sys 0,26</blockquote>
<br />
Giả sử không muốn kết quả chứa các file trong /usr/lib, cách đơn giản nhất là thêm điều kiện lọc:<br />
<blockquote class="tr_bq">
$ time -p find /usr/ -name '*.py' -not -path '/usr/lib/*' | wc -l<br />
1727<br />
real 0,41<br />
user 0,19<br />
sys 0,21</blockquote>
Cách làm này đơn giản, hiển nhiên, nhưng có 1 vấn đề: chậm.<br />
Nó luôn phải duyệt qua 200k files trong /usr, và chỉ in ra các file thỏa mãn điều kiên.<br />
<blockquote class="tr_bq">
$ time -p find /usr/ | wc -l<br />
291606<br />
real 0,35<br />
user 0,10<br />
sys 0,26</blockquote>
Tất nhiên trong bài toán ví dụ này, 0.35s dù có chậm vẫn OK, nhưng nếu với input đủ lớn hay ổ cứng đủ chậm khiến việc find này mất 17s , liệu có cách nhanh hơn?<br />
<br />
Có một cách làm khác cũng vẫn dùng find, nhưng "khó" hơn một chút: dùng option -prune.<br />
<blockquote class="tr_bq">
$ man find | grep -A1 -- ' -prune' <br />
-prune True; if the file is a directory, do not descend into it. If -depth is given, false; no effect. Because -delete implies -depth, you cannot<br />
usefully use -prune and -delete together.</blockquote>
<br />
prune giúp bỏ qua các thư mục.<br />
<br />
<blockquote class="tr_bq">
$ time -p find /usr/ -name src -type d -prune -o -path /usr/share -prune -o -name '*.py' -print | wc -l<br />
9289<br />
real 0,14<br />
user 0,06<br />
sys 0,08</blockquote>
<br />
Thời gian chạy chỉ còn 1 nửa sau khi bỏ qua thư mục lớn nhất /usr/share và các thư mục tên src.<br />
<br />
Thêm -prune sau mỗi option lọc path muốn bỏ qua, -o là `or` để thêm nhiều điều kiện, cuối cùng luôn phải có<span style="color: red;"><b> -o điều_kiện_chọn_file -print</b></span>.<br />
<br />
<h4>
fd - a new find</h4>
fd-find là câu lệnh mới xuất hiện, viết bằng rust, và nhanh hơn find nhiều lần,<br />
có sẵn những "default" hợp lý như tự động bỏ qua các file ẩn hay file trong .gitignore, ... thích hợp khi dùng trong các thư mục code hàng ngày <a href="https://github.com/sharkdp/fd">https://github.com/sharkdp/fd</a><br />
<br />
<h4>
Python os.walk</h4>
Function để duyệt qua các thư mục trong 1 thư mục "<span style="color: #3d85c6;"><b>os.walk</b></span>" hỗ trợ việc prune path bằng cách xóa các thư mục không muốn từ dirs, trích doc của os.walk:<br />
<blockquote class="tr_bq">
<br />
import os<br />
from os.path import join, getsize<br />
for root, dirs, files in os.walk('python/Lib/email'):<br />
print root, "consumes",<br />
print sum([getsize(join(root, name)) for name in files]),<br />
print "bytes in", len(files), "non-directory files"<br />
if 'CVS' in dirs:<br />
dirs.remove('CVS') # don't visit CVS directories</blockquote>
Hết.<br />
<br />
HVN at https://pymi.vn and https://www.familug.org<br />
<br />
Tham khảo:<br />
- <a href="https://stackoverflow.com/a/16595367/807703">https://stackoverflow.com/a/16595367/807703</a>hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-42399992050116969572020-04-14T22:15:00.000+07:002020-04-14T22:15:32.548+07:00Kỹ năng phỏng vấn <div class="entry-content">
Phỏng vấn là hai từ mang yếu tố quyết định, nhưng ít khi được nhắc đến.
Nó thường bị xem như sự may mắn, hay do "giỏi", hay quan hệ tốt ...
Bài viết này giải đáp các thắc mắc thầm kín khi đi phỏng vấn xin việc, đặc biệt
dành cho những người kiếm công việc lập trình đầu tiên.<br />
<h2>
Phỏng vấn là gì?</h2>
Khi muốn đi làm, một lập trình viên sẽ liên hệ với một công ty để "xin việc" nhằm
có một công việc, được trả lương tại công ty đó, ở đây ví dụ là PAMA corp.<br />
PAMA corp sẽ cử một hoặc một nhóm người thực hiện việc "kiểm tra đầu vào". Sau
quá trình kiểm tra, nếu phía PAMA corp đồng ý, phía lập trình viên đồng ý,
sẽ ký một hợp đồng lao động, rồi lập trình viên đi làm, và được trả lương.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9GMqhGBrym8aPDUpnJu1SydRP6p_NFvxddGGCZTCjVyYcQcK3uUBGoT-PcPtnZgXgC9fHMeqViyoZSbT5iYdyN25pNiY0d7laDGTcGUX5X-6XfkpXZ_STxd2djKJqTqHt4ljvT77OTHE/s1600/meme.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="435" data-original-width="849" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9GMqhGBrym8aPDUpnJu1SydRP6p_NFvxddGGCZTCjVyYcQcK3uUBGoT-PcPtnZgXgC9fHMeqViyoZSbT5iYdyN25pNiY0d7laDGTcGUX5X-6XfkpXZ_STxd2djKJqTqHt4ljvT77OTHE/s400/meme.jpg" width="400" /></a></div>
<br />
<h2>
Nộp "đơn xin việc" thế nào?</h2>
Thông thường, tức không phải các trường hợp ngoại lệ như bị "săn" đón đi làm,
hay có quan hệ được "giới thiệu" vào làm, thì:<br />
<ul>
<li>lập trình viên sẽ lên các trang tuyển dụng/tìm việc (<a href="https://jobs.pymi.vn/">như PyJobs</a>)/trang chủ của công ty cụ thể để tìm công việc mong muốn,</li>
<li>hoặc các "head hunter" chuyên đi săn ứng viên hay HR (human resource - nhân
sự) của các công ty chủ động liên hệ, mời (or gạ) nộp hồ sơ phỏng vấn. Chú ý
đây là một nghề hái ra tiền, nên đừng shock nếu được mời chào đon đả.</li>
</ul>
<h2>
Quy trình phỏng vấn ra sao?</h2>
Thông thường sẽ có:<br />
<a name='more'></a><br />
<ul>
<li>HH/HR gọi điện, thống nhất về Job description (JD - yêu cầu công việc), có
thể có cả mức lương thưởng/chính sách. Nếu không có, hãy hỏi kỹ, cụ thể để
khỏi mất thời gian. Bạn sẽ không muốn tốn thời gian 4 tuần đi qua 8 vòng phỏng
vấn để biết mức thu nhập tối đa công ty sẽ trả cho vị trí đó là 1.500 USD, trong
khi thu nhập hiện tại của bạn đang là 3000 USD.
HR sẽ chốt quy trình phỏng vấn (mấy vòng), thời gian phỏng vấn vòng 1.</li>
<li>Vòng 0: các công ty có thể có bài test online, thường là code 2-3 bài thuật
toán trong vòng 1 tiếng trên 1 trang code online như <a class="vglnk" href="http://hackerank.com/" rel="nofollow"><span>hackerank</span><span>.</span><span>com</span></a>.
Hoặc cũng có thể cho 1 bài "test" về nhà làm 1 sản phẩm gì đó rồi nộp sau 1
tuần.</li>
<li>Nếu có vòng 0, và nếu đạt yêu cầu, HR sẽ email/ gọi điện hẹn phỏng vấn vòng 1.</li>
<li>Vòng 1, có thể là cả vòng 2, hay thậm chí vòng N, là việc gặp và trả lời các
câu hỏi do đại diện phía công ty đưa ra.</li>
<li>Nếu N vòng này thành công, sẽ tới vòng cuối cùng: "deal" lương. Dựa vào kết
quả N vòng trước mà công ty quyết định có tuyển lập trinh viên không, và
trả thù lao bao nhiêu.</li>
<li>Hai bên nếu đồng ý sẽ ký giấy tờ, và hẹn ngày đi làm. Với người đang đi làm
ở công ty khác, sẽ thỏa thuận sau thời gian X ngày (thường là 30-45 ngày)
sẽ đi làm do đó là thời gian cần thiết để xin nghỉ, chấm dứt hợp đồng với
công ty cũ.</li>
</ul>
<h2>
Đơn xin việc - Resume - CV</h2>
Thường là 1 file PDF (để đọc được trên mọi hệ điều hành),
bằng tiếng Anh (để tỏ ra chuyên nghiệp), có link GitHub đến các project cá
nhân thay vì đính kèm file RAR.<br />
Xem mẫu CV học viên mới tốt nghiệp <a href="https://pymi.vn/">pymivn</a>
tại <a href="https://bit.ly/pymicv">đây</a>.<br />
Tránh các mẫu CV có "thanh năng lượng" đánh giá 3 sao 5 sao. Really? bạn
có chắc mình đạt 3 sao? so với ai? So với Guido van Rossum?
Để 4 5 sao nhiều khi phản tác dụng khiến cho phía bên kia tìm cách mà dìm bạn
xuống, khiêm tốn là một (đức) tính được yêu thích (trước) khi đi làm.<br />
<h2>
Các vấn đề bất cập của quy trình phỏng vấn</h2>
<ul>
<li>Không có chuẩn, không có khuôn mẫu, mỗi chỗ một kiểu. Có khi lý do
không ai viết bài về chuyện phỏng vấn bởi nó chả bao giờ giống nhau cả.</li>
<li>Vậy nên những thứ viết trong bài này, hãy xem là tương đối, bởi tác giả
không muốn thêm chữ "một cách tương đối" cuối mỗi câu, hay "đôi khi"/"hầu
hết" ở mỗi đầu câu.</li>
</ul>
<h2>
Những bí quyết phỏng vấn</h2>
<ul>
<li>HIỂU rằng ký hợp đồng lao động và đi làm cho một công ty, đó là một thỏa
thuận <strong>kinh tế</strong> giữa 2 bên, thuận mua - vừa bán, như đi mua thịt mua rau.
Phía công ty dùng tiền để mua - phía lập trình viên bán sức lao động. Không
phải chuyện xin cho, ơn huệ, nợ nần gì ai cả. Không có công ty làm việc không
vì lợi nhuận, nhận vào làm vì <a href="https://www.youtube.com/watch?v=iJKV5miglAg">thương bạn như thương cây bàng
non</a>. Cũng không có chuyện "anh
em như thể người nhà, bạn bè đồng nghiệp như là người thân". No, please.
Bạn sẽ bị thay thế bất cứ lúc nào nếu không còn có lợi cho công ty.</li>
<li>Không để lộ mức lương hiện tại, bằng mọi giá. Vì lý do gì công ty PAMA corp
cần biết mức lương cũ của bạn? lý do duy nhất là để ép nó xuống. Nếu bạn nói
lương hiện tại 10 triệu, công ty hoàn toàn tự tin trả bạn 11 triệu. Nhưng nếu
không biết, họ chỉ có thể trả thấp hơn 1 chút mức bạn yêu cầu. Ở Mỹ, có hẳn
<a href="https://www.sfgate.com/business/networth/article/New-law-bans-California-employers-from-asking-12274431.php">luật cấm các công ty hỏi lịch sử lương ứng viên</a>. Ở Việt Nam không có luật này, nhưng nếu bị hỏi, hãy nói đã kỹ thỏa thuận
không tiết lộ <a href="https://en.wikipedia.org/wiki/Non-disclosure_agreement">NDA</a>
của công ty cũ / hiện tại. HR sẽ hỏi vòng rằng "mức mong đợi của
anh là bao nhiêu?", lúc đó cứ thoải mái nói mức bạn mong muốn, hay cộng 7-10
triệu vào mức hiện tại, không mấy ai nhảy việc để nhận thêm 1 triệu mỗi tháng
cả.</li>
<li>Hỏi rõ mức thu nhập cụ thể, kể cả khoảng (range - từ X đến Y). Rất nhiều công
ty để lương theo kiểu "thỏa thuận", hay "có thể thương lượng", hay HR cương
quyết không nói mà đòi phải qua vòng phỏng vấn. Cũng nên chú ý rằng, nếu
công ty nói là max 2000 USD hay upto 2000 USD, thường nghĩa là họ chỉ trả max
1 nghìn mốt.</li>
<li>Hỏi rõ lương làm Gross hay Net, search internet để hiểu rõ sự khác nhau này.</li>
<li>Nhận 80% lương thử việc là điều thường thấy, nhưng không phải bắt buộc.
Ít khi thấy ở các vị trí câp cao vài nghìn USD.</li>
<li>Không phải "giỏi" kỹ thuật hơn thì lương cao hơn. Có chăng lý do lương A cao
hơn B dù trình độ "ngang nhau" là bởi A giỏi thương lượng/đàm phán/thỏa
thuận hơn B.</li>
<li>Các câu hỏi mang tính chất dìm hàng như bằng cấp (đặc biệt khi bạn không tốt
nghiệp đại học ngành công nghệ thông tin) nhằm làm căn cứ để giảm lương của bạn.
Chỉ một số các công ty nhà nước/ngân hàng mới thực sự đòi hỏi bằng cấp. Hay
khi phải cân nhấc 2 ứng viên ngang ngửa, bằng mới được lôi ra.</li>
<li>Mức lương tối thiểu cho một lập trình viên Python biết làm web Flask hay
dùng pandas hay viết crawler (hay tốt nghiệp <a href="https://pymi.vn/">Pymi.vn</a>)
là 8-10 triệu VND tại năm 2019. Sau 1 năm kinh nghiệm,
con số này có thể gấp đôi, hoặc hơn.</li>
<li>Các yêu cầu ghi trong JD là yêu cầu lý tưởng, chứ không phải điều kiện tiên
quyết. Như 2 năm kinh nghiệm hay bằng đại học, hay 10 dòng yêu cầu khác.</li>
<li>Trượt phỏng vấn 10 công ty: chúc mừng, bạn đã đánh 10 con quái và lên level 2.
Thất bại là một phần tất yếu của thành công.
Quan trọng là về biết rút kinh nghiệm, học những câu trả lời sai để lần sau
còn làm đúng. Rồi bạn sẽ ngạc nhiên khi nhiều công ty/nhiều vòng phỏng vấn
có "kho" câu hỏi trùng nhau.</li>
</ul>
<h2>
Kỹ thuật</h2>
Bên trên toàn nói chuyện thoả thuận, tiền, thế còn các câu hỏi kỹ thuật thì sao?
các thuật toán cao siêu thì thế nào???<br />
Thực ra những vấn đề kỹ thuật không mang nhiều tính chất quyết định. Đúng như
táo quân nói, những thứ khác quan trọng hơn:<br />
<ul>
<li>quan hệ: rất nhiều người đi làm nhờ quan hệ, quan hệ ở đây không có nghĩa xấu
như trong táo quân. Một lập trình viên giỏi chưa chắc có việc nhanh bằng
một thanh niên biết code có nhiều môi quen biết, biết chỗ nào đang cần người.
Nhiều mối ngon cũng đến do ông A chỉ biết thằng B làm Python, chả biết thằng
nào khác cả. Lên mạng đi tuyển lại phải đau đầu với chuyện tuyển dụng/ cạnh
tranh với hàng trăm công ty khác khi dev Python giờ hot như cồn và khẩu trang.</li>
<li>may mắn: ôn đúng bài, bị hỏi toàn cái biết, hay đẹp trai cũng là một dạng may
mắn.</li>
</ul>
<h3>
Những điều không phải là thất bại</h3>
<ul>
<li>Các công ty phỏng vấn Python nhưng lại toàn hỏi C, Java, PHP... Nếu là một
lập trình viên Python / backend, thì khi phỏng vấn tôi cần trả lời các vấn
đề về Python, hay SQL, hay thậm chí tool làm việc như git, docker. Nhưng
chẳng có lý do gì lại hỏi C array so sánh với Python list, hay Java/C#
design pattern cho Python cả. Khi gặp những công ty hỏi kiểu này, bạn không trả
lời được thì đừng trách mình "dốt". Nếu có được hỏi lại, hãy hỏi họ giá trị gì
mang lại khi họ hỏi những câu đó với 1 lập trình viên Python. Hoặc bạn sẽ
biết phía bên kia không biết gì về Python (không hiếm với các công ty outsource),
hoặc sẽ học được điều gì đó.</li>
<li>Trượt các câu hỏi thuật toán/IQ: các câu hỏi này rất đa dạng, trừ khi bạn
dành đủ 6 tháng 2 năm cày luyện Cracking Coding Interview để thi vào Google,
cày leetcode... thì đừng mong trả lời được mọi câu hỏi thuật toán lắt léo.
Và nên nhớ, công ty bạn đang nộp vào, không phải là Google.
Quy trình phỏng vấn dùng các câu hỏi thuật toán <a href="https://news.ycombinator.com/item?id=22331804">hiện được xem như một quy
trình nhiều lỗ hổng</a>,
bởi nó không phải 1 cách tốt để dánh giá ứng viên.</li>
<li>Trượt các câu hỏi lý thuyết trong trường đại học: bên tuyển dụng thường hay hỏi
kể tên các thuật toán sort bạn biết: bubble sort, quick sort, merge sort,
heapsort, insert sort, hay cả <a href="https://www.familug.org/2014/12/algorithm-sleep-sort.html">sleep sort</a>
thế nhưng chưa chắc họ đã biết tên thuật toán sort trong Python (list.sort)
tên là gì? (it's Tim sort) và khả năng cao khác, là họ không giải thích được khi
nào bạn phải dùng các thuật toán trường học trên thay vì Tim sort có sẵn
trong Python.</li>
</ul>
<h2>
Fun fact</h2>
<ul>
<li>Có những công ty thực hiện đến hơn 10 cuộc phỏng vấn (phỏng vấn lần lượt từng
người trong team), kéo dài vài tháng.</li>
<li>Có nhiều công ty "top", "hot", "đỉnh", "chỉ tuyển người giỏi nhất", nhưng trả
mức thù lao mà chỗ nào cũng trả được.</li>
</ul>
<h2>
Dặn dò</h2>
Nhớ làm thành thạo bài tập của <a href="https://pymi.vn/">pymi</a> và đọc <a href="https://faq.pymi.vn/">PYMI interview
FAQs</a>.<br />
<h2>
Kết luận</h2>
Phỏng vấn là một kỹ năng, giống như code, để giỏi, cần phải rèn luyện nhiều.
Phỏng vấn là một cuộc đấu trí, thậm chí chẳng có câu hỏi kỹ thuật chi tiết nào
được đưa ra mà ứng viên vẫn hoàn thành xuất sắc cuộc phỏng vấn.<br />
Nếu lần đầu đi nộp CV, hãy rải thảm tất cả các công ty, hãy đi phỏng vấn thật
nhiều, trượt thật nhiều, rút kinh nghiệm, rồi sau 10 lần phỏng vấn, kiểu gì
chả trúng 1. Còn nếu đã đi làm, trừ khi ấm chỗ leader/trưởng phòng/giám đốc/CTO/
CBE thì mỗi mùa xuân hạ thu đông cũng nên đi phỏng vấn 1 lần để biết trình độ
mình ở đâu, và đáng giá được bao nhiêu nào.<br />
<br />
Theo <a href="https://pp.pymi.vn/article/phongvan/">https://pp.pymi.vn/article/phongvan/</a><br />
trang blog của lớp học lập trình Python <a href="https://pymi.vn/">https://pymi.vn</a> <br />
</div>
hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-54352550677174474972020-04-08T23:51:00.001+07:002020-04-09T21:23:19.063+07:00Luyện Linux command line với wargames Overthewire.org bandit Các CLI (chương trình giao diện dòng lệnh) trên *NIX-OS vốn<a href="https://www.familug.org/search/label/Command" target="_blank"> rất nhiều</a>, mỗi câu lệnh cũng hàng đống các option khác nhau, khi tìm hiểu khó mà kiếm được "bài tập" để làm, luyện, hầu hết biết được đều do "kinh nghiệm từng trải" có được khi cần sử dụng mà ra.<br />
<br />
Trang web overthewire.org là 1 trang "wargames" để luyện chơi CaptureTheFlag (CTF) - một trò chơi ưa thích/phổ biến của giới hacker.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkLdI_W6wliP1UuNaNq4Jtgo53B3I6mf3Y5-Ojp6ccm77-5uMrAPScA0kznwpt010R0uP4HrSFFUqBSKc_7Q-6EHN_iU8JykjNNaALDuVWJT_dV3g7TYWnDj2pRUn9SLXCuAjJprWTWpE/s1600/binary-4791836_640.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="359" data-original-width="640" height="222" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkLdI_W6wliP1UuNaNq4Jtgo53B3I6mf3Y5-Ojp6ccm77-5uMrAPScA0kznwpt010R0uP4HrSFFUqBSKc_7Q-6EHN_iU8JykjNNaALDuVWJT_dV3g7TYWnDj2pRUn9SLXCuAjJprWTWpE/s400/binary-4791836_640.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Image by <a href="https://pixabay.com/users/geralt-9301/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=4791836">Gerd Altmann</a> from <a href="https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=4791836">Pixabay</a></td></tr>
</tbody></table>
Trong các wargame trang này cung cấp, có mục bandit, vốn là phần dễ nhất, rất thích hợp làm bài tập luyện command line.<br />
Mỗi bài đều có hướng dẫn cần dùng câu lệnh gì (option cụ thể phải tự đọc manpage hay search mà tìm hiểu).<br />
<br />
<div style="text-align: center;">
<blockquote class="tr_bq">
<b><a href="https://overthewire.org/wargames/bandit/">https://overthewire.org/wargames/bandit/</a></b></blockquote>
</div>
<br />
Qua mỗi bài sẽ làm nhiệm vụ tìm ra password dể SSH vào bài sau.<br />
<br />
Yêu cầu duy nhất là biết SSH vào server. Ví dụ với bài 'bandit0', gõ:<br />
<a name='more'></a><br />
<br />
<blockquote class="tr_bq">
<span style="color: red;"><b>ssh -v bandit0@bandit.labs.overthewire.org -p 2220</b></span></blockquote>
<br />
Chú ý có thể gặp tình trạng "timed out", do server bị "quá tải", hay mạng bị cá mập cắn, vậy nên thử lại vài lần là sẽ được.<br />
<br />
12 bài đầu sẽ rất cơ bản, các lệnh ls, cat, grep, ... mỗi bài chỉ mất cỡ 1 phút để làm.<br />
<br />
Bài 12 xuất hiện <a href="https://www.familug.org/2014/12/programming-phe-rot13.html" target="_blank">ROT13</a> lừng danh<br />
<br />
Bài 1x xuất hiện nmap, nc, telnet, openssl s_client, setuid <br />
Bài 2x xuất hiện cron<br />
Bài 24 sẽ phải viết script đầu tiên.<br />
Bài 26 THỰC SỰ HAY/KHÓ, gợi ý duy nhất là đọc kỹ manpage của lệnh more, đảm bảo bạn biết mọi tính năng và cách nó hoạt động.<br />
Bài 27 đơn giản nếu là một vim user thành thạo<br />
<br />
<br />
...<br />
có 34 bài.<br />
<br />
Happy CLIng.<br />
<br />
Hết.<br />
<br />
HVN at https://pymi.vn and https://www.familug.org<br />
<br />
<br />hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-20410659047100920602020-04-04T20:18:00.001+07:002020-04-05T15:59:35.395+07:00disown, reptyr, nohup, tmux, screen - chạy job ở background Khi chạy các câu lệnh command line, đặc biệt khi đang SSH vào server, ta thường nảy ra nhu cầu: cho câu lệnh tiếp tục chạy nhưng không cần phải kết nối vào server, hay tắt terminal đi.<br />
<br />
Cách làm truyền thống là dùng lệnh `nohup` (no hang up) khi gọi câu lệnh. Câu lệnh sẽ tiếp tục chạy dù tắt terminal hay thoát khỏi server. Cú pháp:<br />
nohup câu_lệnh_như_bình_thường<br />
<br />
Vấn đề ở chỗ: dùng nohup yêu cầu ta phải tính trước chuyện này, phải gõ nohup trước câu lệnh cần chạy, nếu nhỡ quên nohup mà đang chạy dở chừng thì làm sao?<br />
<br />
disown là giải pháp<br />
tmux/screen là giải pháp gián tiếp.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRdS1QEfVb2OBC7IMITJbjaeosBZz777OaC4JJJhrafvS7sz0UkUmfZiJs3UvD92CdqbwcfIklkK6bwkcp-CEa35hR6ULALnMsu9sR9xRxLX1GpiuJrTi3TmScNTw05yDs1p8wo-Posk8/s1600/male-hacker-260nw-652144957.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="280" data-original-width="390" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRdS1QEfVb2OBC7IMITJbjaeosBZz777OaC4JJJhrafvS7sz0UkUmfZiJs3UvD92CdqbwcfIklkK6bwkcp-CEa35hR6ULALnMsu9sR9xRxLX1GpiuJrTi3TmScNTw05yDs1p8wo-Posk8/s1600/male-hacker-260nw-652144957.jpg" /></a></div>
<h3>
disown là gì?<a name='more'></a></h3>
disown là built-in command của các shell hay dùng: bash, zsh, ksh, không phải cài đặt gì cả.<br />
<br />
<blockquote class="tr_bq">
$ man bash | grep ' disown' -A4<br />
disown [-ar] [-h] [jobspec ... | pid ... ]<br />
Without options, remove each jobspec from the table of active jobs. If jobspec is not present, and neither the -a nor the -r option is supplied, the current job is used. If the -h option is given, each jobspec is not removed from the table, but is marked so that SIGHUP is not sent to the job if the shell receives a SIGHUP. If no jobspec is supplied, the -a option means to remove or mark all jobs; the -r option without a jobspec argument restricts operation to running jobs. The return value is 0 unless a jobspec does not specify a valid job.</blockquote>
<h3>
jobs , background job, foreground job</h3>
Khi chạy 1 câu lệnh trên bash, câu lệnh đó sẽ giữ quyền điều khiển, ta không thể chạy câu lệnh nào khác cho đến khi câu lệnh đang chạy chạy xong, ví dụ sau chạy wget để tải 1 flle cài OpenBSD<br />
<blockquote class="tr_bq">
<br />
$ wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs<br />
--2020-04-04 19:25:55-- https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs<br />
Resolving cdn.openbsd.org (cdn.openbsd.org)... 151.101.10.217<br />
Connecting to cdn.openbsd.org (cdn.openbsd.org)|151.101.10.217|:443... connected.<br />
HTTP request sent, awaiting response... 200 OK<br />
Length: 472317952 (450M) [application/octet-stream]<br />
Saving to: ‘install66.fs’<br />
<br />
install66.fs 0%[ ] 1011K 207KB/s eta 42m 1s ^Z<br />
[1]+ Stopped wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs</blockquote>
<br />
Bấm ctrl Z (^Z) để dừng process đang chạy này lại, trả quyền điều khiển về cho bash. Vậy câu lệnh đang chạy nó đi đâu mất rồi?<br />
bash có chứa một danh sách các chương trình đang chạy này và gọi là `job`.<br />
Gõ lệnh jobs để hiện danh sách các active job:<br />
<br />
<blockquote class="tr_bq">
$ jobs<br />
[1]+ Stopped wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs</blockquote>
process này hiện ở trạng thái "stopped/suspend", nó không chạy mà đang bị "tạm dừng".<br />
Gõ bg kèm số jobs (ở trên có thấy là thấy số 1), vậy gõ bg %1 để chạy job này ở "background".<br />
<br />
<blockquote class="tr_bq">
$ bg %1<br />
[1]+ wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs &<br />
Redirecting output to ‘wget-log’.</blockquote>
<br />
Giờ gõ lại `jobs` sẽ thấy chữ "Running" chứ không phải "Stopped", và process được chạy trở lại.<br />
<br />
<blockquote class="tr_bq">
$ jobs<br />
[1]+ Running wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs &</blockquote>
<br />
Để chuyển chương trình này về foreground, chiếm lại "sân khấu", gõ fg %1:<br />
<br />
<blockquote class="tr_bq">
$ fg %1<br />
wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs<br />
--2020-04-04 19:25:55-- https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs<br />
Resolving cdn.openbsd.org (cdn.openbsd.org)... 151.101.10.217<br />
Connecting to cdn.openbsd.org (cdn.openbsd.org)|151.101.10.217|:443... connected.<br />
HTTP request sent, awaiting response... 200 OK<br />
Length: 472317952 (450M) [application/octet-stream]<br />
Saving to: ‘install66.fs’<br />
<br />
<br />
2020-04-04 19:47:01 (602 KB/s) - Read error at byte 23899568/472317952 (Success). Retrying.<br />
<br />
--2020-04-04 19:47:02-- (try: 2) https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs<br />
Connecting to cdn.openbsd.org (cdn.openbsd.org)|151.101.10.217|:443... connected.<br />
HTTP request sent, awaiting response... 206 Partial Content<br />
Length: 472317952 (450M), 448418384 (428M) remaining [application/octet-stream]<br />
Saving to: ‘install66.fs’<br />
<br />
install66.fs 5%[+++> ] 23,99M 148KB/s eta 49m 8s ^Z<br />
[1]+ Stopped wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs</blockquote>
<br />
Một process khi ở trạng thái stopped/suspend, nếu xem trong bảng ps/top, sẽ thấy trạng thái là T to:<br />
<br />
<blockquote class="tr_bq">
$ ps xau | grep wget<br />
hvn 30707 0.0 0.0 49628 6280 pts/0 T 19:25 0:00 wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs</blockquote>
<br />
T = stopped by job control signal<br />
<br />
Một cách khác để thay đổi trạng thái T này sang chạy, là gửi signal: SIGCONT<br />
<br />
<blockquote class="tr_bq">
$ ps xau | grep wget<br />
hvn 30707 0.0 0.0 49628 6280 pts/0 T 19:25 0:00 wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs<br />
<br />
$ kill -SIGCONT 30707<br />
Redirecting output to ‘wget-log’.<br />
<br />
$ ps xau | grep wget<br />
hvn 30707 0.0 0.0 49628 6280 pts/0 S 19:25 0:00 wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs</blockquote>
<h3>
Disown</h3>
<br />
Gõ disown %1 để bỏ jobs này khỏi list jobs:<br />
<blockquote class="tr_bq">
$ disown %1<br />
$ jobs<br />
$ ps auwwxf | grep -B2 wget<br />
hvn 14428 0.0 0.0 31816 6064 pts/1 Ss+ 15:22 0:00 | \_ bash<br />
hvn 19332 0.0 0.2 43044 17536 pts/0 Ss 17:57 0:01 | \_ bash<br />
hvn 30707 0.0 0.0 49628 6280 pts/0 S 19:25 0:01 | | \_ wget https://cdn.openbsd.org/pub/OpenBSD/6.6/amd64/install66.fs</blockquote>
<br />
Giờ tắt terminal đi, hay thoát khỏi server khi đang SSH thì process này vẫn cứ tiếp tục chạy.<br />
<br />
Khi tắt terminal, hay thoát khỏi server, bash shell sẽ nhận được SIGHUP, và mặc định nó sẽ truyền SIGHUP này tới tất cả process con của nó để tắt hết đi. Disown 1 job sẽ bỏ qua job đó và không truyền SIGHUP trong tương lại. Khi thoát khỏi shell, process bash kết thúc, process con bị mất parent trở thành orphan process, thường sẽ được system manager PID 1 nhận làm process con.<br />
<br />
<blockquote class="tr_bq">
<pre><code>/* Cause all jobs, running or stopped, to receive a hangup signal. If
a job is marked J_NOHUP, don't send the SIGHUP. */
void
hangup_all_jobs ()
{
register int i;
/* XXX could use js.j_firstj here */
for (i = 0; i < js.j_jobslots; i++)
{
if (jobs[i])
{
if (jobs[i]->flags & J_NOHUP)
continue;
killpg (jobs[i]->pgrp, SIGHUP);
if (STOPPED (i))
killpg (jobs[i]->pgrp, SIGCONT);
}
}
}</code></pre>
</blockquote>
<a href="http://git.savannah.gnu.org/cgit/bash.git/tree/jobs.c?h=bash-4.4-testing#n1414">http://git.savannah.gnu.org/cgit/bash.git/tree/jobs.c?h=bash-4.4-testing#n1414</a><br />
<br />
<a href="https://github.com/att/ast/blob/683bccf3bab8545b6334ab7b7c179e08f5eb89fa/src/cmd/ksh93/sh/jobs.c#L981" target="_blank">https://github.com/att/ast/blob/683bccf3bab8545b6334ab7b7c179e08f5eb89fa/src/cmd/ksh93/sh/jobs.c#L981 </a><br />
<h3>
reptyr</h3>
Ngược lại với disown, reptyr giúp "chiếm" lại quyền điều khiển process sau khi bị disown.<br />
<br />
<blockquote class="tr_bq">
$ apt-cache search reptyr<br />
reptyr - Tool for moving running programs between ptys</blockquote>
<h3>
Screen và Tmux</h3>
Thay vì phải nohup,disown, thì một giải pháp khác là luôn chạy lệnh trong tmux/screen. Câu lệnh đầu tiên sau khi SSH vào server là gõ tmux.<br />
Tmux hay screen sẽ tiếp tục chạy câu lệnh dù kết nối đến server bị đứt, hay thoát khỏi terminal.<br />
<br />
Tham khảo<br />
- man bash<br />
- <a href="https://superuser.com/questions/184047/bash-zsh-undoing-disown">https://superuser.com/questions/184047/bash-zsh-undoing-disown</a><br />
<br />
Hết<br />
HVN at https://pymi.vn và https://www.familug.org<br />
Đăng ký học Python tại https://pymi.vnhvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-23753126707063082362020-02-05T00:13:00.000+07:002020-02-05T00:19:42.615+07:00Grep, SilverSearcher (ag), RipGrep (rg) và file ẩn Trên *NIX, một file được gọi là file ẩn (hidden file) khi tên của nó bắt đầu với dấu chấm (.).<br />
<br />
Các file phổ biến thường gặp là các file để cấu hình các phần mềm, ví dụ: .vimrc .bashrc .gitignore .gitlabci,yml ...<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsAp6kgK-_EPn-9a8Nz_iZKpNx6UmKU2CtCWH38rjK4hK0Yt5SNMth2sD8pFtTYsALI9qf-KVpo7HhU4aVljd5Tag6rW0df2ZxijJP-e5dDjPmfNYbp0SQ5arGUefPiLr-tP8nS1vk4gs/s1600/detective-156465_640.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="543" data-original-width="640" height="271" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsAp6kgK-_EPn-9a8Nz_iZKpNx6UmKU2CtCWH38rjK4hK0Yt5SNMth2sD8pFtTYsALI9qf-KVpo7HhU4aVljd5Tag6rW0df2ZxijJP-e5dDjPmfNYbp0SQ5arGUefPiLr-tP8nS1vk4gs/s320/detective-156465_640.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Image by <a href="https://pixabay.com/users/OpenClipart-Vectors-30363/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=156465">OpenClipart-Vectors</a> from <a href="https://pixabay.com/?utm_source=link-attribution&utm_medium=referral&utm_campaign=image&utm_content=156465">Pixabay</a></td></tr>
</tbody></table>
<br />
Khi muốn tìm một từ khóa bất kỳ trong các file ở thư mục hiện tại, ta có thể dùng grep:<br />
<br />
grep -Rin noi_dung . # dấu chấm đại diện cho thư mục hiện tại<br />
<br />
<blockquote class="tr_bq">
$ mkdir testdir; echo pika > testdir/.filepassword<br />
$ cd testdir/<br />
$ grep -Rin pika .<br />
./.filepassword:1:pika</blockquote>
<br />
Một nhược điểm của grep so với các chương trình "hiện đại" hơn như <a href="https://www.familug.org/search/label/ag" target="_blank">SilverSearcher (ag)</a> hay RipGrep (rg) là<br />
<a name='more'></a><br />
nó không hiểu các file ignore, đặc biệt là .gitignore, nên khi tìm kiếm, nó vẫn thực hiện tìm cả trong các thư mục mà người dùng đã cố tình bỏ qua bằng việc ghi tên chúng vào .gitignore.<br />
<br />
ag và rg mặc định sẽ tuân thủ file .gitignore, và cũng mặc định bỏ qua các hidden file, kể cả thư mục .git trong các software project quản lý bằng git.<br />
<br />
Điều này sẽ gây ra rắc rối nếu người dùng chuyển từ grep sang các chương trình hiện đại hơn mà không đọc kỹ hướng dẫn:<br />
<br />
<br />
Để tìm cả các file hidden, với <a href="https://www.familug.org/search/label/ag" target="_blank">ag</a> cần dùng option --hidden hay -u<br />
<blockquote class="tr_bq">
<br />
--hidden<br />
Search hidden files. This option obeys ignored files. <br />
<br />
-u --unrestricted<br />
Search all files. This ignores .ignore, .gitignore, etc. It searches binary and hidden files as well.</blockquote>
<br />
<blockquote class="tr_bq">
$ ag pika # không thấy gì<br />
$ ag --hidden pika<br />
.gitignore<br />
1:pikagit</blockquote>
<br />
Tương tự với rg<br />
<br />
<blockquote class="tr_bq">
$ rg -uuu pika<br />
.filepassword<br />
1:pika<br />
<br />
$ rg --hidden pika<br />
.filepassword<br />
1:pika</blockquote>
<br />
Nếu luôn muốn tìm hidden file mà vẫn tuân theo ignore files như .gitignore, bạn có thể đặt alias trong .bashrc:<br />
<blockquote class="tr_bq">
<br />
alias ag='ag --hidden'</blockquote>
<br />
Hết.<br />
<br />
HVN at https://pymi.vn and https://www.familug.orghvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-77161237739931821242020-02-03T23:44:00.000+07:002020-02-04T08:35:51.627+07:00Cài Unbound làm caching DNS resolver trên máy ảo OpenBSD 6.6<h3>
DNS Server (<span class="js-about-item-abstr">recursive name server)</span></h3>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiysUgYB8rIz-VN8GY4FQxVOpzL8_ayjbm0YBRsapMEwpnRMElsjlJ0_zW1xNVrzznkj7CzR-xTjQubwQQ8dtGm1qvgcwZZa-Y-uEd1QWnwia2LWJRHkztBonkHQuWTiJDV0f6unmAYoRs/s1600/banner1.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="62" data-original-width="468" height="42" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiysUgYB8rIz-VN8GY4FQxVOpzL8_ayjbm0YBRsapMEwpnRMElsjlJ0_zW1xNVrzznkj7CzR-xTjQubwQQ8dtGm1qvgcwZZa-Y-uEd1QWnwia2LWJRHkztBonkHQuWTiJDV0f6unmAYoRs/s320/banner1.gif" width="320" /></a></div>
<br />
Người dùng máy tính đặc biệt ở Việt Nam đã từng biết đổi DNS để vào Facebook những ngày mạng còn "chậm".<br />
<br />
Những con số như 8.8.8.8 (Google DNS), 208.67.222.222 (OpenDNS) hay 1.1.1.1 (Cloudflare DNS) không còn lạ gì.<br />
Những địa chỉ IP này có chạy 1 DNS server làm nhiệm vụ trả lời IP tương ứng của các domain được hỏi.<br />
<br />
Ví dụ facebook.com có (những) IP là gì?<br />
<blockquote class="tr_bq">
$ nslookup facebook.com 8.8.8.8<br />
Server: 8.8.8.8<br />
Address: 8.8.8.8#53<br />
<br />
Non-authoritative answer:<br />
Name: facebook.com<br />
Address: 31.13.95.36<br />
Name: facebook.com<br />
Address: 2a03:2880:f102:83:face:b00c:0:25de</blockquote>
<h3>
authoritative name server vs recursive resolver</h3>
Chương trình chạy ở 8.8.8.8 sẽ tìm địa chỉ IP tương ứng với domain facebook.com như bạn đã yêu cầu. Đây là một dịch vụ "trung gian", nó sẽ đi hỏi các "root nameservers" (.com, .org...) để tìm câu trả lời, bản thân nó không có câu trả lời.<br />
Việc đi hỏi chỗ khác để lấy câu trả lời gọi là "recursive query", bởi chỗ khác cũng có thể không có câu trả lời và phải đi hỏi tiếp cấp cao hơn. Chương trình thực hiện tính năng tìm kiếm "hộ" này gọi là <span style="color: #3d85c6;"><b>Recursive resolver</b></span>. Khác với chương trình có câu trả lời TRỰC TIẾP cho 1 domain cụ thể - gọi là <span style="color: #cc0000;"><b>authoritative name server</b></span>.<br />
<br />
<h4>
Ưu điểm của các dịch vụ này </h4>
- Tính sẵn sàng cao, ít khi bị sập (đều do những "ông lớn" internet vận hành)<br />
- Có thể nhanh hơn, ví dụ với Google DNS 8.8.8.8, khi được truy cập, một server của Google nằm gần bạn nhất sẽ trả lời. (Phía dưới số 8.8.8.8 không phải là 1 máy ở đâu đó, mà là rất nhiều máy, và nó chọn cái nào gần bạn nhất)<br />
<br />
<h4>
NHƯỢC ĐIỂM</h4>
- Privacy (sự riêng tư): mọi câu hỏi của bạn, dịch vụ DNS đều biết. Tức là bạn vào website nào, thì Google (cho 8.8.8.8), hay Cloudflare (1.1.1.1), ... đều biết cả. Nghe có vẻ không to tát, nhưng hãy nghĩ xem, chỉ cần biết các trang web bạn vào ngày hôm nay, cũng phần LỚN biết bạn đang làm gì (youtube, facebook, hay <span style="color: red;"><b>các trang web top truy cập</b></span> của Việt Nam) <br />
<br />
<h3>
Unbound <a name='more'></a></h3>
1 cách đơn giản để giải quyết vấn đề này là tự chạy DNS server để dùng trong mạng nhà mình (cho cả điện thoại), hay thậm chí, trên máy tính của mình.<br />
Unbound là giải pháp dễ dàng, xịn nhất ở đây.<br />
<br />
<h3>
<a href="https://nlnetlabs.nl/projects/unbound/about/" target="_blank">Unbound</a> vs <a href="https://www.isc.org/bind/" target="_blank">BIND</a></h3>
Unbound ra đời sau, có kiến trúc hiện đại hơn, cấu hình đơn giản hơn, và được cho là bảo mật hơn.<br />
<br />
Cài đặt Unbound<br />
Trên Ubuntu 18.04:<br />
<br />
<blockquote class="tr_bq">
sudo apt-get update && sudo apt-get install -y unbound</blockquote>
Phần còn lại hướng dẫn cấu hình unbound trên máy ảo OpenBSD sử dụng Vagrant.<br />
<br />
<blockquote class="tr_bq">
$ whatis unbound<br />
unbound (8) - Unbound DNS validating resolver </blockquote>
<h3>
Unbound trên <a href="https://www.openbsd.org/" target="_blank">OpenDNS</a></h3>
(hoàn toàn tương tự có thể cài Unbound trên Ubuntu, Debian...)<br />
<br />
Unbound là DNS server mặc định trên <a href="https://www.openbsd.org/" target="_blank">OpenBSD</a>, hệ điều hành bảo mật nhất trái đất (theo ai đánh giá thì tùy người đó).<br />
Tạo OpenBSD box dùng <a href="https://www.familug.org/search/label/vagrant" target="_blank">Vagrant</a><br />
<blockquote class="tr_bq">
mkdir openbsdvm<br />
cd openbsdvm/ <br />
vagrant init <br />
sed -i 's:base:generic/openbsd6:' Vagrantfile <br />
vagrant up <br />
#Chờ Vagrant tải OpenBSD box về máy<br />
vagrant ssh </blockquote>
<br />
SSH vào máy ảo, bật unbound và hỏi unbound xem IP của <a href="http://pymi.vn/">pymi.vn</a> là bao nhiêu<br />
<blockquote class="tr_bq">
-bash-5.0$ uname -a<br />
OpenBSD bazinga.localdomain 6.6 GENERIC.MP#372 amd64<br />
-bash-5.0$ sudo -sH<br />
bash-5.0# rcctl enable unbound<br />
bash-5.0# rcctl start unbound<br />
bash-5.0# ps xau | grep unbound<br />
_unbound 32604 0.0 0.5 11408 11372 ?? I 3:36PM 0:00.04 unbound -c /var/unbound/etc/unbound.conf<br />
root 3181 0.0 0.0 132 300 p0 R+/1 4:04PM 0:00.00 grep unbound<br />
-bash-5.0$ nslookup tuoitre.vn 8.8.8.8<br />Server: 8.8.8.8<br />Address: 8.8.8.8#53<br /><br />Non-authoritative answer:<br />Name: tuoitre.vn<br />Address: 222.255.239.80<br /><br />-bash-5.0$ nslookup tuoitre.vn 127.0.0.1<br />Server: 127.0.0.1<br />Address: 127.0.0.1#53<br /><br />Non-authoritative answer:<br />Name: tuoitre.vn<br />Address: 222.255.239.80</blockquote>
Kết quả unbound trả về giống hệt 8.8.8.8 của Google.<br />
<br />
Là một máy ảo vagrant, ta có thể truy cập máy này từ máy host (máy thật), tìm địa chỉ IP của máy ảo vagrant (chú ý, có thể set IP cố định trong Vagrantfile):<br />
<blockquote class="tr_bq">
bash-5.0# ifconfig | grep inet<br />
inet 127.0.0.1 netmask 0xff000000<br />
inet 192.168.121.82 netmask 0xffffff00 broadcast 192.168.121.255</blockquote>
Thay đổi file /var/unbound/etc/unbound.conf (đường dẫn xuất hiện trong output lệnh ps xau)<br />
<blockquote class="tr_bq">
server:<br />
interface: 0.0.0.0<br />
#...<br />
access-control: 0.0.0.0/0 refuse<br />
access-control: 127.0.0.0/8 allow<br />
access-control: 192.168.121.0/24 allow<br />
# ...</blockquote>
<br />
Thay interface từ 127.0.0.1 thành 0.0.0.0 để bên ngoài có thể truy cập (bên ngoài thực ra cũng chỉ là máy host, muốn các thiết bị trong LAN - các thiết bị dùng cùng mạng truy cập được, cần <a href="https://www.vagrantup.com/docs/networking/public_network.html" target="_blank">config network bridge</a> cho máy ảo này )<br />
<br />
Restart rồi truy cập từ máy host:<br />
<blockquote class="tr_bq">
bash-5.0# rcctl restart unbound<br />
unbound(ok)<br />
unbound(ok)</blockquote>
Từ máy host<br />
<blockquote class="tr_bq">
$ nslookup pymi.vn 192.168.121.82<br />
Server: 192.168.121.82<br />
Address: 192.168.121.82#53<br />
<br />
Non-authoritative answer:<br />
Name: pymi.vn<br />
Address: 104.27.128.55<br />
Name: pymi.vn<br />
Address: 104.27.129.55<br />
Name: pymi.vn<br />
Address: 2606:4700:3034::681b:8137<br />
Name: pymi.vn<br />
Address: 2606:4700:3036::681b:8037</blockquote>
<br />
Giờ có thể <a href="https://duckduckgo.com/?q=h%C6%B0%E1%BB%9Bng+d%E1%BA%ABn+%C4%91%E1%BB%95i+DNS+%C4%91%E1%BB%83+v%C3%A0o+facebook+ubuntu&t=ffab&ia=web" target="_blank">đổi DNS của máy host tới địa chỉ này như từng đổi thành 8.8.8.8 </a>, rồi enjoy. <br />
<br />
Chú ý: sẽ cần bật máy ảo mỗi khi bật máy, điều này có thể config trên backend của Vagrant (Tức Virtualbox hay KVM hay VMWare). HOẶC cài trực tiếp Unbound lên máy của bạn thay vì máy ảo, rồi set DNS server tới 127.0.0.1.<br />
<br />
Chú ý: Unbound có sẵn tính năng caching, tức lần đầu truy cập thì chậm, nhưng từ lần 2 trở đi sẽ là ngay lập tức (chú ý query time của lần 2).<br />
<br />
<blockquote class="tr_bq">
$ dig familug.org @192.168.121.82<br />
<br />
; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> familug.org @192.168.121.82<br />
;; global options: +cmd<br />
;; Got answer:<br />
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5067<br />
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1<br />
<br />
;; OPT PSEUDOSECTION:<br />
; EDNS: version: 0, flags:; udp: 4096<br />
;; QUESTION SECTION:<br />
;familug.org. IN A<br />
<br />
;; ANSWER SECTION:<br />
familug.org. 300 IN A 216.239.32.21<br />
<br />
;; Query time: 251 msec<br />
;; SERVER: 192.168.121.82#53(192.168.121.82)<br />
;; WHEN: Mon Feb 03 23:39:20 +07 2020<br />
;; MSG SIZE rcvd: 56<br />
<br />
$ dig familug.org @192.168.121.82<br />
<br />
; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> familug.org @192.168.121.82<br />
;; global options: +cmd<br />
;; Got answer:<br />
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54850<br />
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1<br />
<br />
;; OPT PSEUDOSECTION:<br />
; EDNS: version: 0, flags:; udp: 4096<br />
;; QUESTION SECTION:<br />
;familug.org. IN A<br />
<br />
;; ANSWER SECTION:<br />
familug.org. 298 IN A 216.239.32.21<br />
<br />
;; Query time: 0 msec<br />
;; SERVER: 192.168.121.82#53(192.168.121.82)<br />
;; WHEN: Mon Feb 03 23:39:22 +07 2020<br />
;; MSG SIZE rcvd: 56</blockquote>
<br />
Hết.<br />
Bài viết thực hiện trên:<br />
<blockquote class="tr_bq">
$ lsb_release -d; dpkg -l vagrant | grep vagrant<br />
Description: Ubuntu 18.04.4 LTS<br />
ii vagrant 2.0.2+dfsg-2ubuntu8 all Tool for building and distributing virtualized development environments</blockquote>
<br />
<br />
HVN at <a href="https://pymi.vn/">https://pymi.vn</a> and <a href="https://www.familug.org/">https://www.familug.org</a><br />
<br />hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com1tag:blogger.com,1999:blog-7188813081599719537.post-80272021008073447452020-02-02T17:10:00.002+07:002020-02-02T20:29:52.590+07:00Vagrant - vẫn là công cụ quản lý máy ảo tuyệt vời ở năm 2020 - What???<br />
- Yeah!!!<br />
<br />
Vagrant không phải mới, không phải lạ, ra đời cỡ 10 năm trước hay hơn.<br />
<br />
<h3>
<a href="https://www.vagrantup.com/" target="_blank">Vagrant</a> là gì</h3>
<br />
<span class="phoneticspelling">/<a href="https://www.lexico.com/definition/vagrant" target="_blank">ˈveɪɡr(ə)nt/</a></span><a href="https://www.lexico.com/definition/vagrant" target="_blank"><span class="iteration"> </span><span class="ind">A person without a settled home or regular work who wanders from place to place and lives by begging.</span></a><span class="phoneticspelling"></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFRbb0dy3hBSbpfMNhlS5NqBjOFDGp9lqE6uOtLCyZaa5lSEPJRnKxqhbRHDeipX-lbrRW0HA2nOXbErW3afPfS7Ezfh5iYcQl99W5_ACkMs6oV1avGZS78Z2GLGwvjzRQD5gUMrnY9Cw/s1600/vagrantlogo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="180" data-original-width="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFRbb0dy3hBSbpfMNhlS5NqBjOFDGp9lqE6uOtLCyZaa5lSEPJRnKxqhbRHDeipX-lbrRW0HA2nOXbErW3afPfS7Ezfh5iYcQl99W5_ACkMs6oV1avGZS78Z2GLGwvjzRQD5gUMrnY9Cw/s1600/vagrantlogo.png" /></a></div>
<br />
<br />
Vagrant là công cụ để quản lý (tạo/bật/tắt) máy ảo, chạy ngon lành trên các hệ điều hành phổ biến.<br />
Vagrant là công cụ không thể thiếu của developer thời trước khi có Docker.<br />
<br />
<h3>
Bạn có thể tạo một máy ảo nhanh nhất trong bao nhiêu phút?</h3>
- Tải file ISO , vd Ubuntu<br />
- Cài Virtualbox, hay KVM hay VMWare<br />
- Cài máy ảo, format ổ đĩa, next next next ..., tùy vào từng hệ điều hành sẽ phải thao tác khác nhau.<br />
<br />
Hoặc đơn giản với vagrant:<br />
<br />
- Cài KVM hay Virtualbox hay VMWare<br />
- Chạy lệnh: vagrant init<br />
- Sửa file Vagrantfile vừa được tạo, thay box "base" bằng box mong muốn, ví dụ "generic/ubuntu1804" (tìm các "box" trên https://vagrantcloud.com/search)<br />
- Chạy lệnh: vagrant up<br />
- Vagrant sẽ tải "box" về máy trong lần đầu sử dụng box này (các lần sau vẫn dùng box generic/ubuntu1804 sẽ không cần tải lại ở các lần sau), sau đó máy sẽ bật lên trong vòng ~< 1 phút, không cần cài đặt<br />
- SSH bằng lệnh: vagrant ssh<br />
<br />
<script async="" id="asciicast-297340" src="https://asciinema.org/a/297340.js"></script>
<h3>
Các câu lệnh vagrant cần thiết<a name='more'></a></h3>
<br />
- Tạo file mẫu Vagrantfile: vagrant init<br />
- Bật máy ảo: vagrant up<br />
- SSH vào: vagrant ssh<br />
- Hầu hết các box đều cho phép sudo với user mặc định vagrant, gõ sudo -sH để chuyển thành root <br />
- Tắt máy ảo: vagrant halt<br />
- Restart sau khi thay đổi Vagrantfile: vagrant reload.<br />
<br />
<h3>
Các ưu điểm của Vagrant:</h3>
- Chạy trên mọi hệ điều hành phổ biến, nghĩa là ngày xưa bạn dev trên Ubuntu mà giờ công ty cấp cho Macbook thì cũng không có gì thay đổi cả.<br />
- Cấu hình đơn giản: chỉ cần sửa file Vagrantfile<br />
- Nhiều box để lựa chọn, từ debian, ubuntu, centos cho đến cả openbsd<br />
- Chạy máy ảo trong mạng LAN, có địa chỉ IP riêng, so với docker chỉ bind đến 1 port trên cùng địa chỉ máy. Ví dụ: khi config chỉ cho phép set IP mà không cho set port, chạy service qua Docker rồi export port 5353 không thể config được (giả sử port 53 đã bị dùng).<br />
- Giải pháp khi một chương trình nào đó chỉ chạy trên Linux mà bạn lại chỉ được cấp Macbook<br />
<br />
<br />
<blockquote class="tr_bq">
$ whatis vagrant<br />
vagrant (1) - Tool for building and distributing virtualized development environments.</blockquote>
<h4>
Cài đặt vagrant </h4>
Trên Ubuntu 1804:<br />
<br />
<blockquote class="tr_bq">
sudo apt-get update && sudo apt-get install -y vagrant</blockquote>
<br />
Vagrant viết bằng Ruby, bởi Hashicorp https://github.com/hashicorp/vagrant<br />
<br />
<blockquote class="tr_bq">
$ dpkg -s vagrant<br />
...<br />
Version: 2.0.2+dfsg-2ubuntu8<br />
Depends: bsdtar, curl, openssh-client, ruby, ruby-childprocess (>= 0.3.7), ruby-erubis (>= 2.7.0), ruby-i18n (>= 0.6.0), ruby-listen, ruby-log4r (>= 1.1.9), ruby-net-scp (>= 1.1.0), ruby-net-sftp, ruby-net-ssh (>= 1:2.6.6), ruby-rest-client</blockquote>
<br />
Ví dụ tạo một máy ảo Ubuntu 18.04:<br />
<br />
<br />
<blockquote class="tr_bq">
$ mkdir u1804<br />
$ cd u1804/<br />
$ vagrant init<br />
$ sed -i 's:base:generic/ubuntu1804:' Vagrantfile <br />
$ vagrant up<br />
$ vagrant status<br />
Current machine states:<br />
<br />
default running (libvirt)<br />
<br />
The Libvirt domain is running. To stop this machine, you can run<br />
`vagrant halt`. To destroy the machine, you can run `vagrant destroy`.<br />
<br />
$ vagrant ssh -c 'lsb_release -d'<br />
Description: Debian GNU/Linux 10 (buster)<br />
Connection to 192.168.121.114 closed.</blockquote>
<blockquote>
$ vagrant halt<br />
==> default: Halting domain...</blockquote>
<br />
Hết.<br />
<br />
HVN at https://pymi.vn and https://familug.org hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com1tag:blogger.com,1999:blog-7188813081599719537.post-29555471832896148752020-01-07T17:32:00.001+07:002020-01-07T17:46:38.812+07:00Service Discovery với Consul - từ con tới sồ <h3>
### Consul là gì </h3>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJcwsNs2c8pU4oqiyLeHWV5wPtlY5Ir8CDd7vvgBKgtZP3jNSZoi6jmBy96uv6ym0lwcrt_0Wr4lNvHJi4RLGt-9ggfJlEhmUDErgPCEs0419W3_a25r9P3CMtr4FmMf8gvJIyxBs3j6w/s1600/consul.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="677" data-original-width="678" height="199" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJcwsNs2c8pU4oqiyLeHWV5wPtlY5Ir8CDd7vvgBKgtZP3jNSZoi6jmBy96uv6ym0lwcrt_0Wr4lNvHJi4RLGt-9ggfJlEhmUDErgPCEs0419W3_a25r9P3CMtr4FmMf8gvJIyxBs3j6w/s200/consul.png" width="200" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">https://www.consul.io/ </td></tr>
</tbody></table>
Cách đọc: /<span style="color: #3d85c6;"><b>ˈkänsəl/ /ˈkɑnsəl</b></span>/ - không phải <strike><span style="color: red;"><b>con sun</b></span></strike><br />
<br />
<blockquote class="tr_bq">
```<br />
$ apt-cache search consul | grep ^consul<br />
consul - tool for service discovery, monitoring and configuration<br />
```</blockquote>
Consul là một phần mềm còn khá mới (bản 1.0 từ 2017<br />
https://github.com/hashicorp/consul/tree/v1.0.0), được ra đời ban đầu như một<br />
giải pháp cho "Service Discovery", kèm KeyValue storage. Sau này phát triển<br />
thêm nhiều tính năng khác như Service Mesh.<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<br />
<iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/NVl9cIiPF80" width="560"></iframe>
<br />
<h3>
</h3>
<h3>
#### Service Discovery là gì </h3>
<a name='more'></a>Khái niệm service discovery không mới mẻ, nhưng chỉ thực sự phổ biến vói từ khóa này <br />
khi Microservices trở thành trào lưu (~ từ 2015).<br />
Service Discovery là việc tìm địa chỉ IP/port của một service trong hệ thống, trở nên<br />
phức tạp khi trong hệ thống có nhiều service. Đặc biệt khi chạy trên cloud (AWS, Azure, GCP...), IP của các service thay đổi thường xuyên hơn do việc tạo/xóa máy ảo (hay container) là chuyện thường ngày trong môi trường cloud.<br />
<br />
Việc tìm từ tên ra IP không đâu xa lạ, chính là việc DNS server đã làm từ khi có internet <br />
tới giờ. Thay vì gõ địa chỉ IP để vào 1 website, người dùng gõ 1 cái tên (domain), các <br />
dịch vụ DNS sẽ đổi từ tên đó ra địa chỉ IP tương ứng. Các phần mềm DNS server phổ biến trên<br />
`*NIX` là BIND, Unbound, ...<br />
<br />
Chuyện thay đổi "động" IP khi thêm service hay thay đổi máy chạy service cũng đã được giải <br />
quyết với DNS server truyền thống qua một cơ chế có tên "Dynamic DNS", nhưng việc sử dụng<br />
cũng không dễ tới mức gọi là "đơn giản".<br />
<br />
Consul ban đầu là một Dynamic DNS server giúp cho việc này trở thành cực kỳ đơn giản. <br />
Ngoài consul, một phần mềm khác cũng không kém phổ biến trong lĩnh vực này là <br />
`etcd` kết hợp với `SkyDNS` (bộ đôi này được dùng trong core của <a href="http://www.familug.org/2017/03/kubernetes.html" target="_blank">Kubernetes</a>).<br />
<br />
Consul có chức năng `HealthCheck`, nó liên tục kiểm tra xem service có truy cập được không,<br />
và chỉ trả về các service "sống" khi được hỏi. Tưởng tượng có 10 máy chạy service web, <br />
khi 3 máy down, service worker hỏi `consul` sẽ chỉ nhận về danh sách 7 máy còn sống.<br />
<br />
Consul nằm trong bộ software của Hashicorp, cùng những cái tên đình đám khác như: Vault,<br />
Terraform, Nomad. Consul đã được dùng trong production của nhiều công ty lớn,<br />
ví dụ như<br />
[GitLab](https://about.gitlab.com/blog/2019/11/08/the-consul-outage-that-never-happened/).<br />
<br />
<br />
<br />
<h3>
#### Ai cần dùng Service Discovery</h3>
Khi cần quản lý nhiều service tương tác với nhau. Khi địa chỉ các máy thay đổi thưởng xuyên.<br />
Hay đơn giản khi cần quản lý các thiết bị trong nhà (<a href="https://www.familug.org/search/label/RaspberryPi" target="_blank">Raspberry Pi</a> cũng có thể chạy consul).<br />
<br />
<h3>
### Cài đặt</h3>
Mặc dù có thể cài bằng apt nhưng phiên bản trên apt rất cũ, không nên dùng. <br />
<br />
<blockquote class="tr_bq">
```<br />
$ apt-cache policy consul<br />
consul:<br />
Installed: (none)<br />
Candidate: 0.6.4~dfsg-3<br />
Version table:<br />
0.6.4~dfsg-3 500<br />
500 http://vn.archive.ubuntu.com/ubuntu bionic/universe amd64 Packages<br />
```</blockquote>
<br />
Phiên bản mới nhất hiện tại <br />
<br />
<blockquote class="tr_bq">
```<br />
$ curl -L api.github.com/repos/hashicorp/consul/tags 2>/dev/null | /usr/bin/python -c 'import sys,json; print([i["name"] for i in json.load(sys.stdin) if "beta" not in i["name"]][0])'<br />
v1.6.2<br />
```</blockquote>
<br />
Tải bản mới nhất qua https://www.consul.io/downloads.html<br />
<br />
<blockquote class="tr_bq">
```<br />
$ curl -LO https://releases.hashicorp.com/consul/1.6.2/consul_1.6.2_linux_amd64.zip<br />
100 38.2M 100 38.2M 0 0 2382k 0 0:00:16 0:00:16 --:--:-- 3591k<br />
$ unzip -l consul_1.6.2_linux_amd64.zip <br />
Archive: consul_1.6.2_linux_amd64.zip<br />
Length Date Time Name<br />
--------- ---------- ----- ----<br />
108053286 2019-11-14 04:31 consul<br />
--------- -------<br />
108053286 1 file<br />
```</blockquote>
<br />
`consul` được viết bằng Golang, nên sản phẩm để mang đi deploy chỉ là 1 file binary duy nhất. File này giải nén ra <br />
có kích thước ~ 100M (yeahhhh) <br />
<br />
<blockquote class="tr_bq">
```<br />
$ unzip consul_1.6.2_linux_amd64.zip <br />
Archive: consul_1.6.2_linux_amd64.zip<br />
inflating: consul <br />
$ du consul -h<br />
104M consul<br />
```</blockquote>
<br />
<h3>
### Chạy ở dev mode </h3>
Consul khi chạy service có 2 chế độ: server hoặc agent.<br />
Một chế độ đặc biệt thứ 3 là `dev` dùng để chạy thử nghiệm.<br />
<br />
<blockquote class="tr_bq">
```<br />
$ ./consul version<br />
Consul v1.6.2<br />
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)<br />
<br />
$ ./consul agent -dev<br />
==> Starting Consul agent...<br />
Version: 'v1.6.2'<br />
Node ID: 'a7e68a9a-77bd-6559-ae17-a302860a6e0d'<br />
Node name: 'hvnzen'<br />
Datacenter: 'dc1' (Segment: '<all>')<br />
Server: true (Bootstrap: false)<br />
Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)<br />
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)<br />
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false, Auto-Encrypt-TLS: false<br />
...<br />
2020/01/05 01:02:17 [INFO] serf: EventMemberJoin: hvnzen.dc1 127.0.0.1<br />
2020/01/05 01:02:17 [INFO] serf: EventMemberJoin: hvnzen 127.0.0.1<br />
2020/01/05 01:02:17 [INFO] consul: Adding LAN server hvnzen (Addr: tcp/127.0.0.1:8300) (DC: dc1)<br />
...<br />
2020/01/05 01:02:17 [INFO] agent: Started DNS server 127.0.0.1:8600 (tcp)<br />
2020/01/05 01:02:17 [INFO] agent: Started DNS server 127.0.0.1:8600 (udp)<br />
2020/01/05 01:02:17 [INFO] agent: Started HTTP server on 127.0.0.1:8500 (tcp)<br />
2020/01/05 01:02:17 [INFO] agent: Started gRPC server on 127.0.0.1:8502 (tcp)<br />
==> Consul agent running!<br />
2020/01/05 01:02:17 [INFO] raft: Node at 127.0.0.1:8300 [Candidate] entering Candidate state in term 2<br />
...<br />
2020/01/05 01:02:17 [INFO] raft: Node at 127.0.0.1:8300 [Leader] entering Leader state<br />
...<br />
```</blockquote>
<br />
Chú ý: consul sử dụng các port sau: <br />
<br />
<blockquote class="tr_bq">
```<br />
$ lsof -n -P -p `pgrep consul` <br />
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME<br />
consul 24079 hvn cwd DIR 259,6 4096 5641002 /home/hvn/me/consullab<br />
consul 24079 hvn rtd DIR 259,4 4096 2 /<br />
consul 24079 hvn txt REG 259,6 108053286 5641005 /home/hvn/me/consullab/consul<br />
consul 24079 hvn 0u CHR 136,1 0t0 4 /dev/pts/1<br />
consul 24079 hvn 1u CHR 136,1 0t0 4 /dev/pts/1<br />
consul 24079 hvn 2u CHR 136,1 0t0 4 /dev/pts/1<br />
consul 24079 hvn 3u IPv4 1340781 0t0 TCP 127.0.0.1:8300 (LISTEN)<br />
consul 24079 hvn 4u a_inode 0,13 0 11931 [eventpoll]<br />
consul 24079 hvn 5u IPv4 1340782 0t0 TCP 127.0.0.1:8302 (LISTEN)<br />
consul 24079 hvn 6u IPv4 1340783 0t0 UDP 127.0.0.1:8302 <br />
consul 24079 hvn 7u IPv4 1340784 0t0 TCP 127.0.0.1:8301 (LISTEN)<br />
consul 24079 hvn 8u IPv4 1340785 0t0 UDP 127.0.0.1:8301 <br />
consul 24079 hvn 9u IPv4 1340786 0t0 UDP 127.0.0.1:8600 <br />
consul 24079 hvn 10u IPv4 1339562 0t0 TCP 127.0.0.1:8600 (LISTEN)<br />
consul 24079 hvn 11u IPv4 1339564 0t0 TCP 127.0.0.1:8500 (LISTEN)<br />
consul 24079 hvn 12u IPv4 1339566 0t0 TCP 127.0.0.1:8502 (LISTEN)<br />
consul 24079 hvn 13u IPv4 1340787 0t0 TCP 127.0.0.1:42547->127.0.0.1:8300 (ESTABLISHED)<br />
consul 24079 hvn 14u IPv4 1337143 0t0 TCP 127.0.0.1:8300->127.0.0.1:42547 (ESTABLISHED)<br />
```</blockquote>
<br />
<h3>
### Ports</h3>
- TCP 8300: Consul RPC Server / Raft Server<br />
- TCP 8500: WebUI / HTTP API <br />
- TCP 8502: gRPC<br />
- TCP|UDP 8301: LAN <br />
- TCP|UDP 8302: WAN<br />
- TCP|UDP 8600: DNS <br />
<br />
<h3>
### DNS</h3>
<br />
Dùng lệnh `dig` để tra cứu thông tin về service tên `consul` xem nó chạy <br />
ở đâu, dùng port nào:<br />
<br />
<blockquote class="tr_bq">
```<br />
$ dig @127.0.0.1 -p8600 consul.service.consul SRV<br />
<br />
; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> @127.0.0.1 -p8600 consul.service.consul SRV<br />
; (1 server found)<br />
;; global options: +cmd<br />
;; Got answer:<br />
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17727<br />
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 3<br />
;; WARNING: recursion requested but not available<br />
<br />
;; OPT PSEUDOSECTION:<br />
; EDNS: version: 0, flags:; udp: 4096<br />
;; QUESTION SECTION:<br />
;consul.service.consul. IN SRV<br />
<br />
;; ANSWER SECTION:<br />
consul.service.consul. 0 IN SRV 1 1 8300 hvnzen.node.dc1.consul.<br />
<br />
;; ADDITIONAL SECTION:<br />
hvnzen.node.dc1.consul. 0 IN A 127.0.0.1<br />
hvnzen.node.dc1.consul. 0 IN TXT "consul-network-segment="<br />
<br />
;; Query time: 0 msec<br />
;; SERVER: 127.0.0.1#8600(127.0.0.1)<br />
;; WHEN: Tue Jan 07 16:50:41 +07 2020<br />
;; MSG SIZE rcvd: 144<br />
```</blockquote>
<br />
Dòng ANSWER SECTION đã ghi rõ port 8300, ADDITION SECTION ghi địa chỉ IPv4 127.0.0.1<br />
<br />
<br />
<h3>
### HTTP </h3>
<br />
Dùng `curl` truy cập HTTP API của consul xem thông tin về chính service này:<br />
<br />
<blockquote class="tr_bq">
```<br />
$ curl http://localhost:8500/v1/catalog/service/consul?passing<br />
[<br />
{<br />
"ID": "6cf38528-6566-b630-10db-cf7dbc993571",<br />
"Node": "hvnzen",<br />
"Address": "127.0.0.1",<br />
"Datacenter": "dc1",<br />
"TaggedAddresses": {<br />
"lan": "127.0.0.1",<br />
"wan": "127.0.0.1"<br />
},<br />
"NodeMeta": {<br />
"consul-network-segment": ""<br />
},<br />
"ServiceKind": "",<br />
"ServiceID": "consul",<br />
"ServiceName": "consul",<br />
"ServiceTags": [],<br />
"ServiceAddress": "",<br />
"ServiceWeights": {<br />
"Passing": 1,<br />
"Warning": 1<br />
},<br />
"ServiceMeta": {<br />
"raft_version": "3",<br />
"serf_protocol_current": "2",<br />
"serf_protocol_max": "5",<br />
"serf_protocol_min": "1",<br />
"version": "1.6.2"<br />
},<br />
"ServicePort": 8300,<br />
"ServiceEnableTagOverride": false,<br />
"ServiceProxy": {<br />
"MeshGateway": {},<br />
"Expose": {}<br />
},<br />
"ServiceConnect": {},<br />
"CreateIndex": 9,<br />
"ModifyIndex": 9<br />
}<br />
]<br />
]<br />
```</blockquote>
<br />
Cũng thấy Address 127.0.0.1 và ServicePort 8300.<br />
<br />
`?passing` để lọc ra các service pass HealthCheck, với DNS, mặc định <br />
lọc điều kiện này.<br />
<br />
## TODO Phần 2: consul server & agent<br />
<br />
### Hết<br />
<br />
HVN at <a href="https://pymi.vn/">https://pymi.vn</a> and <a href="https://www.familug.org/">https://www.familug.org</a>hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-9863235488555903142020-01-03T22:51:00.000+07:002020-01-03T22:51:12.891+07:00Gỡ các phần mềm cài sẵn trên điện thoại SamSung Galaxy - với UbuntuNếu lỡ có mua một máy điện thoại SamSung, bạn sẽ chịu một cảm giác cực kỳ khó chịu khi máy có cài "giúp sẵn" một đống phần mềm mà thậm chí không có lựa chọn để gỡ (uninstall) trên máy:<br />
- Facebook: yup, không phải ai cùng muốn cài facebook trên điện thoại cả.<br />
- MS Office<br />
- MS LinkedIn<br />
- SamSung Bixby <br />
<br />
thậm chí có cả Zalo hay Lazada mà mình chưa từng dám bấm vào, cũng không có option để gỡ đi.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSkBrUAZjOkhmGI7H7uz6mz7g9RRDD1zQZ6gkCsQQWU9RWs5a0S1ks6R81BT0KpUyjlZDDtCpObvvkGROmv3MA_GwB0svMkGyXgMHXq0dXW8wGKrwNQ1dXXXZlR_RswwCD7VLoRJJTWMg/s1600/20200103_222749.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="995" data-original-width="1068" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSkBrUAZjOkhmGI7H7uz6mz7g9RRDD1zQZ6gkCsQQWU9RWs5a0S1ks6R81BT0KpUyjlZDDtCpObvvkGROmv3MA_GwB0svMkGyXgMHXq0dXW8wGKrwNQ1dXXXZlR_RswwCD7VLoRJJTWMg/s320/20200103_222749.jpg" width="320" /></a></div>
<br />
<br />
Không có sẵn không có nghĩa là không thể, và ta lại phải thò tay vào chọc.<br />
<br />
Trên Ubuntu 18.04, cài adb (<b>Android</b> Debug Bridge) - một phần mềm không thể thiếu của lập trình viên Android, và cài rất dễ dàng qua apt-get:<br />
<br />
<blockquote class="tr_bq">
$ sudo apt-get install -y android-tools-adb android-tools-fastboot</blockquote>
<br />
Cài xong sẽ gõ được lệnh adb:<br />
<a name='more'></a><br />
<blockquote class="tr_bq">
$ adb devices<br />List of devices attached</blockquote>
<br />(trống, không có thiết bị nào).<br />
<br />
Đến đây, mở điện thoại Android ra, bật chế độ USB debugging lên, nếu chưa từng làm, nó sẽ gồm 2 bước:<br />
- Bật chế độ "developer mode": vào Settings -> About phone -> Software Information -> Gõ liên tục vào nút "Build number" sau khoảng 5 lần nó sẽ bật lên.Lúc này trong Settings sẽ xuất hiện 1 mục mới là "Developer options"<br />
- Vào Settings -> Developer options -> USB debugging bật lên.<br />
Cắm điện thoại vào máy và chạy lại lệnh adb trên:<br />
<br />
<blockquote class="tr_bq">
$ adb devices<br />List of devices attached<br />R58M9XYZB device</blockquote>
<br />
Đến đây vào adb shell rồi gõ lệnh như bash shell:<br />
<br />
<blockquote class="tr_bq">
$ adb shell<br />a50s:/ $ pm list packages | wc -l<br />362<br />a50s:/ $ pm list packages | head<br />package:com.samsung.android.provider.filterprovider<br />package:com.sec.android.app.DataCreate<br />package:com.android.cts.priv.ctsshim<br />package:com.sec.android.widgetapp.samsungapps<br />package:com.samsung.android.smartswitchassistant<br />package:com.sec.vsim.ericssonnsds.webapp<br />package:com.sec.android.app.setupwizardlegalprovider<br />package:com.samsung.android.app.galaxyfinder<br />package:com.sec.location.nsflp2<br />package:com.sec.android.app.chromecustomizations</blockquote>
<br />
Dung grep để tìm phần mềm muốn gỡ, ví dụ SamSung browser<br />
<br />
<blockquote class="tr_bq">
a50s:/ $ pm list packages | grep browser package:com.samsung.android.app.sbrowseredge</blockquote>
Gỡ, chạy với quyền user 0 (root)<br />
<blockquote class="tr_bq">
a50s:/ $ pm uninstall --user 0 com.samsung.android.app.sbrowseredge<br />Success</blockquote>
Cũng có thể gỡ hàng loạt bằng cách viết script, do các câu lệnh trong adb shell có thể gõ như câu lệnh bình thường:<br />
<br />
<blockquote class="tr_bq">
$ adb shell pm list packages | wc -l <br />361</blockquote>
Sau đó cài một phần mềm <a href="https://duckduckgo.com/?q=evie+launcher&t=ffab&ia=web" target="_blank">launcher mới như Evie</a> là có một chiếc điện thoại như của mình.<br />
<br />
Tham khảo thêm danh sách các "bloatware" cài sẵn của SamSung trên <a href="https://forum.xda-developers.com/galaxy-s10/how-to/galaxy-s10-s10-debloat-bloatware-t3912073">https://forum.xda-developers.com/galaxy-s10/how-to/galaxy-s10-s10-debloat-bloatware-t3912073</a><br />
<br />
Hết<br />
<br />
HVN @ <a href="https://pymi.vn/">https://pymi.vn</a> and <a href="https://www.familug.org/">https://www.familug.org</a><br />
<br />hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com4tag:blogger.com,1999:blog-7188813081599719537.post-52458278434004678802019-11-28T00:21:00.002+07:002019-12-01T21:04:37.900+07:00zombie - những xác chết biết đi mà không thể kill<h3>
top </h3>
top là câu lệnh để hiển thị các process đang chạy trên một máy tính *NIX.<br />
Trên Windows sử dụng chương trình "Task Manager" để làm nhiệm vụ tương tự. <br />
<br />
top rất dễ dùng, gõ `top` rồi ngồi nhìn màn hình update thông tin mỗi 3s, và bấm q để kết thúc.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHhsbidNYmG2H2EEoOaYIOSfIVuz1mZq7dRaYfhULRzoAPLPprnFdteApN3FX_lu8vwRkiQUgm8695ufpwdjdDbUA7zTMuhIsNbURObn81SxY_BSbSfUOAQ2ZFL0XBpRO0uIKmfllYh6w/s1600/zombie.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1067" data-original-width="800" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHhsbidNYmG2H2EEoOaYIOSfIVuz1mZq7dRaYfhULRzoAPLPprnFdteApN3FX_lu8vwRkiQUgm8695ufpwdjdDbUA7zTMuhIsNbURObn81SxY_BSbSfUOAQ2ZFL0XBpRO0uIKmfllYh6w/s320/zombie.png" width="239" /></a></div>
<br />
<br />
Thế nhưng top cũng là một chương trình chứa rất nhiều thông tin, mà hỏi ra mỗi dòng có nghĩa là gì, cũng đủ để viết vài <a href="https://peteris.rocks/blog/htop/" target="_blank">bài thật dài</a>...<br />
<br />
<b>htop</b> là một lệnh tương tự <b>top</b>, nhưng có giao diện dòng lệnh thân thiện hơn, màu mè hơn. <b>htop</b> không được cài sẵn trên mọi máy tính giống như <b>top</b>, người dùng phải tự cài thêm.<br />
<br />
<blockquote class="tr_bq">
$ whatis top<br />
top (1) - display Linux processes</blockquote>
<br />
<a name='more'></a><br />
<br />
Một cách khác dùng lệnh top ít phổ biến, đó là dùng ở "batch mode" với option -b, khi thêm -n1 , <b>top</b> sẽ có tác dụng tương tự như lệnh <b>ps</b>, chỉ in ra 1 lần.<br />
<br />
<blockquote class="tr_bq">
$ top -bn1 | head <br />
top - 23:16:05 up 1:41, 1 user, load average: 2,91, 2,60, 1,66<br />
Tasks: 284 total, 4 running, 212 sleeping, 0 stopped, 0 zombie<br />
%Cpu(s): 3,9 us, 2,9 sy, 0,0 ni, 93,0 id, 0,1 wa, 0,0 hi, 0,2 si, 0,0 st<br />
KiB Mem : 7975128 total, 4841968 free, 1716268 used, 1416892 buff/cache<br />
KiB Swap: 2097148 total, 2097148 free, 0 used. 5578912 avail Mem <br />
<br />
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND<br />
5564 hvn 20 0 31452 5560 3776 R 100,0 0,1 12:02.90 bash<br />
2781 hvn 20 0 721456 43840 28704 R 87,5 0,5 10:28.96 /usr/lib/gnome-t+<br />
5790 root 20 0 0 0 0 I 31,2 0,0 1:19.12 [kworker/u16:2-e]</blockquote>
<br />
Bỏ qua 5 dòng đầu đầy thông tin tổng quát về OS,<br />
tập trung vào output từ chỗ:<br />
<br />
<blockquote class="tr_bq">
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND<br />
5564 hvn 20 0 31452 5560 3776 R 100,0 0,1 12:02.90 bash</blockquote>
<br />
Các tên cột PID USER .... COMMAND<br />
có giải thích cụ thể trong man 1 top , phần 3. FIELDS / Columns<br />
<br />
Bài này sẽ không giải thích các cột khác mà chỉ nói tới cột có chữ <span style="color: red;"><b>S - Status</b></span><br />
<br />
Trích <b>man 1 ps</b><br />
<blockquote class="tr_bq">
D uninterruptible sleep (usually IO)<br />
R running or runnable (on run queue)<br />
S interruptible sleep (waiting for an event to complete)<br />
T stopped by job control signal<br />
t stopped by debugger during the tracing<br />
W paging (not valid since the 2.6.xx kernel)<br />
X dead (should never be seen)<br />
Z defunct ("zombie") process, terminated but not reaped by its<br />
parent</blockquote>
<br />
Thuật ngữ sử dụng ở đây có khác một chút, mỗi dòng trong output này được gọi là 1 task (ở trên ta dùng từ process). Cột S hiển thị status (trạng thái) của task.<br />
<br />
Trên máy thực hiện bài viết này, các task chỉ ở 1 trong 3 trạng thái:<br />
<br />
<blockquote class="tr_bq">
$ top -bn1 | tail +10 | awk '{print $8}'| sort -nr | uniq -c<br />
215 S<br />
1 R<br />
66 I</blockquote>
<br />
S == sleeping<br />
R == running<br />
<a href="https://unix.stackexchange.com/questions/462098/unrecognized-process-state-output-in-ps-command/462102#462102" target="_blank">I == idle </a><br />
<br />
D == uninterruptible sleep (usually IO) - thường là đang đọc ghi đĩa cứng. Ví dụ: khi chạy lệnh cp copy file sẽ thấy process cp này có state D <br />
<br />
Running nên được hiểu là sẵn sàng để chạy, task đang nằm trong Linux kernel run-queue.<br />
<br />
Z - zombie là một status đáng chú ý hơn cả ở đây, dù nó không xuất hiện trong ví dụ. Khi một task có status là Z, ta gọi process này là zombie process.<br />
<br />
<h3>
zombie process là gì</h3>
Trích <a href="http://manpages.ubuntu.com/manpages/bionic/man2/wait.2.html" target="_blank">man 2 wait</a><br />
<br />
<blockquote>
A child that terminates, but has not been waited for becomes a zombie. The kernel maintains a minimal set of information about the zombie process (PID, termination status, resource usage information) in order to allow the parent to later perform a wait to obtain information about the child. As long as a zombie is not removed from the system via a wait, it will consume a slot in the kernel process table, and if this table fills, it will not be possible to create further processes. If a parent process terminates, then its zombie children (if any) are adopted by init(1), (or by the nearest subreaper process as defined through the use of the prctl(2) PR_SET_CHILD_SUBREAPER operation); init(1) automatically performs a wait to remove the zombies.</blockquote>
zombie process (cũng gọi là defunct process) là một process đã kết thúc, đã xong (có thể hiểu là đã chết), nhưng vẫn xuất hiện trong process table (xem bằng lệnh top hay ps). Thuật ngữ zombie ở đây ám chỉ việc process đã chết, nhưng ta vẫn thấy nó tồn tại trong output các câu lệnh top, ps.<br />
Zombie process tồn tại trong một khoảng thời gian ngắn (vài giây) là chuyện hoàn toàn bình thường.<br />
<h4>
Vậy làm sao để xóa nó khỏi process table?</h4>
hay hỏi theo ngôn ngữ thông dụng:<br />
<h4>
làm sao "<a href="https://www.familug.org/2019/11/kill.html" target="_blank">kill</a>" zombie process?</h4>
<br />
Ta không thể "<a href="https://www.familug.org/2019/11/kill.html" target="_blank">kill</a>" zombie process, theo nghĩa bắt buộc 1 chương trình đang chạy phải dừng lại <a href="https://www.familug.org/2019/11/kill.html" target="_blank">một cách lịch sự (SIGTERM) hay ép buộc (SIGKILL)</a>. Bởi zombie process đã dừng lại rồi, đã chết rồi, không thể giết (kill) được nữa.<br />
<br />
<h4>
Tại sao lại phải xóa zombie process?</h4>
Mặc dù đã kết thúc và không còn chiếm bộ nhớ hay CPU nữa, nhưng zombie process chiếm 1 chỗ (entry) trong process table - một số PID. Đây là một tài nguyên hữu hạn, và khi hết số, hệ điều hành sẽ không thể cấp được cho các chương trình cần chạy.<br />
<br />
<h4>
Giới hạn số PID là bao nhiêu?</h4>
Giá trị này có thể thay đổi được, mặc định trên Ubuntu 1804 x64 có giá trị mặc định là:<br />
<blockquote class="tr_bq">
$ cat /proc/sys/kernel/pid_max<br />
32768</blockquote>
<br />
Số này hoàn toàn không phải là lớn, trên những server phục vụ hàng trăm ngàn người dùng cùng lúc.<br />
<br />
<h4>
Làm thế nào để xóa zombie process?</h4>
Trước tiên cần hiểu tại sao lại có zombie process: trong một chương trình chạy multiprocess, process chính (parent) sẽ tạo ra các process khác (children).<br />
Để quản lý các children, parent sẽ phải theo dõi Status của chúng. Việc theo dõi này được thực hiện bằng cách theo dõi các thay đổi của Status của children thông qua systemcall `wait`. Nếu trong code của parent "quên" không wait children, thì khi children chạy xong (terminated), nó sẽ ở trạng thái Z. Chỉ khi parent gọi wait để kiểm tra Status của children, lúc đó kernel mới reap (gặt hái) zombie process, xóa khỏi kernel process table.<br />
Một chương trình để lại zombie process được xem là chương trình có bug cần fix.<br />
<br />
Để dọn dẹp zombie process, ta cần đi tìm parent của nó để xử lý:<br />
- Gửi <span style="color: #3d85c6;"><b>SIGCHLD</b></span> đến parent process để nó hiểu cần đi tìm children và wait<br />
- Nếu không có tác dụng, có thể xem xét kill parent process (gửi SIGTERM hay SIGKILL). Sau khi parent process đã kết thúc, các children process trở thành các "<span style="color: red;"><b>orphan</b></span>" (mồ côi) process, và được system process manager (init - PID 1) nhận làm con nuôi. System manager là một parent có trách nhiệm, nó sẽ gọi "wait" để kernel xóa các zombie process khỏi kernel process table.<br />
- Nếu zombie có parent process ID (PPID) là 1 - tức init, mà không được dọn dẹp thì đây là bug của hệ điều hành (cụ thể là kernel, ví dụ Linux).<br />
<br />
Dùng lệnh ps xau cũng cho ra output tương tự như top -bn1:<br />
<blockquote class="tr_bq">
$ ps xau | head -n2<br />
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND<br />
root 1 0.0 0.1 78052 9268 ? Ss 21:34 0:05 /lib/systemd/systemd --system --deserialize 20</blockquote>
<br />
Thực hiện trên:<br />
<blockquote class="tr_bq">
$ lsb_release -a ; uname -a<br />
No LSB modules are available.<br />
Distributor ID: Ubuntu<br />
Description: Ubuntu 18.04.3 LTS<br />
Release: 18.04<br />
Codename: bionic<br />
Linux hvnzen 5.0.0-32-generic #34~18.04.2-Ubuntu SMP Thu Oct 10 10:36:02 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux</blockquote>
<br />
Hết.<br />
HVN at https://pymi.vn and http://www.familug.org.<br />
<br />
<br />
Còn đây là ca khúc Zombie:<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="344" src="https://www.youtube.com/embed/6Ejga4kJUts" width="459"></iframe>
hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-44458637660246085672019-11-16T00:14:00.004+07:002019-11-16T00:31:36.691+07:00kill - signal trên OS: SIGINT vs SIGTERM vs SIGKILL vs SIGHUP<h2>
kill</h2>
Lệnh kill không lạ gì với người dùng Linux/MacOS. Ngay từ những ngày đầu học dùng dòng lệnh trên các hệ điều hành này, người dùng đã biết dùng kill để "giết" một chương trình đang chạy (một process).<br />
<br />
Câu lệnh này quá cơ bản, dẫn đến dễ bị bỏ qua việc tìm hiểu nó kỹ càng.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbjK5ODk6YxsdpXr1EKpVjbvPuAi6K2S03whwLjkuZg1bxbdc-iNvNlMPLR_ItqUqd3UnCOUz2zlNF2ZAXwhmV75iGEpMz6cOll5ePRNu30yYAk0oVAGhf413AwBk1scSNISAEf0Lgp10/s1600/joker.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="800" data-original-width="518" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbjK5ODk6YxsdpXr1EKpVjbvPuAi6K2S03whwLjkuZg1bxbdc-iNvNlMPLR_ItqUqd3UnCOUz2zlNF2ZAXwhmV75iGEpMz6cOll5ePRNu30yYAk0oVAGhf413AwBk1scSNISAEf0Lgp10/s320/joker.jpg" width="207" /></a></div>
<br />
<br />
Để kill 1 chương trình đang chay, người dùng sẽ tìm PID (process ID - một số nguyên dương được hệ điều hành cấp cho chương trình lúc bắt đầu chạy) bằng lệnh:<br />
<blockquote class="tr_bq">
ps xau | grep tên_chương_trình</blockquote>
<br />
Ví dụ:<br />
<br />
<blockquote class="tr_bq">
$ ps xau | grep -i firefox<br />
hvn 12537 8.9 4.4 3202268 358612 ? Sl 23:17 0:47 /home/hvn/Downloads/firefox/firefox-bin</blockquote>
<br />
<br />
12537 chính là số PID. Sau đó gõ lệnh:<br />
<br />
<blockquote class="tr_bq">
kill 12537</blockquote>
<br />
và nếu chương trình vẫn chạy, thì dùng biện pháp mạnh:<br />
<br />
<blockquote class="tr_bq">
kill -9 12537 </blockquote>
<br />
sẽ đảm bảo nhanh, gọn, sạch sẽ, xong.<br />
<br />
Nhưng bên dưới là cả một chủ đề không quá đơn giản đến vậy.<br />
<br />
<a name='more'></a><br />
<blockquote class="tr_bq">
$ whatis kill; dpkg -S `which kill`<br />
kill (1) - send a signal to a process<br />
kill (2) - send signal to a process<br />
procps: /bin/kill</blockquote>
<br />
Vậy kill không phải là để "giết chết 1 process" mà thực ra nó gửi 1 signal (tín hiệu) tới một process.<br />
<br />
Trong Description của <span style="color: #3d85c6;"><b>man 1 kill</b></span> có ghi:<br />
<br />
<blockquote class="tr_bq">
The default signal for kill is TERM. Use -l or -L to list available signals. Particularly useful signals include HUP, INT, KILL, STOP, CONT, and 0. Alternate signals may be specified in three ways: -9, -SIGKILL or -KILL. Negative PID values may be used to choose whole process groups; see the PGID column in ps command output. A PID of -1 is special; it indicates all processes except the kill process itself and init.</blockquote>
<br />
Thay vì kiểu gõ dùng số như kill -9, có thể gõ rõ tên signal ra, như<span style="color: red;"><b> kill -SIGKILL </b></span><br />
<br />
Các signal phổ biến<span style="color: red;"><b> TERM KILL HUP INT </b></span><br />
- Khi chỉ gõ kill PID, kill sẽ mặc định gửi đi signal TERM (terminate) hay gọi là SIGTERM.<br />
- kill -9 PID sẽ gửi đi SIGKILL tới PID<br />
- SIGHUP (hang up) được gửi tới process khi process điều khiển bị đứt/ngắt (ví dụ đang SSH vào chạy lệnh thì đứt mạng)<br />
- SIGINT (interrupt) được gửi khi user đang chạy chương trình thì bấm Ctrl-C<br />
<br />
<h2>
SIGTERM khác gì SIGKILL?</h2>
> Unlike <code>SIGKILL</code>, this signal can be blocked,
handled, and ignored. It is the normal way to politely ask a program to
terminate.<br />
https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html<br />
<br />
Process đang chạy khi nhận được SIGTERM sẽ hiểu là được yêu cầu kết thúc một cách lịch sự, chương trình thường sẽ dọn dẹp / đóng các tài nguyên đang sử dụng rồi thoát. Lập trình viên cũng có thể làm gì tùy ý, hoặc không làm gì cả. Yêu cầu là việc của ông còn làm hay không thì không phải việc của ông.<br />
Đây là lý do nhiều khi kill PID không đủ, phải kill -9<br />
<br />
<h3>
SIGKILL</h3>
> The <code>SIGKILL</code> signal is used to cause immediate program termination.
It cannot be handled or ignored, and is therefore always fatal. It is
also not possible to block this signal.<br />
> In fact, if <code>SIGKILL</code> fails to terminate a process, that by itself
constitutes an operating system bug which you should report.<br />
<br />
<br />
Người dùng gửi SIGKILL để kết thúc chương trình ngay lập tức, chữ KILL ở đây ám chỉ process sẽ không thể làm gì được, thậm chí là không biết. SIGKILL được hệ điều hành xử lý và nếu nó không kết thúc chương trình ngay, sẽ được xem như là một bug cần sửa của hệ điều hành.<br />
<br />
<h3>
SIGINT</h3>
Khi chạy chương trình bằng dòng lệnh, người dùng bấm Ctrl-C để gửi SIGINT tới chương trình, chương trình sẽ nhận được signal này và tùy ý xử lý, thường là sẽ dừng chương trình lại giống SIGTERM. Có thể dùng kill -2 hay kill -SIGINT để gửi signal này.<br />
<br />
<h3>
Làm thế nào để tiếp tục chạy 1 chương trình mà có thể ngắt kết nối SSH?</h3>
Vấn đề này thường được "vượt qua" bằng cách dùng chương trình tmux/screen sau khi SSH vào máy khác, dù mạng sau đó có đứt, thì chương trình vẫn chạy bình thường.<br />
Một giải pháp CHỦ ĐỘNG hơn, là dùng câu lệnh nohup. Ví dụ:<br />
nohup sleep 1000<br />
để vẫn tiếp chạy câu lệnh sleep 1000 dù mạng có đứt khi SSH.<br />
Nhưng nếu quên vào tmux, screen, quên cả nohup thì làm sao? Toang rồi!<br />
Chưa! nếu dùng bash, có 1 câu lệnh builtin tên là disown sẽ giúp "bảo kê" chương trình đang chạy khỏi SIGHUP.<br />
<br />
Trích man 1 bash<br />
<br />
<blockquote>
The shell exits by default upon receipt of a SIGHUP. Before exiting, an interactive shell resends the SIGHUP to all jobs, running or stopped. Stopped jobs are sent SIGCONT to ensure that they receive the SIGHUP. To prevent the shell from sending the signal to a particular job, it should be removed from the jobs table with the disown builtin (see SHELL BUILTIN COMMANDS below) or marked to not receive SIGHUP using disown -h.</blockquote>
<br />
Các signal - không hề đơn giản, có tới 69-5 signal!!!<br />
<br />
<blockquote class="tr_bq">
$ kill -L</blockquote>
<blockquote>
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP<br />
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1<br />
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM<br />
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP<br />
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ<br />
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR<br />
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3<br />
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8<br />
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13<br />
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12<br />
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7<br />
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2<br />
63) SIGRTMAX-1 64) SIGRTMAX </blockquote>
<br />
<br />
Thực hiện trên<br />
<blockquote class="tr_bq">
$ lsb_release -a; dpkg-query --show procps<br />
No LSB modules are available.<br />
Distributor ID: Ubuntu<br />
Description: Ubuntu 18.04.3 LTS<br />
Release: 18.04<br />
Codename: bionic<br />
procps 2:3.3.12-3ubuntu1.1</blockquote>
<br />
Tham khảo:<br />
- <a href="http://manpages.ubuntu.com/manpages/bionic/man7/signal.7.html" target="_blank">man 7 signal</a><br />
- man 1 kill<br />
- https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html<br />
<br />
<br />
Hết.<br />
HVN at https://pymi.vn and https://www.familug.org.
hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-18301438642638614242019-05-25T14:43:00.002+07:002019-05-26T14:45:31.925+07:00Để trở thành DevOps EngineerDevOps là một từ khóa công nghệ cực hot trong khoảng 2015-2018, đánh dấu<br />
sự thay đổi về quy trình phát triển phần mềm trên toàn Trái Đất. Tại thời điểm<br />
viết bài (2019-05), vị trí này đã có mặt ở hầu hết các công ty công nghệ tại <br />
Việt Nam.<br />
<br />
Bài viết này giải thích các khái niệm liên quan tới DevOps, đưa ra một giáo <br />
trình để trở thành DevOps Engineer.<br />
<h3>
## DevOps là gì </h3>
DevOps không có định nghĩa chính thức, cũng không có định nghĩa rõ ràng, nó <br />
là một khái niệm mới tự dưng xuất hiện, chưa từng có trước đây.<br />
Cách hiểu phổ biến nhất, đây là một "cách/quy trình phát triển và vận hành phần mềm".<br />
Được sinh ra từ việc khắc nhập 2 chữ: <span style="color: red;"><b>DEV</b></span>elopment (phát triển) - <span style="color: red;"><b>OP</b></span>eration<span style="color: red;"><b>S</b></span> (vận hành).<br />
<br />
<h4>
### Quy trình phát triển phần mềm truyền thống</h4>
Lập trình viên code, code xong chuyển qua cho SysAdmin cài đặt (deploy), QA sẽ<br />
tham gia kiểm tra chất lượng, nếu OK, SysAdmins sẽ mang đi deploy trên hệ thống<br />
chạy thật, nếu gặp lỗi / report bởi người dùng, SysAdmin sẽ chuyển lại cho <br />
developer sửa. Vòng lặp quy trình này thường mất nhiều thời gian trong tất cả<br />
các khâu, tối thiểu mỗi khâu mất 1 tuần.<br />
<br />
Dev ---> SysAdmin deploy nội bộ ---> QA ---> Sysadmin deploy ----> Người dùng.<br />
^-------------------------------------|--------------------------------|<br />
<br />
Chú ý ở mô hình này, xảy ra một sự xung đột về lợi ích: Dev luôn muốn các tính<br />
năng, code của mình được đưa tới người dùng nhanh nhất. SysAdmin luôn muốn<br />
hệ thống ổn định nên sẽ không muốn mang code mới đi chạy (vì code mới luôn <br />
có bug).<br />
<br />
<h4>
### Quy trình phát triển theo DevOps</h4>
<br />
Dev ---> DevOps --> QA ---> Người dùng<br />
^----------|---------|----------|<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP_OfBzTC5OKbztn0Uf_f8Pu0h0qpRFdjBTj1DhKS_wQ693RPI81G0kXFnnjty_eHFOs5w9dZ0ZBR0h1k-oa7N5uMTWWfOMCB42qwihXd4v92cFJXkE-2torVpNj3nDwPcSQyk1PL0x9w/s1600/devops.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="768" data-original-width="1280" height="192" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP_OfBzTC5OKbztn0Uf_f8Pu0h0qpRFdjBTj1DhKS_wQ693RPI81G0kXFnnjty_eHFOs5w9dZ0ZBR0h1k-oa7N5uMTWWfOMCB42qwihXd4v92cFJXkE-2torVpNj3nDwPcSQyk1PL0x9w/s320/devops.jpg" width="320" /></a></div>
<br />
<a name='more'></a><br />
<br />
Trong mô hình này, DevOps engineer thường thay thế SysAdmin truyền thống, <br />
họ thường là các SysAdmin, nhưng có khả năng viết code tốt, sử dụng các phần mềm,<br />
tích hợp chúng với hệ thống đã có để tự động hóa các quy trình.<br />
<br />
Một chú ý quan trọng là: DevOps engineer thì phải biết code, nhưng không phải là<br />
người code sản phẩm (không thay thế cho dev), mà code để tích hợp và tự động hóa<br />
vận hành. Ở Việt Nam, nhiều công ty (lớn) học đòi làm DevOps,<br />
cũng đăng tuyển DevOps<br />
engineer, nhưng yêu cầu code sản phẩm, kiêm kèm vận hành, mong tuyển 1 làm 2,<br />
trả lương 01.<br />
<br />
<br />
<h2>
## DevOps là làm những gì</h2>
Cần phân biệt rõ ràng, DevOps không phải là 1 chức danh, nó là một "quy trình",<br />
một "cách thức". Kỹ sư thực hiện DevOps gọi là DevOps engineer.<br />
<br />
Quy trình DevOps để phát triển 1 trang web thường bao gồm những thứ sau:<br />
- Code phải được lưu trữ tập trung (sử dụng 1 VCS, ví dụ như Git, Mercurial), thường phổ<br />
biến với việc dùng GitHub, GitLab, BitBucket. DevOps Engineer phải biết cách cài<br />
đặt (GitLab), vận hành hay ít nhất là quản lý các công cụ này. <br />
- Các thay đổi code cần được review - sử dụng GitLab Merge Request, GitHub Pull<br />
Request, hay Gerrit CL<br />
- Code sau khi review được merge vào, có một hệ thống sẽ chạy các chương trình <br />
kiểm tra code mới / code cũ (test). Các chương trình này gọi là CI (continuous integration), phổ biến với các phần mềm như Jenkins, GitLab CI, TravisCI, <br />
CircleCI, DroneCI... Đồng thời, việc phát triển phần mềm có thêm yêu cầu phải viết test<br />
(unittest, integration test, end2end test...). Công đoạn này làm tăng tính đúng <br />
đắn của code, giảm thiểu việc test phần mềm thủ công, QA... (giảm, chứ không<br />
cắt hoàn toàn).<br />
- Sau khi code đã được test, được đóng lại thành "sản phẩm" sẵn sàng để mang đi deploy. Sản phẩm này có thể đơn giản là code (như python) được đánh tag / version, hay các artifact thu được sau khi build (như Java). <br />
- Sản phẩm được mang deploy trên một (hoặc 2 3) môi trường nội bộ, thường gọi là staging, hay QA environment, việc này cần làm liên tục, tự động ngay sau khi <br />
các bước trước đã hoàn thành, nên thường sử dụng các phần mềm gọi là CD <br />
(continuous delivery), như Spinnaker, GoCD, Jenkins... kết hợp với các Configuration<br />
Management tool để cài đặt phần mềm/ quản lý file cấu hình như <a href="https://www.familug.org/2015/06/saltstack-chao-muoi-em-la-ai.html" target="_blank">SaltStack</a>, Chef, Puppet,<br />
Ansible. Giai đoạn này thường là phức tạp nhất, đòi hỏi các chương trình test <br />
kiểm tra phần mềm "như thật", sử dụng cả test thủ công, load test xem hiệu năng<br />
có đảm bảo, etc... các hệ thống monitor/logging cũng được cài đặt trên hệ thống <br />
này để theo dõi quy trình. <br />
- Nếu giai đoạn trên thành công, sản phẩm sẽ được đưa tới công đoạn cuối cùng:<br />
production deploy, tức tương tự như bước trên, nhưng thực hiện trên môi trường chạy thật. Giai đoạn này khá nhạy cảm, bởi dù<br />
có chạy trăm, ngàn test, cũng không đảm bảo được rằng hệ thống sẽ không có bug <br />
gì. Vậy nên quy trình này đòi hỏi: khả năng quay lại phiên bản cũ nếu có lỗi gì<br />
(gọi là rollback), các chiến thuật deploy để chỉ ảnh hưởng tới một phần người dùng<br />
(blue/green, canary). Hệ thống monitor và kỹ sư phải trực tiếp theo dõi, sử dụng<br />
các công cụ nhận biết sự bất thường (abnormal detection), ví dụ lượng người dùng<br />
giảm, lượng response code 500 tăng ... hay dùng mắt thường để theo dõi các đồ thị.<br />
Hệ thống logging/alert/metric monitor đóng vai tròn quan trọng trong giai đoạn này<br />
thường sử dụng các phần mềm như: Graylog2, ELK, TICK stack, Grafana, Prometheus, <br />
... hay thậm chí là Nagios cổ xưa, gì cũng được, có còn hơn không.<br />
<br />
Quy trình nói trên, rõ ràng không phải là một quy trình đơn giản, đòi hỏi phải<br />
sử dụng các phần mềm chuyên dụng, viết các đoạn code tích hợp các phần mềm lại<br />
với nhau thành một quy trình thống nhất, linh hoạt, đòi hỏi DevOps engineer<br />
phải có nhiều kỹ năng không hề tầm thường (vậy nên lương thường rất cao).<br />
<br />
<h4>
### Big note</h4>
Quy trình nói trên, rõ ràng không thể chỉ do một (nhóm) người mà làm được,<br />
nó đòi hỏi có sự hỗ trợ, thay đổi tư duy, văn hóa, quy trình ở toàn bộ phòng ban, thậm chí cả công ty. <br />
<br />
<h3>
### Các kỹ năng, tool cho DevOps engineer</h3>
- Kỹ năng cài đặt, bảo trì, cấu hình, vận hành phần mềm trên server (thường là Linux, vd Ubuntu).<br />
Hay nói cách khác, DevOps engineer cần phải <a href="http://www.familug.org/2015/01/e-tro-thanh-linux-sysadmin.html" target="_blank">là một sysadmin</a> trước.<br />
HOẶC: nếu công ty nhiều tiền/startup được hỗ trợ thì đi dùng các dịch vụ có sẵn,<br />
lúc đó giảm bớt được kỹ năng cài đặt,<br />
chỉ còn phải cấu hình cho phù hợp với nhu cầu. <br />
- Kỹ năng code (viết script): bash là yêu cầu tối thiểu phải có, thành thạo CLI,<br />
tuyệt nhất là thành thạo thêm một ngôn ngữ như <a href="https://www.familug.org/2011/04/learning-python.html" target="_blank">Python</a>, Ruby hay Golang.<br />
- Kỹ năng troubleshooting: mọi thứ không bao giờ hoạt động trơn chu như ta mong<br />
muốn, luôn cần thành thạo các công cụ troubleshooting như đọc log, strace, lsof,<br />
tcpdump hay wireshark... và khả năng biết Google tìm kiếm.<br />
- Kỹ năng tự học/đọc tài liệu: với lượng công cụ khổng lồ nói trên, không ai có<br />
thể đi học đâu đó rồi về mà làm ngay được, bạn cần phải có khả năng tự tìm hiểu, tự đọc tài liệu.<br />
- Thành thạo một CM: <a href="https://www.familug.org/2015/06/saltstack-chao-muoi-em-la-ai.html" target="_blank">SaltStack</a>, Ansible, Chef hay Puppet<br />
- Cài đặt cấu hình CI/CD: thành thạo 1 phần mềm như Jenkins hay GitLabCI<br />
- Sử dụng 1 công cụ monitor log (Graylog2/ELK) và metric monitor (Grafana, Prometheus hay TICK)<br />
- Sử dụng 1 public cloud (AWS, Google Cloud, Azure, Digital Ocean, Linode, etc...) một cách thủ công và tự động với tool (pulumi, terraform, cloudformation, salt-cloud,...) và tự viết script (gọi cloud API, boto3, azure API ...) <br />
<br />
Trên đây là các yêu cầu cứng và dễ dàng biến đổi (VD công ty yêu cầu Ansible mà <br />
thành thạo SaltStack hay Chef thì coi là tương đương, chỉ mất 1 2 tháng để chuyển<br />
đổi), còn yêu cầu CỤ THỂ, thì tùy thuộc vào từng yêu cầu<br />
của từng công ty (tốt nhất là nếu bạn thích làm ở công ty nào thì mở Job Description tuyển dụng của công ty đó ra mà xem).<br />
<br />
Các yêu cầu như Terraform, CloudFormation, Kubernetes, Docker... là các yêu cầu mềm, có thì<br />
tốt, không có thì vào học mất 1 2 tháng là làm okie.<br />
<br />
<h4>
### Làm sao để kiếm việc</h4>
Theo mô tả công việc ở trên, DevOps engineer sờ vào toàn bộ công đoạn phát triển phần mềm, chạm tới cả hệ thống sản phẩm mà khách hàng sử dụng - tức là tiền - tức là quan trọng - tức là đòi hỏi phải có kinh nghiệm chứ không ai dại đưa hệ thống kiếm tỷ đồng hàng ngày vào tay một người chưa biết gì, đang tập sự.<br />
<br />
DevOps engineer không tự sinh ra, không tự mất đi, chỉ chuyển từ công ty này sang công ty khác, từ vị trí SysAdmin hay Backend Dev qua. Vì vậy để kiếm một vị trí này, bạn có thể nhắm tới các startup nhỏ, hệ thống chưa có gì, hoặc nhảy sang từ SysAdmin hay backend dev.<br />
<br />
Hoặc nhiều khi do hên xui, cứ lấy JD muốn làm, nộp vào, phỏng vấn, không ai biết trước được chữ ngờ!<br />
<h4>
### Note cuối bài</h4>
DevOps không phải khái niệm được định nghĩa rõ ràng, đúng sai, vậy nên nhỡ công<br />
ty bạn làm khác với công ty khác, không có nghĩa là bạn hơn hay kém, hay đúng<br />
hay sai gì ở đây.<br />
<br />
<h4>
### Tham khảo </h4>
- https://martinfowler.com/bliki/DevOpsCulture.html<br />
- https://aws.amazon.com/devops/what-is-devops/<br />
<br />
Bài viết được viết để giải đáp thắc mắc của một học viên Python tại <a href="https://pymi.vn/" target="_blank">PyMi</a> khóa HCM 1903.<br />
<br />
Hết.<br />
HVN at https://pymi.vn and https://www.familug.org.hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com4tag:blogger.com,1999:blog-7188813081599719537.post-76801907836950891402019-03-17T14:27:00.000+07:002019-05-01T08:27:48.489+07:00Tăng tốc Django API Service tới 90% với cacheops<h3>
1. Code chạy đúng là được rồi mà?</h3>
Code chạy đúng thôi là chưa đủ, luôn phải tối ưu thêm về mặt tốc độ.<br />
<br />
Bạn có thể viết code đẹp như tranh, nhưng nếu một request của khách mất đến 10s để xử lý, thì 99% là khách hàng của bạn sẽ bỏ đi ngay lập tức (trừ khi bạn là trang .gov hoặc khi bạn cần đăng ký tín chỉ).<br />
<br />
Ok, bạn tiếp tục "outsource" những phần xử lý nặng nề cho <a href="http://www.celeryproject.org/">Celery</a> hoặc <a href="http://python-rq.org/">RQ</a> để khách đỡ phải nhìn màn hình trắng. Nhưng kể cả vậy thì nó cũng không làm vấn đề biến mất. Nó chỉ giúp khách của bạn biết rằng ở bên dưới bạn vẫn đang vật lộn xử lý chứ không "chết hẳn". Thay vì màn hình trắng thì giờ là hình xoay xoay.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://media2.giphy.com/media/l3nWhI38IWDofyDrW/giphy.gif?cid=3640f6095c8de8ff532e35662e5ee6e1" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="480" data-original-width="480" height="320" src="https://media2.giphy.com/media/l3nWhI38IWDofyDrW/giphy.gif?cid=3640f6095c8de8ff532e35662e5ee6e1" width="320" /></a></div>
<br />
Và lúc đó, bạn cần phải "tối ưu" lại code của mình rồi.<br />
<br />
Và sau khi vò đầu bứt tai ngồi đoán, <u>imort pdb; pdb.set_trace()</u> loạn xạ, thêm các kiểu đo đếm thời gian, bạn bó tay. Vì đó không phải cách bạn nên làm để tối ưu code.<br />
<h3>
2. Analysis<a name='more'></a></h3>
<div>
Đầu tiên, ta cần phải <b>phân tích</b> và <b>đo lường</b>. Bạn có thể ngồi đoán mò cả ngày xem code bị nghẽn ở đâu, hoặc dùng một cách khoa học hơn để biết phải sửa chỗ nào.</div>
<div>
<br /></div>
<div>
Và việc đó gọi là `<a href="https://en.wikipedia.org/wiki/Profiling_(computer_programming)">profilling</a>` code. Python có sẵn thư viện để profile, đó là <a href="https://docs.python.org/3.7/library/profile.html">cProfile</a>. Còn Django thì có <a href="https://django-debug-toolbar.readthedocs.io/en/latest/">django-debug-toolbar</a> và Django Rest Framework có <a href="https://github.com/jazzband/django-silk">django-silk</a>.</div>
<div>
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://raw.githubusercontent.com/jazzband/django-silk/master/screenshots/1.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="319" data-original-width="800" height="255" src="https://raw.githubusercontent.com/jazzband/django-silk/master/screenshots/1.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Django Silk</td></tr>
</tbody></table>
<div>
Khi bạn truy cập 1 API, Silk sẽ profile lại các SQL và thời gian thực hiện, số lần thực hiện, được gọi ở dòng nào, hàm nào v.v.. Và nó còn lưu lại rất nhiều các thông tin hữu ích khác nữa liên quan đến request đó.</div>
<div>
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://raw.githubusercontent.com/jazzband/django-silk/master/screenshots/3.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="206" data-original-width="800" height="164" src="https://raw.githubusercontent.com/jazzband/django-silk/master/screenshots/3.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Django silk lưu lại các SQL query</td></tr>
</tbody></table>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://raw.githubusercontent.com/jazzband/django-silk/master/screenshots/5.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="503" data-original-width="800" height="402" src="https://raw.githubusercontent.com/jazzband/django-silk/master/screenshots/5.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">và cả chỗ được gọi nữa</td></tr>
</tbody></table>
<div>
(Lưu ý: bạn chỉ nên setup ở môi trường dev hoặc local, vì thời gian profile và lưu lại vào db nhiều khi còn lâu hơn thời gian code thực thi)</div>
<div>
<br /></div>
<h3>
3. Tối ưu code</h3>
<div>
Sau khi bạn biết được query/đoạn code nào bị lặp nhiều nhất, hay chạy lâu nhất, sai logic hay không, thì bạn có thể chỉnh lại và xem có tăng được chút hiệu quả nào không (thường là sẽ có, không ít thì nhiều).</div>
<div>
<br /></div>
<div>
Ví dụ như trường hợp của mình, sau khi phân tích và tối ưu hoá lại, kết quả là khá đáng kể.</div>
<div>
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZuYbbMq1aNYv32elboKorWO4LV-hu7LEQqOcjfS9m1dfX9bQ4MU7dYmTBt5grLd3-xltaaDizPPSPImGS3JenaN_nxqDWFnA0iV5cPYjwGyt21NjOImwWP5j1H3T5biVWTRlsoy9cDDI/s1600/wallet_optimize.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="388" data-original-width="810" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZuYbbMq1aNYv32elboKorWO4LV-hu7LEQqOcjfS9m1dfX9bQ4MU7dYmTBt5grLd3-xltaaDizPPSPImGS3JenaN_nxqDWFnA0iV5cPYjwGyt21NjOImwWP5j1H3T5biVWTRlsoy9cDDI/s400/wallet_optimize.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">phần màu cam nhỏ nhỏ là thời gian của Django Silk khi profilling.<br />
210 query là đã tối ưu 1 lần, trước đó khoảng gần 300 query!</td></tr>
</tbody></table>
<div>
Nhưng thế vẫn là chưa đủ. Vì sẽ đến 1 thời điểm mà việc bạn tối ưu code, lại dẫn đến việc tăng thời gian thực hiện, hoặc việc thực hiện thay đổi sẽ tốn nhiều thời gian và công sức, dẫn đến khả năng gặp lỗi logic. Sự đánh đổi ở đây lớn hơn lợi ích nó mang lại, vì vậy không đáng để làm.</div>
<div>
<br /></div>
<div>
Tỷ như ở đây mình tối ưu được số query, nhưng lại làm tăng thời gian thực hiện lên.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-AnG3BwFbjh8WVdy0sPwBUydNiOFAd1ohU0_DTAPHPjD7n9dwAPbaxT5FfdVhB_SlOnD_8hKVv5L5ndTuAA-51cpYuD3XRf-7_woAu54bAmnkgQ_PRHM_J1WfLSlO21ZqMviYx4De_hA/s1600/wallet_2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="332" data-original-width="376" height="282" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-AnG3BwFbjh8WVdy0sPwBUydNiOFAd1ohU0_DTAPHPjD7n9dwAPbaxT5FfdVhB_SlOnD_8hKVv5L5ndTuAA-51cpYuD3XRf-7_woAu54bAmnkgQ_PRHM_J1WfLSlO21ZqMviYx4De_hA/s320/wallet_2.png" width="320" /></a></div>
<div>
<br /></div>
<div>
Vì thế bạn nên tối ưu theo một hướng khác nữa, đó là Cache.</div>
<div>
<br /></div>
<h3>
4. Cache</h3>
<div>
Cache có nghĩa là thay vì mỗi lần client request, bạn lại thực hiện lại đầy đủ các bước và logic, thì bạn sẽ lưu lại kết quả của request này, lần sau ai đó request đến thì bạn chỉ việc ung dung lấy ra và đưa lại cho họ mà không cần phải tính toán lại. Điều này làm giảm tải rất nhiều cho server và tăng một tốc độ đáng kể. (Và thường sẽ sử dụng <a href="https://redis.io/">Redis</a> hay <a href="https://memcached.org/">Memcache</a> để lưu)</div>
<div>
<br /></div>
<div>
Nhưng nếu database của bạn thay đổi, mà bạn không xử lý việc đó để update cache, thì khả năng rất cao là dù bạn nhanh, nhưng bạn sẽ sai. Vì thế bạn nên dùng một thư viện để quản lý việc cache, trừ khi đó là những chỗ rất đặc biệt phải tự code.</div>
<div>
<br /></div>
<div>
Django có sẵn cả một <a href="https://docs.djangoproject.com/en/2.1/topics/cache/">thư viện</a> giúp bạn làm việc đó. Nhưng nếu bạn dùng Django Rest, bạn nên ngó qua thư viện <a href="https://github.com/Suor/django-cacheops">django-cacheops</a> (django-cacheops chỉ support Redis).</div>
<div>
<br /></div>
<div>
Thư viện này giúp chúng ta cache lại các queryset, theo dõi sự thay đổi của database để không bị sai sót (nhưng bạn vẫn cần cực kỳ cẩn thận và theo dõi, test lại đầy đủ các case để cân nhắc chỗ nào cần cache, chỗ nào không).</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGJzkSbI4lEVqRMdKSUNRvd_blyelcOtvhwzD38tI9lQWHpZDUCZo9XGQMN8tqq1Qo7atSDPKX3o0m2uBHnBNAs1j93mUqOnZeLdzoUhg02OFP3KNvRGLJUmIUFELX2uXuR8Yr8Gz3xMw/s1600/wallet_3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="346" data-original-width="1138" height="194" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGJzkSbI4lEVqRMdKSUNRvd_blyelcOtvhwzD38tI9lQWHpZDUCZo9XGQMN8tqq1Qo7atSDPKX3o0m2uBHnBNAs1j93mUqOnZeLdzoUhg02OFP3KNvRGLJUmIUFELX2uXuR8Yr8Gz3xMw/s640/wallet_3.png" width="640" /></a></div>
<div>
<br /></div>
<div>
Yep, sau khi setup xong, lần đầu chạy số query đã giảm đáng kể (nhờ cache lại những model ít thay đổi như User, Config v.v..). Lần tiếp theo chạy, số query giảm về còn 0!</div>
<div>
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://memegenerator.net/img/images/300x300/16217424.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="300" data-original-width="300" src="https://memegenerator.net/img/images/300x300/16217424.jpg" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Feeling so good!</td></tr>
</tbody></table>
<div>
<br /></div>
<h3>
5. Kết luận</h3>
<div>
Trước khi tối ưu, mỗi lần user request đến API trên, bên mình mất đến 7 - 8s(!!!) cho một lần load. Còn hiện giờ, thời gian load đã giảm còn khoảng 0.3s. Một sự thoả mãn không hề nhẹ.</div>
<div>
<br /></div>
<div>
Dĩ nhiên, vẫn phải rà soát và test lại từng chỗ một để đảm bảo tính chuẩn xác, và có những đoạn code phải bỏ cache đi vì tần suất thay đổi quá lớn, nhưng dù sao về mặt tổng quát thì tất cả các API đều được tăng tốc độ (vì số lượng query giảm đi rất nhiều).</div>
<div>
<br /></div>
<div>
Còn nhiều thứ để tối ưu hơn nữa, nhưng tạm thời thế cũng ổn rồi. Bạn có thể xem thêm cách phân tích dữ liệu profile trả về qua <a href="https://www.udacity.com/course/design-of-computer-programs--cs212">khoá học của Peter Norvig</a>, hoặc google, đọc docs.</div>
<div>
<br /></div>
<div>
Nếu quan tâm đến performance của Python, bạn có thể đọc thêm <a href="https://www.amazon.com/High-Performance-Python-Performant-Programming/dp/1449361595">High Performance Python</a>.<br />
Hoặc sách High Performance Django tại đây: https://mega.nz/#!YBEGwCYT!KmNWDmu7i2EJ-1CXT7ZtdEcaJV6m7JE3ZWvB95N4utc</div>
<div>
<br /></div>
<div>
Đỗ Anh Tú</div>
Anonymoushttp://www.blogger.com/profile/05161118147269028823noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-63017062601878014422018-12-24T08:46:00.000+07:002018-12-24T08:52:57.188+07:00Chia sẻ file nhạc/ảnh/video trong mạng nội bộ (UPnP/DLNA)Thời điểm này, mỗi gia đình đều có vài thiết bị "thông minh", TiVi, điện thoại, laptop, máy chơi game PlayStation... mà dữ liệu nhạc / film có thể nằm mỗi cái ở một chỗ. Có nhiều cách để chia sẻ file giữa các thiết bị này với nhau mà người ta đã làm cả chục năm nay rồi.<br />
<br />
Một cái tên trông không đẹp cho lắm nhưng đã dùng thì rất tiện: UPnP (<span class="js-about-item-abstr">Universal Plug and Play)</span><br />
<br />
<blockquote class="tr_bq">
<span class="js-about-item-abstr">Universal Plug and Play is a set of
networking protocols that permits networked devices, such as personal
computers, printers, Internet gateways, Wi-Fi access points and mobile
devices to seamlessly discover each other's presence on the network and
establish functional network services for data sharing, communications,
and entertainment.</span></blockquote>
<br />
UPnP là một bộ giao thức mạng cho phép các thiết bị trong mạng tự khám phá ra nhau và chia sẻ dữ liệu.<br />
Giao thức mạng tức là như HTTP, FTP ... <br />
<br />
DLNA (Digital Living Network Alliance) là tên một "guideline"/bộ hướng dẫn, bao gồm nhiều thứ và có cả UPnP.<br />
<br />
Cài đặt:<br />
<blockquote class="tr_bq">
sudo apt install -y minidlna</blockquote>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV0oON0WeQ-YQAodOCQzTMIAInSKdrKRhXo6BsxRIGLodQT9HBHBp71EvE1sdGRAslypdO52isTcelRxDRcQ8Ex7WwarXmMNyKJ-UoOyXfjGsLU7EJBTWdn7GM48bUjK9GhK0fn1LZkDuW/s1600/raspberrywifi.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1600" data-original-width="1522" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV0oON0WeQ-YQAodOCQzTMIAInSKdrKRhXo6BsxRIGLodQT9HBHBp71EvE1sdGRAslypdO52isTcelRxDRcQ8Ex7WwarXmMNyKJ-UoOyXfjGsLU7EJBTWdn7GM48bUjK9GhK0fn1LZkDuW/s320/raspberrywifi.jpg" width="304" /></a></div>
<br />
(UPnP có sẵn trên Windows, khỏi cài đặt gì)<br />
<a name='more'></a><br />
<br />
<blockquote class="tr_bq">
$ whatis minidlna<br />
minidlna (1) - lightweight DLNA/UPnP-AV server</blockquote>
Cấu hình server này rất đơn giản, ghi đường dẫn tới thư mục muốn chia sẻ trong file /etc/minidlna.conf rồi restart là xong.<br />
<br />
Các client muốn dùng được phải có hỗ trợ UPnP, DLNA. Trên mọi hề điều hành phổ biến có thể dùng Kodi (XBMC) - kèm cả server UPnP luôn. Trên Android chương trình này sẽ chạy fullscreen nên hợp với xem film, có thể dùng Foobar2000 để nghe nhạc. Foobar2000 có trên IOS.<br />
VLC có hỗ trợ nhưng chạy không ổn định trên Ubuntu lẫn Android, load lúc được lúc không.<br />
<br />
minidlna dùng port UDP 1900, port 8200 chạy HTTP server hiển thị thông tin status<br />
<br />
<blockquote class="tr_bq">
$ lsof -i4 -n -p `pgrep minidlna` | grep minidlnad | grep :<br />
<br />
minidlnad 6004 minidlna 8u IPv4 97305 0t0 UDP 239.255.255.250:1900 <br />
minidlnad 6004 minidlna 9u IPv4 97306 0t0 TCP *:8200 (LISTEN)<br />
minidlnad 6004 minidlna 10u IPv4 97311 0t0 UDP 192.168.1.111:53042 </blockquote>
<br />
<br />
Minidlna dùng 7.5 MB RAM, tốn ít CPU và có thể chạy trên Raspberry Pi.<br />
<blockquote class="tr_bq">
$ ps xau | grep minidlna<br />
minidlna 6004 0.0 0.0 352176 7560 ? Ssl 08:06 0:00 /usr/sbin/minidlnad -f /etc/minidlna.conf -P /run/minidlna/minidlna.pid</blockquote>
<br />
Tưởng tượng cả gia đình đều có thể nghe nhạc xem film mọi lúc, không cần tải film/nhạc trên từng thiết bị, chỉ với 1 em raspberrypi chạy điện 2.5V - gọn - nhẹ - tiết kiệm.<br />
<br />
Hết.<br />
HVN at https://pymi.vn and https://www.familug.org<br />
<br />hvnc2k8http://www.blogger.com/profile/14576195557292941616noreply@blogger.com1tag:blogger.com,1999:blog-7188813081599719537.post-57096757542070336262018-12-22T18:28:00.000+07:002019-05-01T08:31:59.411+07:00Làm full tất cả CPU bằng 1 câu lệnhCách nào để làm CPU chạy full load ?<br />
<br />
Khi mang em laptop bị nóng rồi tự tắt đi sửa, các anh kỹ thuật dùng cách vào youtube và tìm video 4k rồi mở fullscreen để xem...<br />
<br />
Cách này chưa chắc đã làm full CPU, lại yêu cầu phải có mạng internet đủ nhanh.<br />
Những options nào khác?<br />
<br />
1. Dùng bash<br />
<blockquote class="tr_bq">
while true; do true; done</blockquote>
2. Dùng Python<br />
<blockquote class="tr_bq">
python -c 'while True: pass'</blockquote>
<br />
<br />
Hai cách này nghe có vẻ ổn, nhưng chúng chỉ làm full 01 CPU. Trong khi máy bạn có thể có nhiều CPU - ngay cả các Android smartphone của năm 2016 cũng đã có 4 CPU rồi.<br />
<br />
<h3>
Lấy số CPU của máy</h3>
Khái niệm CPU được hiểu theo nghĩa khác nhau trong các hòan cảnh khác nhau.<br />
Nếu hiểu theo nghĩa: số chương trình chạy đồng thời cùng lúc (thực sự - chứ không phải giả đồng thời bằng cách chuyển nhanh qua các chương trình khác nhau chạy mỗi cái 1 tí như cách 1 CPU làm để chạy nhiều chương trình), ta có thể gõ `top` rồi bấm 1.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLg3CmQWOQOhyphenhyphenUsKEKiZet9eKv-xiMpbJYWx5L5n5cKstvVQDBBd9YEqhqRNb7weWjwZ_iW0UK4s9cvO_Hh3asKiTWDKv8V8ysa4_7TMeBKGH7BdxKQCdwm5koMX0eVHAx0i05qzJCirUe/s1600/python_full_4cpu.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="437" data-original-width="1359" height="128" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLg3CmQWOQOhyphenhyphenUsKEKiZet9eKv-xiMpbJYWx5L5n5cKstvVQDBBd9YEqhqRNb7weWjwZ_iW0UK4s9cvO_Hh3asKiTWDKv8V8ysa4_7TMeBKGH7BdxKQCdwm5koMX0eVHAx0i05qzJCirUe/s400/python_full_4cpu.png" width="400" /></a></div>
<br />
Trên Linux, mọi thứ đều là file , thông tin CPU nằm trong /proc/cpuinfo<br />
<a name='more'></a><br />
<blockquote class="tr_bq">
$ grep 'core id' /proc/cpuinfo <br />
core id : 0<br />
core id : 0<br />
core id : 1<br />
core id : 1</blockquote>
getconf là câu lệnh đi kèm glibc, chuẩn POSIX tức có trên cả OSX, BSD ... <br />
<blockquote class="tr_bq">
$ getconf -a | grep processor -i<br />
_NPROCESSORS_CONF 4<br />
_NPROCESSORS_ONLN 4<br />
$ getconf _NPROCESSORS_ONLN<br />
4</blockquote>
<br />
Ở đây có tất cả 4 processor (CPU).<br />
Với 2 core, mỗi core 2 processor -> 2 x 2 = 4<br />
<blockquote class="tr_bq">
<br />
$ sysctl -a | grep cpu<br />
...</blockquote>
cũng sẽ hiện thông tin lần lượt cho các CPU0 1 2 3<br />
<br />
Trên Linux, lệnh lscpu sẽ cho thông tin<br />
chi tiết:<br />
<br />
<blockquote class="tr_bq">
$ lscpu<br />
Architecture: x86_64<br />
CPU op-mode(s): 32-bit, 64-bit<br />
Byte Order: Little Endian<br />
CPU(s): 4<br />
On-line CPU(s) list: 0-3<br />
Thread(s) per core: 2<br />
Core(s) per socket: 2<br />
Socket(s): 1<br />
NUMA node(s): 1<br />
Vendor ID: GenuineIntel<br />
CPU family: 6<br />
Model: 69<br />
Model name: Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz</blockquote>
Với 1 socket, với 2 core, mỗi core có 2 thread, => 1 x 2 x 2 = 4<br />
<br />
<blockquote>
CPUs = Threads per core X cores per socket X sockets</blockquote>
Vậy cách làm đúng ở đây là mở 4 cửa sổ và chạy theo cách python hoặc cách bash nói trên.<br />
Với máy tính có 128 CPU thì chúc bạn vui vẻ nhé!<br />
<br />
<h3>
Chạy lệnh song song với xargs</h3>
xargs là câu lệnh giải quyết bài tóan chạy song song các câu lệnh ở đây.<br />
<br />
Đầu tiên lấy số CPU online (khái niệm offline xuất hiện trên máy tính có nhiều khe cắm CPU - các máy server)<br />
<blockquote>
$ getconf _NPROCESSORS_ONLN<br />
4</blockquote>
<br />
Tạo ra dãy số từ 1 đến 4 với lệnh seq<br />
<br />
<blockquote class="tr_bq">
$ seq $(getconf _NPROCESSORS_ONLN)<br />
1<br />
2<br />
3<br />
4</blockquote>
<br />
Biến 4 dòng này thành 4 đầu vào cho chương trình, mỗi chương trình có nhiệm vụ chạy full CPU<br />
<blockquote class="tr_bq">
seq $(getconf _NPROCESSORS_ONLN) | xargs -n1 -P$(getconf _NPROCESSORS_ONLN) yes > /dev/null</blockquote>
yes là câu lệnh in ra màn hình liên tục dòng chữ `y`, nó có tác dụng như 1 vòng lặp vô hạn.<br />
<br />
<blockquote class="tr_bq">
export CPUS=$(getconf _NPROCESSORS_ONLN); seq $CPUS | xargs -n1 -P $CPUS python -c 'while True: pass' </blockquote>
<br />
Sử dụng chương trình `psensor`, nhiệt độ của các Core được hiển thị trên biểu đồ, tăng từ ~45 nhiệt độ bình thường lên 69, 70 độ C (nóng).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIMvLZwvVWNeFOVSP3dshDiniu3qkO032GpK3bv4PO-7-9nF7DLrtQI66IW_0PksdIqLPIs9HCd-ygSV7Si7-i0T7t1Sd8sAscC03Zthm4jylEd6qj6oP4piYWLdXjOYoDjgx8tS9y4lBC/s1600/psensor_cpu_temperature.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="191" data-original-width="801" height="95" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIMvLZwvVWNeFOVSP3dshDiniu3qkO032GpK3bv4PO-7-9nF7DLrtQI66IW_0PksdIqLPIs9HCd-ygSV7Si7-i0T7t1Sd8sAscC03Zthm4jylEd6qj6oP4piYWLdXjOYoDjgx8tS9y4lBC/s400/psensor_cpu_temperature.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
Các thiết bị làm mát phải vào cuộc, đảm bảo nhiệt độ không cao hơn, nếu không sẽ xảy ra cháy thiết bị - sau một vài phút ta có thể bắt đầu nghe thấy tiếng quạt CPU kêu khi nó bắt đầu hoạt động. Nếu quạt hỏng không quay, nhiệt độ sẽ tăng cao và hệ điều hành thường sẽ tắt thiết bị để đảm bảo không có cháy nổ.<br />
<br />
Các option của lệnh xargs:<br />
- n 1 : số argument gửi tới mỗi chương trình, ở đây là 1<br />
- P : số process tối đa được chạy đồng thời - ở đây set bằng số CPU online = 4<br />
python -c 'while True: pass' - câu lệnh được chạy, như thấy ở hình top, các chương trình này lần lượt nhận đầu vào là 1 2 3 4.<br />
<br />
4 chương trình sau sẽ được chạy song song<br />
<blockquote class="tr_bq">
python -c 'while True: pass' 1<br />
python -c 'while True: pass' 2<br />
python -c 'while True: pass' 3<br />
python -c 'while True: pass' 4</blockquote>
Với máy có lệnh `nproc`, có thể dùng lệnh này<br />
<blockquote>
seq $(nproc) | xargs -n1 -P $(nproc) python -c 'while True: pass'</blockquote>
<blockquote class="tr_bq">
$ whatis nproc<br />
nproc (1) - print the number of processing units available<br />
$ dpkg -S `which nproc`<br />
coreutils: /usr/bin/nproc</blockquote>
Tham khảo:<br />
- CPU count: <a href="https://unix.stackexchange.com/a/279354">https://unix.stackexchange.com/a/279354</a><br />
<br />
Hết.<br />
HVN at <a href="https://pymi.vn/">https://pymi.vn</a> and https://www.familug.orghvnc2k8http://www.blogger.com/profile/14576195557292941616noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-86280891789611362552018-12-17T23:19:00.002+07:002018-12-17T23:22:58.179+07:00Random notesĐi làm không có nhiều thời gian để ngồi nắn nót từng bài blog, từng chủ đề từ à tớ ê.<br />
Vậy nên để khởi động lại việc viết lách, những bài random notes sẽ note lại những thứ nhỏ bé học được gần đây mà không sắp xếp theo chủ đề nào cả.<br />
<h3>
0. Bấm gì thay TAB để auto-complete?</h3>
Ctrl i sẽ cho hiệu ứng auto-complete tương tự.<br />
<br />
<blockquote class="tr_bq">
$ bind -p | grep \\C-i<br />
"\C-i": complete<br />
"\e\C-i": dynamic-complete-history</blockquote>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLstx_Akci-4VhgK-NkoroDtMqTVtyFX-s9EqoE0VPQKqCR7qM1Zgtiluc189SZGA3lLIjMTx9uuK5yNckSRWGurQleThMWQxbm7zGuGZ2kzvLgoNJnx92XRUnwziU5BHswIihTLD3EE1x/s1600/tetris.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="489" data-original-width="522" height="299" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjLstx_Akci-4VhgK-NkoroDtMqTVtyFX-s9EqoE0VPQKqCR7qM1Zgtiluc189SZGA3lLIjMTx9uuK5yNckSRWGurQleThMWQxbm7zGuGZ2kzvLgoNJnx92XRUnwziU5BHswIihTLD3EE1x/s320/tetris.png" width="320" /></a></div>
<br />
<h3>
1. rxvt VS other terminal?<a name='more'></a></h3>
Được "dùng xái" một em laptop cài Elementary (base Ubuntu 16.04)<br />
<blockquote class="tr_bq">
<br />
$ lsb_release -d<br />
Description: elementary OS 0.4.1 Loki</blockquote>
giao diện cute, gọn gàng, nhưng CPU lúc nào cũng min ở 10% cho giao diện.<br />
Vậy nên tạo 1 user khác rồi cài i3, nhẹ, nhanh, nguy hiểm.<br />
<blockquote class="tr_bq">
<br />
~$ ps xau | grep [i]3$<br />
hvn 4298 0.0 0.1 137132 11912 ? Ss 22:15 0:00 i3</blockquote>
<br />
11MB RSS và 0.1 % CPU.<br />
<br />
<br />
Mặc định i3 sẽ dùng terminal có sẵn - ở đây là terminal đi kèm elementary và nó cũng không được tối ưu cho việc nhanh hay nhẹ. urxvt (rxvt-unicode) là cái tên đầu tiên nhớ tới do một thời dùng Arch Linux. Nhưng sau một hồi ngồi config để:<br />
- chỉnh font chữ to lên<br />
- đổi màu nền<br />
- paste dùng chuột giữa, <a href="https://unix.stackexchange.com/questions/212360/copying-pasting-with-urxvt">không dùng phím được</a> nếu không chỉnh thêm...<br />
<br />
quyết định tìm một option khác.<br />
Lxterminal (terminal đi xem LXDE) nhỏ, nhẹ, nhanh. Chỉnh font chữ to nhỏ / màu nền bằng giao diện.<br />
<br />
Set màu background và foreground theo black theme của <a href="https://ethanschoonover.com/solarized/">Solarized theme</a><br />
<blockquote class="tr_bq">
Background color to #002b36 and Foreground color to #839496</blockquote>
<br />
Có thể lúc còn ham mê học hành, bạn có thể dành cả buổi tối để config màu nền hay font chữ, thì đến thời điểm từng phút mỗi buổi tối là sự đánh đổi giữa các lựa chọn, lựa chọn tốn ít công sức nhất sẽ là thứ ta dùng.<br />
<br />
<h3>
2. Chơi game NES dùng Joystick / Joypad </h3>
Chương trình `nestopia` là lựa chọn hiển nhiên nhất để chơi NES trên Ubuntu.<br />
Nếu có tay điện tử 4 nút (mua tầm 10$ cho 1 cặp https://duckduckgo.com/?q=usb+nes+controller&t=canonical&atb=v147-5__&ia=products ) , cắm vào máy sẽ phải cấu hình.<br />
<br />
Cài:<br />
apt install -y jstest-gtk joystick <br />
<br />
Rồi chạy jstest-gtk để cấu hình các phím bấm. Sau đó vào Nestopia > Emulator > Configuration > Input chon Joystick và cấu hình các phím bấm một lần nữa. <br />
<br />
Search: NES game Rom để tải game Mario, Contra, xếp hình tetris ... và trở lại tuổi thơ.<br />
<br />
<h3>
3. pavucontrol</h3>
Khi dùng i3, chỉnh âm lượng là một vấn đề không đơn giản.<br />
Có thể ngồi học câu lệnh tăng giảm âm lượng với pactl, alsamixer... hoặc cài pavucontrol, chạy vào keoooóo.<br />
<br />
<br />
Hết.<br />
HVN at https://pymi.vn and https://familug.orghvnc2k8http://www.blogger.com/profile/14576195557292941616noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-59304570915963346362018-09-02T14:35:00.000+07:002018-09-02T14:35:57.798+07:00Giảm ánh sáng xanh gây mù trên máy tính với redshift, f.luxCác nghiên cứu khoa học gần đây cho biết ánh sáng xanh rất có hại cho mắt và có thể khiến bạn bị mù - và ảnh hưởng tới giấc ngủ (<a href="https://phys.org/news/2018-08-chemists-blue.html">https://phys.org/news/2018-08-chemists-blue.html</a>)<br />
<br />
MacOS/iOS các phiên bản mới gần đây có sẵn tính năng giảm ánh sáng xanh, tăng màu đỏ để giảm sự hại mắt.<br />
- MacOS <a href="https://support.apple.com/en-us/HT207513">https://support.apple.com/en-us/HT207513</a><br />
- iPhone/iPad .. <a href="https://support.apple.com/en-sg/HT202613">https://support.apple.com/en-sg/HT202613</a> <br />
<br />
<a href="https://justgetflux.com/" target="_blank">F.lux</a> là một trong những phần mềm tiên phong trong lĩnh vực này, có hỗ trợ các hệ điều hành phổ biến:<br />
Linux (also available for Windows Mac iPhone/iPad Android) <br />
<br />
redshift là một phần mềm tương tự, có thể cài bằng apt trên 16.04:<br />
<blockquote class="tr_bq">
sudo apt-get install -y redshift</blockquote>
Chạy redshift, màn hình sẽ tự điều chỉnh màu sắc dựa theo tọa độ địa lý của bạn.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoU8YGi2ULfKT8K0aIElgPQQeY_2M9Zkb9rIz_0KpgtSb_EUz1Dpi0SANTA58SZ7FM3ZhdckL9JP9DDxJM04L-Kp5nMoCfS38N2F41yaczpxBGe0vXjq_eGTfuYpHYQ6Ubt_2vQpQAESk/s1600/redshift-icon-256.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="256" data-original-width="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoU8YGi2ULfKT8K0aIElgPQQeY_2M9Zkb9rIz_0KpgtSb_EUz1Dpi0SANTA58SZ7FM3ZhdckL9JP9DDxJM04L-Kp5nMoCfS38N2F41yaczpxBGe0vXjq_eGTfuYpHYQ6Ubt_2vQpQAESk/s1600/redshift-icon-256.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
Gì cũng được, miễn có là được.<br />
<a name='more'></a><br />
<br />
Hết.<br />
<br />
HVN at <a href="https://pymi.vn/">https://pymi.vn</a> and <a href="https://www.familug.org/">https://www.familug.org</a> <br />
<br />hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-30896683435929596932018-09-02T10:55:00.000+07:002018-09-02T11:00:51.090+07:00Di chuyển trên less - xem web bằng w3mless là lệnh để xem file (chỉ xem, không sửa).<br />
less là phiên bản cải tiến của "more", `less is more`.<br />
<br />
Có thể bạn chưa bao giờ gõ less, nhưng nếu bạn đã gõ man, tức là bạn đang dùng less.<br />
<br />
Trong less hỗ trợ phím chuyển trang của cả emacs lẫn vim.<br />
Với <span style="color: orange;"><b>vim</b></span>, đó là<span style="color: red;"><b> Ctrl f</b></span> (forward - cuốn xuống) và <span style="color: red;"><b>Ctrl u</b></span> (upward - cuốn lên).<br />
Với <span style="color: orange;"><b>emacs</b></span>, đó là <span style="color: red;"><b>Ctrl v</b></span> và Meta v (thường là <b><span style="color: red;">Alt-v</span></b>) - với chữ v giống dấu mũi tên xuống, và alt tạo ra hiệu ứng ngược lại với xuống - tức là lên.<br />
<br />
Do các tổ hợp phím (key binding) emacs được dùng khắp mọi nơi trên *NIX, sử dụng Ctrl v Meta v giúp bạn di chuyển ở hầu hết các chương trình *NIX khác, có thể kể tới trình duyệt <span style="color: #0b5394;"><b>w3m</b></span>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiH2eVyaxlRYjYBGsdtaHzXH3tvBTG8DYKKXevtaP1niOrlYIx-XSaYcrAtqfQZjbw5YmLkCNI7SKvZRPkyFfYNAMBvv3rUTUz684T88NwCP33eRKKCPIB_XYARWXj3Ll3I-n5ZYk-luAM/s1600/tmux_w3m.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="563" data-original-width="886" height="253" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiH2eVyaxlRYjYBGsdtaHzXH3tvBTG8DYKKXevtaP1niOrlYIx-XSaYcrAtqfQZjbw5YmLkCNI7SKvZRPkyFfYNAMBvv3rUTUz684T88NwCP33eRKKCPIB_XYARWXj3Ll3I-n5ZYk-luAM/s400/tmux_w3m.png" width="400" /></a></div>
<br />
<br />
<span style="color: #0b5394;"><b>w3m</b></span> là trình duyệt trên terminal,<br />
<br />
<a name='more'></a>chỉ hỗ trợ text, không JS, không ảnh/video, nếu với mục đích HỌC TẬP, bạn hoàn toàn có thể dụng w3m để tăng độ tập trung. w3m có hỗ trợ nhiều tab, nhưng khi mở 1 tab mới, chương trình sẽ bị "block" cho tới khi load xong, không chuyển tới tab khác được. Một cách workaround là dùng <a href="https://www.familug.org/search/label/tmux" target="_blank">tmux</a> và mỗi window/pane mở 1 page dùng w3m. Happy-happy.<br />
<br />
Hết.<br />
HVN at <a href="https://www.familug.org/">https://www.familug.org</a> and <a href="https://pymi.vn/">https://pymi.vn</a>hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0tag:blogger.com,1999:blog-7188813081599719537.post-6050862440731986282018-05-27T11:13:00.002+07:002018-05-27T11:13:57.143+07:00In ra màn hình số 1 - dùng ngôn ngữ nào nhanh/nhẹ nhất?Nếu chỉ cần in ra mà hình số 1, ngôn ngữ lập trình nào sẽ chạy tốn ít RAM nhất?<br />
Thí nghiệm sau sẽ cho ta thấy các chương trình cần bao nhiêu RAM để chạy và in
ra số 1, kết thúc dòng bằng một dấu xuống dòng (newline <code>\n</code>).<br />
Ta dùng chương trình <code>/usr/bin/time</code> để có cả thông số về RAM (<code>RSS</code>) thay vì chỉ gõ time - lệnh builtin của bash.<br />
<h2>
bash4.3</h2>
<div class="highlight">
<pre>$ /usr/bin/time bash -c <span class="s1">'echo 1'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 3064maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+136minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
dash</h2>
<div class="highlight">
<pre>$ /usr/bin/time /bin/dash -c <span class="s1">'echo 1'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed ?%CPU <span class="o">(</span>0avgtext+0avgdata 1492maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+67minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
mawk</h2>
<div class="highlight">
<pre>$ /usr/bin/time mawk <span class="s1">'BEGIN {print 1}'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 1932maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+86minor<span class="o">)</span>pagefaults 0swaps
<a name='more'></a></pre>
</div>
<h2>
gawk 4.1.3</h2>
<div class="highlight">
<pre>$ /usr/bin/time gawk <span class="s1">'BEGIN {print 1}'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">42</span>%CPU <span class="o">(</span>0avgtext+0avgdata 3632maxresident<span class="o">)</span>k
552inputs+0outputs <span class="o">(</span>2major+173minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
Python3.5.2</h2>
<div class="highlight">
<pre>$ /usr/bin/time python3 -c <span class="s1">'print(1)'</span>
<span class="m">1</span>
<span class="m">0</span>.03user <span class="m">0</span>.01system <span class="m">0</span>:00.05elapsed <span class="m">98</span>%CPU <span class="o">(</span>0avgtext+0avgdata 9292maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+1100minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
Python2.7.12</h2>
<div class="highlight">
<pre>$ /usr/bin/time python2 -c <span class="s1">'print(1)'</span>
<span class="m">1</span>
<span class="m">0</span>.01user <span class="m">0</span>.00system <span class="m">0</span>:00.01elapsed <span class="m">94</span>%CPU <span class="o">(</span>0avgtext+0avgdata 6640maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+819minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
Ruby 2.3.1</h2>
<div class="highlight">
<pre>$ /usr/bin/time ruby -e <span class="s1">'puts 1'</span>
<span class="m">1</span>
<span class="m">0</span>.03user <span class="m">0</span>.01system <span class="m">0</span>:00.04elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 10336maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+1595minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
Perl v5.22.1</h2>
<div class="highlight">
<pre>$ /usr/bin/time perl -e <span class="s1">'print "1\n"'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 4144maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+176minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
NodeJS 6.11.2</h2>
<div class="highlight">
<pre>$ /usr/bin/time node -p <span class="s1">'1'</span>
<span class="m">1</span>
<span class="m">0</span>.09user <span class="m">0</span>.00system <span class="m">0</span>:00.10elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 24080maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+2920minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
Elixir 1.6.3</h2>
<div class="highlight">
<pre>$ /usr/bin/time elixir -e <span class="s1">'IO.puts 1'</span>
<span class="m">1</span>
<span class="m">0</span>.17user <span class="m">0</span>.04system <span class="m">0</span>:00.16elapsed <span class="m">137</span>%CPU <span class="o">(</span>0avgtext+0avgdata 29268maxresident<span class="o">)</span>k
16inputs+0outputs <span class="o">(</span>1major+7837minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
Guile 2.0.11</h2>
<div class="highlight">
<pre>$ /usr/bin/time guile -c <span class="s1">'(display 1) (newline)'</span>
<span class="m">1</span>
<span class="m">0</span>.02user <span class="m">0</span>.00system <span class="m">0</span>:00.01elapsed <span class="m">115</span>%CPU <span class="o">(</span>0avgtext+0avgdata 7720maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+976minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
printf 8.25 - chương trình viết bằng C</h2>
<div class="highlight">
<pre>$ /usr/bin/time <span class="nb">printf</span> <span class="s1">'1'</span>
<span class="m">10</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 1844maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+72minor<span class="o">)</span>pagefaults 0swaps
</pre>
</div>
<h2>
Làm lại toàn bộ bằng Python</h2>
Tạo một dictionary, chứa các tên ngôn ngữ - kèm với câu lệnh sẽ dùng để thử nghiệm<br />
<div class="highlight">
<pre><span class="kn">import</span> <span class="nn">shlex</span>
<span class="kn">import</span> <span class="nn">subprocess</span>
<span class="n">programs</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'bash'</span><span class="p">:</span> <span class="s1">'bash -c "echo 1"'</span><span class="p">,</span>
<span class="s1">'dash'</span><span class="p">:</span> <span class="s1">'dash -c "echo 1"'</span><span class="p">,</span>
<span class="s1">'mawk'</span><span class="p">:</span> <span class="s2">"mawk 'BEGIN {print 1}'"</span><span class="p">,</span>
<span class="s1">'gawk'</span><span class="p">:</span> <span class="s2">"gawk 'BEGIN {print 1}'"</span><span class="p">,</span>
<span class="s1">'python2.7'</span><span class="p">:</span> <span class="s1">'python2 -c "print(1)"'</span><span class="p">,</span>
<span class="s1">'python3.5'</span><span class="p">:</span> <span class="s1">'python3 -c "print(1)"'</span><span class="p">,</span>
<span class="s1">'ruby2.3'</span><span class="p">:</span> <span class="s1">'ruby -e "puts 1"'</span><span class="p">,</span>
<span class="s1">'node6'</span><span class="p">:</span> <span class="s1">'node -p 1'</span><span class="p">,</span>
<span class="s1">'perl5'</span><span class="p">:</span> <span class="s2">"perl -e </span><span class="se">\"</span><span class="s2">print '1</span><span class="se">\n</span><span class="s2">'</span><span class="se">\"</span><span class="s2">"</span><span class="p">,</span>
<span class="s1">'elixir'</span><span class="p">:</span> <span class="s1">'elixir -e "IO.puts 1"'</span><span class="p">,</span>
<span class="s1">'guile'</span><span class="p">:</span> <span class="s1">'guile -c "(display 1) (newline)"'</span><span class="p">,</span>
<span class="s1">'printf'</span><span class="p">:</span> <span class="s1">'printf "1</span><span class="se">\n</span><span class="s1">"'</span>
<span class="p">}</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="k">def</span> <span class="nf">get_rss_from_stderr</span><span class="p">(</span><span class="n">stderr</span><span class="p">):</span>
<span class="n">match</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="s1">'(?P<rss>\d+)maxresident'</span><span class="p">)</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">err</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">))</span>
<span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s1">'rss'</span><span class="p">))</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="kn">as</span> <span class="nn">pd</span>
<span class="n">languages</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">rsses</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">language</span><span class="p">,</span> <span class="n">cmd</span> <span class="ow">in</span> <span class="n">programs</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="n">cmd</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'/usr/bin/time'</span><span class="p">]</span> <span class="o">+</span> <span class="n">shlex</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span>
<span class="n">process</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">Popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">)</span>
<span class="n">out</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">process</span><span class="o">.</span><span class="n">communicate</span><span class="p">()</span>
<span class="k">assert</span> <span class="n">out</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'1</span><span class="se">\n</span><span class="s1">'</span>
<span class="n">rss</span> <span class="o">=</span> <span class="n">get_rss_from_stderr</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">language</span><span class="p">,</span> <span class="n">rss</span><span class="p">)</span>
<span class="n">languages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">language</span><span class="p">)</span>
<span class="n">rsses</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">rss</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">index</span><span class="o">=</span><span class="n">languages</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">rsses</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s1">'RSS'</span><span class="p">])</span>
<span class="n">df_sorted_rss</span> <span class="o">=</span> <span class="n">df</span><span class="o">.</span><span class="n">sort_values</span><span class="p">(</span><span class="n">by</span><span class="o">=</span><span class="s1">'RSS'</span><span class="p">)</span>
<span class="c1"># run in Jupyter</span>
<span class="kn">from</span> <span class="nn">matplotlib</span> <span class="kn">import</span> <span class="n">pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="nn">matplotlib</span>
<span class="n">matplotlib</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">use</span><span class="p">(</span><span class="s1">'fivethirtyeight'</span><span class="p">)</span>
<span class="o">%</span><span class="n">matplotlib</span> <span class="n">inline</span>
<span class="n">df_sorted_rss</span><span class="o">.</span><span class="n">plot</span><span class="o">.</span><span class="n">bar</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s1">'RSS size in byte when print 1'</span><span class="p">);</span>
</pre>
</div>
<img alt="Dynamic typing languages" src="https://pp.pymi.vn/images/dynamic.png" /><br />
<h2>
Kết luận</h2>
Khoan vội kết luận rằng cả thế giới nên chuyển hết về dùng dash cho
đỡ tốn RAM hay xa lánh Elixir vì nó chạy tới 30 MB RAM để in số 1 ra màn
hình.<br />
Sử dụng đúng công cụ, cho đúng vấn đề thích hợp mới là điều quan trọng.<br />
<ul>
<li>dash được viết ra để dùng làm <code>shell (sh)</code> cho Ubuntu, nó cần nhẹ, dù trả giá bằng việc không nhiều tính năng, và chỉ là một shell scripting language</li>
<li><a href="http://www.familug.org/search/label/bash">bash</a> là shell được dùng phổ biến nhất thế giới</li>
<li>awk là ngôn ngữ lập trình đã gắn liền với lịch sử của thế giới UNIX,
rất tiện để tính toán dữ liệu cột / hàng. Với AWK cùng các CLI tool
truyền thống, người ta có thể <a href="https://adamdrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html">giải quyết bài toán "big data" nhanh gấp 200 lần dùng Hadoop (EMR)</a></li>
<li>mawk nhanh hơn gawk</li>
<li>perl5 luôn có chỗ đứng của nó, nó vẫn ở trên máy Ubuntu dù chưa bao giờ mình chủ ý cài</li>
<li><a href="http://www.familug.org/search/label/guile">guile</a> là extension language chính thức được lựa chọn bởi GNU, nó chính là ngôn ngữ scheme được mang vào ứng dụng thực tế</li>
<li><a href="http://pymi.vn/">Python</a> - ngôn ngữ dễ đọc, phổ biến
nhất thế giới, đã đẩy ngành tính toán khoa học từ bộ môn trong phòng thí
nghiệm trở thành ngành hot nhất trái đất. PS: Python khi bật lên đã
dùng ~ 8MB RAM.</li>
<li>Ruby còn dùng nhiều RAM hơn nữa, dù về mặt tính năng thường được so
ngang với Python, nhưng mặt ứng dụng vào khoa học thì là vực so với trời
Python.</li>
<li>NodeJS là một JavaScript engine chuyển biến cả ngành web, cũng chưa
rõ sao nó ngốn gần 25MB để bật lên, có khi đó vốn là bản chất của
JavaScript.</li>
<li><a href="http://elixir.pymi.vn/">Elixir</a> không chỉ bật lên một
interpreter như bash, mà đó là cả hệ thống phân tán - chạy máy ảo BEAM
của Erlang, nên con số 30MB thậm chí còn là hơi ... ít.</li>
</ul>
Bài này không xét các ngôn ngữ mà phải viết thành file, compile mới chạy, vì tác giả quá lười.<br />
PS: học hết đống trên ở <a href="http://www.familug.org/2016/12/free-ebook.html">đây</a><br />
Hết.<br />
HVN at https://pymi.vn and https://www.familug.org<br />
Theo <a href="http://pp.pymi.vn/dynamic.html">http://pp.pymi.vn/dynamic.html</a> hvnhttp://www.blogger.com/profile/09033151657280444867noreply@blogger.com0