Đừng sudo, nếu không biết virtualenv
- "Mình không cài bằng pip được..."
- "chạy sudo pip ... nhé "
Đó là những câu hỏi và trả lời thường xuất hiện, khi người ta nói về pip. Đây là vấn đề chung với những người dùng không hiểu về phân quyền trên UNIX và thường sẽ làm mọi thứ trở nên rắc rối thêm bằng cách chạy sudo.
sudo - câu lệnh thần thánh
Một mẩu truyện nhỏ cho thấy sức mạnh của sudo
, khi ta nói mà đối phương không nghe, thêm sudo
vào đầu là mọi thứ đều sẽ hoạt động.
sudo - SUperuser DO
Trên các hệ điều hành *NIX có chia ra nhiều user để nhiều người dùng chung một máy tính. Trong đó, mọi máy tính luôn có user root
- user có quyền năng tối thượng, thường được gọi là quyền admin hay superuser, có thể thực hiện bất kỳ thay đổi gì trên máy tính. Do có quyền năng vô hạn như vậy, nên khi thực hiện việc gì cũng ẩn chứa nguy hiểm, một câu lệnh gõ nhầm cũng có thể phá huỷ hệ thống, và nếu như ai cũng là root thì hệ thống sẽ loạn, người dùng không hiểu biết có thể cài đặt các loại virus/malware lên máy tính (hi Windows).
Để giải quyết vấn đền này, người ta sản xuất ra phần mềm tên "sudo", cho phép cấp quyền cho các user nhất định. Với sự tồn tại của sudo, người ta thay đổi cách dùng máy tính:
- tất cả mọi người đều có tài khoản riêng của mình
- cấp quyền
root
cho một số user cụ thể - user được cấp quyền có thể chạy câu lệnh như user root.
Khi nào cần quyền root
Mỗi user trên *NIX được cấp một thư mục gọi là thư mục HOME. Có thể truy cập thư mục này bằng:
cd ~
hay
cd $HOME
Thư mục này thường có đường dẫn /home/yourname/
.
và mặc định thuộc toàn quyền sử dụng của user
Hệ thống có thư mục đặc biệt /tmp
, ai cũng đều có thể ghi vào (nhưng người này không thể can thiệp vào file/thư mục của người kia) - và các file trong này sẽ tự bị xoá mỗi khi máy tắt.
Còn lại, các thư mục khác trên máy tính chỉ có root
mới có thể thay đổi (/etc, /log, /var, /usr, /bin ...), khi sử dụng các phần mềm thực hiện thay đổi ảnh hưởng đến cả hệ thống, ví dụ dùng apt-get
để cài package, lý do ta cần chạy sudo vì:
- apt-get sẽ ghi file vào các thư mục nói trên. Ví dụ khi cài package
nginx
, các file sau sẽ được tạo ra:
$ dpkg -L nginx | grep nginx
/var/cache/nginx
/var/log/nginx
/usr/lib/nginx
/usr/lib/nginx/modules
/usr/share/doc/nginx
/usr/share/doc/nginx/CHANGES.ru.gz
/usr/share/doc/nginx/changelog.Debian.gz
/usr/share/doc/nginx/changelog.gz
/usr/share/doc/nginx/copyright
/usr/share/doc/nginx/README
/usr/share/man/man8/nginx.8.gz
/usr/share/lintian/overrides/nginx
/usr/share/nginx
/usr/share/nginx/html
/usr/share/nginx/html/index.html
/usr/share/nginx/html/50x.html
/usr/sbin/nginx-debug
/usr/sbin/nginx
/etc/init.d/nginx-debug
/etc/init.d/nginx
/etc/logrotate.d/nginx
/etc/nginx
/etc/nginx/koi-win
/etc/nginx/fastcgi_params
/etc/nginx/uwsgi_params
/etc/nginx/mime.types
/etc/nginx/koi-utf
/etc/nginx/nginx.conf
/etc/nginx/conf.d
/etc/nginx/conf.d/default.conf
/etc/nginx/scgi_params
/etc/nginx/win-utf
/etc/default/nginx-debug
/etc/default/nginx
/etc/nginx/modules
...
Chỉ user root
mới có quyền ghi vào /etc/ /bin ... đó là lý do cần thêm sudo
khi người dùng với user của họ chạy câu lệnh apt-get
.
Nếu ta cấu hình cho apt-get
không ghi vào các thư mục trên, ta hoàn toàn có thể chạy apt-get
để cài đặt package trên thư mục home của mình. Các chương trình được cài xong sẽ chỉ mình mình dùng được, do user khác không truy cập được vào các file này.
sudo pip
Khi dùng pip cài ipython (không dùng virtualenv), đây là nơi ipython sẽ được cài:
root@hvn:~# pip show ipython
Name: ipython
Version: 6.1.0
Summary: IPython: Productive Interactive Computing
Home-page: https://ipython.org
Author: The IPython Development Team
Author-email: [email protected]
License: BSD
Location: /usr/local/lib/python3.5/dist-packages
Requires: prompt-toolkit, setuptools, pygments, simplegeneric, jedi, pexpect, traitlets, pickleshare, decorator
/usr/local
là thư mục chỉ root
mới được quyền ghi, vì vậy bạn không thể chạy pip install PACKAGE
, bởi user thông thường không có quyền (permission denied) ghi vào /usr
$ pip install phg
..,
PermissionError: [Errno 13] Permission denied: '/usr/local/lib/python3.5/dist-packages/phg-1.1.0.dist-info'
Mô-tuýp truyền thống lại xảy ra:
- "Mình không cài được ..."
- "sudo pip ..."
Cách làm này không sai, nhưng thiếu hiểu biết:
- nó cài đặt package lên thư mục hệ thống /usr/local/lib, khi có nhu cầu sử dụng 2 phiên bản của cùng 1 phần mềm, ta không thể gỡ một bản đi và giữ lại bản kia (VD: django 1.7 và django 1.11)
- không phải code nào cài từ pip cũng an toàn, khi chạy với user root cài một package chứa code phá hoại, nó có thể huỷ diệt cả hệ thống (vì user root có quyền làm mọi thứ).
Giải pháp là virtualenv!
Virtualenv
virtualenv là một phần mềm viết bằng Python, giúp tạo ra các "môi trường ảo" để tách biệt các môi trường của các project khác nhau.
Mỗi "môi trường ảo" về bản chất chỉ là một thư mục, với một vài thay đổi khiến cho khi ta dùng pip để cài package, các package sẽ được cài vào thư mục này. Điều này giải quyết các vấn đề nói trên:
- tách biệt các môi trường giữa các project, 2 project có thể dùng 2 phiên bản khác nhau của cùng 1 phần mềm.
- người dùng có thể tạo môi trường ảo trong thư mục $HOME của mình, không cần chạy sudo - không ảnh hưởng đến hệ thống.
virtualenv hoạt động được là nhờ vào cách mà Python thực hiện import. Luật import của Python, khi ta import spam
:
- Tìm các builtin module xem có cái nào tên là spam không. Danh sách các builtin module được chứa trong sys:
python -c 'import sys; print(sys.builtin_module_names)'
('__builtin__', '__main__', '_ast', '_bisect', '_codecs', '_collections', '_functools', '_heapq', '_io', '_locale', '_md5', '_random', '_sha', '_sha256', '_sha512', '_socket', '_sre', '_struct', '_symtable', '_warnings', '_weakref', 'array', 'binascii', 'cPickle', 'cStringIO', 'cmath', 'datetime', 'errno', 'exceptions', 'fcntl', 'gc', 'grp', 'imp', 'itertools', 'marshal', 'math', 'operator', 'posix', 'pwd', 'select', 'signal', 'spwd', 'strop', 'sys', 'syslog', 'thread', 'time', 'unicodedata', 'xxsubtype', 'zipimport', 'zlib')
- Nếu không thấy, tìm lần lượt trong các thư mục chứa trong list :
sys.path
, cho đến khi tìm thấy file spam.py. - Nếu vẫn không thấy, raise Exception
ImportError
.
Khi ta bật (activate) một virtualenv lên (source), thực tế ta thay đổi sys.path, nhét đường dẫn đến "môi trường ảo" hiện tại lên đầu sys.path, vì vậy khi thực hiện import, Python sẽ tìm trong "môi trường ảo" trước.
Đọc kỹ những dòng sau để thấy sự ảnh hưởng của virtualenv với Python sys.path,
khi bật virtualenv, đường dẫn tới virtualenv sẽ xuất hiện trong sys.path,
tức module / code nào nằm trong thư mục
/home/hvn/myvenv/lib/python3.6/site-packages
đều có thể được import.
$ python3 -m venv myvenv
$ source myvenv/bin/activate
(myvenv) $ python3 -c 'import sys; print(sys.path)'
['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/home/hvn/myvenv/lib/python3.6/site-packages']
(myvenv) $ deactivate
$ python3 -c 'import sys; print(sys.path)'
['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages']
Với virtualenv, ta sẽ không bao giờ phải sudo pip
nữa, mỗi project sẽ có một môi trường riêng biệt, không ai ảnh hưởng tới ai. Và chỉ gõ sudo pip
khi cần thay đổi 1 package trên toàn hệ thống.
Sử dụng pip trong venv
Cho python3.6 trở lên, ví dụ trên Ubuntu 18.04, xem tài liệu cho lệnh dùng trên Windows.
$ python3 -m venv myvenv
$ source myvenv/bin/activate
(myvenv) $ python3 -m pip install django
Collecting django
Cache entry deserialization failed, entry ignored
Downloading https://files.pythonhosted.org/packages/5c/63/6d7efecbf3f06db8c6577950a24a191e55cadf7cda4d7fe6976206c886dd/Django-3.0.7-py3-none-any.whl (7.5MB)
100% |████████████████████████████████| 7.5MB 206kB/s
Collecting pytz (from django)
Using cached https://files.pythonhosted.org/packages/4f/a4/879454d49688e2fad93e59d7d4efda580b783c745fd2ec2a3adf87b0808d/pytz-2020.1-py2.py3-none-any.whl
Collecting sqlparse>=0.2.2 (from django)
Using cached https://files.pythonhosted.org/packages/85/ee/6e821932f413a5c4b76be9c5936e313e4fc626b33f16e027866e1d60f588/sqlparse-0.3.1-py2.py3-none-any.whl
Collecting asgiref~=3.2 (from django)
Cache entry deserialization failed, entry ignored
Downloading https://files.pythonhosted.org/packages/68/00/25013f7310a56d17e1ab6fd885d5c1f216b7123b550d295c93f8e29d372a/asgiref-3.2.7-py2.py3-none-any.whl
Installing collected packages: pytz, sqlparse, asgiref, django
Successfully installed asgiref-3.2.7 django-3.0.7 pytz-2020.1 sqlparse-0.3.1
(myvenv) $ python
Python 3.6.9 (default, Apr 18 2020, 01:56:04)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.__version__
'3.0.7'
>>> quit()
$ python3 -m pip freeze
asgiref==3.2.7
Django==3.0.7
pkg-resources==0.0.0
pyfml==0.0.0
pytz==2020.1
sqlparse==0.3.1
Kết luận
sudo
là sức mạnh, đừng chỉ vì quá mạnh mà thích làm gì cũng được 🙄