# apt install

### 痛点说明

当我们在使用 debian/ubuntu 的时候，如果需要安装一些应用，通常会执行 `sudo apt install ...`，如果源中有的话，这是最傻瓜式，也最方便的方式了。

但是笔者其实一直不是很喜欢这种方式，原因如下：

1. 这种方式由于给到了 root 权限，无形中，背后做了很多事，对于笔者来说，总感觉缺乏一些掌控
2. 这种方式安装的软件，相关的文件会分散到各种目录，很乱。
3. 没法精确的去控制到底使用哪个版本，因为源中可能没有。

为什么乱呢？ 这和 Linux 的设计有关。这块笔者不是很清晰，之前看过一片 [文章](https://www.jianshu.com/p/86dd6e34ce91)，这篇文章很长，它后面简要介绍了 Linux 上安装软件的特点，强烈建议玩 Linux 的同学看下。这里，我简单描述下，希望我理解的没有问题。\
&#x20;我们先说下 Windows。 在 Windows 下，安装一个软件，通常会是一个安装包，点击安装，会指定一个安装目录，安装完成后，这个软件连同这个软件的相关依赖，都会同时放进我们指定的安装目录。\
&#x20;当然，如果是那种解压直接用的，直接找个目录放解压就好。这个时候，是不是很清晰，某个软件就是安装在某个目录。当然，除此外，然后也有可能在 比如 User 目录下，放一些配置文件，缓存什么的。但是笔者以为，相比较 Linux，可以说相当清爽。\
&#x20;反观 Linux 呢？ 当我们执行 `sudo apt install ...` 时，最终文件会散落到 `/usr`、`/var`、`/run` 、`/etc`等等这些目录，如果你是个小白，可能对此完全蒙圈,即便对于一些开发人员，其实对这些也知之不多。只有对于有经验的运维人员，才能快速熟练的找到相关文件的位置。

我们从 Linux 上面软件的安装方式出发，其实就能窥见这其中的混乱了。 比如说 Ubuntu，当我们要安装一个软件时，通常会有几种安装方式：

1. `sudo apt install ...`
2. `sudo dpkg -i ***.deb`
3. `snap install ...`
4. 如果是桌面版的话，可以直接从应用商店下载
5. 下载二进制文件压缩包，解压直接使用
6. 下载源码，自行编译，再 `make install`

所以，在使用 Linux 的时候，我们总会去搜索诸如 “Ubuntu18.04 如何安装 mongodb ” 这样的问题，而且经常是一个软件有几种不同的安装方式，对于初学者，当从其中一个点出发的时候，基本是蒙圈的。与此同时，我们在使用 Windows 完全不会有这样的困扰，管你是 QQ，PS 还是 mysql，基本上通过一个安装包解决问题。

下面我们来梳理下混乱之下到底是些什么？\
&#x20;以下描述基于 Ubuntu18.04 安装 redis 为例， 当我们执行 `sudo apt install redis` 的时候，系统做了如下事件：

1. 创建了 `redis` user 和 `redis` group， 同时指定其 home 为 `/var/lib/redis`，我们 执行 `cat /etc/passwd | grep redis`， 看到以下结果，注意该用户无法登录系统

```
    redis:x:124:129::/var/lib/redis:/usr/sbin/nologin
```

1. 添加了 `/etc/init.d/redis-server` 的可执行脚本
2. 添加了 `/etc/rcx.d/S01redis-server` 的软链
3. 添加了 `/lib/systemd/system//redis-server.service` 文件，供 systemd 调度
4. 添加了 `/etc/systemd/system/multi-user.target.wants/redis-server.service -> /lib/systemd/system/redis-server.service` 的软链，用于开机启动
5. 将 `redis-server`、`redis-cli` 等命令 放到 `/usr/bin` 目录下
6. 启动 redis-server 服务，这个时候会关联一下目录
   * &#x20;`/etc/redis/redis.conf` redis 默认配置文件
   * &#x20;`/var/lib/redis` redis 用户家目录，默认情况下，redis 的数据会放在这里
   * &#x20;`/var/log/redis` 默认 redis 的日志会放在这里
   * &#x20;`/run/redis.pid` 默认 redis daemon 的 pid 放在这里

如果这个软件按照规范来的话，通常是做了这些事情。当然不同的软件会有所差别，这个差别可能是未知的，你如果想要搞明白，需要去花好多时间研究。

作为一个工程师，笔者以为信心源于全面的测试和对细节的掌控，所以以上的安装方式，我其实是惧怕的。\
&#x20;出于以上种种原因吧，笔者更倾向于下载二进制文件解压，尽量像在 Windows 下，采用单一目录安装。其实这种安装方式，在 Linux 下也越发的常见了，比如 hadoop 生态组件，比如 elasticsearch 生态的组件，都是采用这种方式安装的。\
&#x20;我们来看下两种安装方式目录结构的区别

`apt install` 的方式，这种方式将应用程序相关文件拆开，放到 Linux 下的各种目录下，其实这种方式我觉得类似于将一个项目完全从纯技术角度分包，当项目小的时候还好，但是项目很大时，一切都会变得恶心。目录结构如下：

```
# tree /var/log
/var/log
├── redis
│   └── redis-server.log
├── mongod
│   └── mongod.log

# tree /var/lib
/var/lib
├── redis
│   └── .bashrc
├── mongod
│   └── .bashrc

# tree /run
/run
├── redis
│   └── redis.pid
├── mongod
│   └── mongod.pid


# tree /etc
/etc
├── redis
│   └── redis.conf
├── mongod
│   └── mongod.conf
```

第二种方式是先将某软件相关的文件聚合到自己的目录下，然后在自己的目录下，从技术角度再分包。我们采用这种方式组织文件。其实也体现了内聚的思想。类似于，将整个项目根据不同的 feature，先分包，然后在 feature 包内，再从技术角度出发细分。如果项目足够复杂，当然这种是更好的。其目录结构如下：

```
# tree /opt
/opt
├── redis
│   ├── data
│   │   └── redis.data
│   ├── logs
│   │   └── redis.log
│   ├── redis.conf
│   ├── redis.pid
├── mongodb
    ├── data
    │   └── mongod.data
    ├── logs
    │   └── mongod.log
    ├── mongod.conf
    ├── mongod.pid

```

### 部署 mongodb

下面，我们以 mongodb 为例，在 Ubuntu18.04 上详细的走一遍流程。mongodb 官方提供了二进制文件下载，[在这里](https://links.jianshu.com/go?to=https%3A%2F%2Fwww.mongodb.org%2Fdl%2Flinux)。\
&#x20;下面我先说下我自己的部署标准

1. 该软件整体安装在 `/opt` 目录下
2. 相关的可执行文件如 `mongo`、`mongod` 全局可用
3. 配置到系统服务，可开机启动

了解了以上思路，我们正式开始部署流程，注意以下所有操作都是在 root 账户下，后面不再赘述。

#### 1. 先解压从官网下载的二进制文件包，在 `/opt` 目录下解压后，解压后文件目录如下

```
# tree /opt
/opt
└── mongodb-linux-x86_64-3.6.4
    ├── bin
    │   ├── bsondump
    │   ├── install_compass
    │   ├── mongo
    │   ├── mongod
    │   ├── mongodump
    │   ├── mongoexport
    │   ├── mongofiles
    │   ├── mongoimport
    │   ├── mongoperf
    │   ├── mongoreplay
    │   ├── mongorestore
    │   ├── mongos
    │   ├── mongostat
    │   └── mongotop
    ├── GNU-AGPL-3.0
    ├── MPL-2
    ├── README
    └── THIRD-PARTY-NOTICES
```

#### 2. 创建相关文件及文件夹

```
# 进入到 mongodb 安装目录
cd /opt/mongodb-linux-x86_64-3.6.4

# 创建将来存放 mongo 数据的文件夹
mkdir data 

# 创建日志文件夹
mkdir logs

# 创建并编写配置文件
vim mongod.conf

# 创建并编写 mongod.service, 用于将来注册进 systemd
vim mongod.service
```

配置文件 mongod.conf 内容如下

```
# mongod.conf
# Where to store the data.
dbpath = /opt/mongodb/data
storageEngine = wiredTiger
directoryperdb = true

# where to log
logpath = /opt/mongodb/logs/mongodb.log
logappend=true
logRotate = reopen

port = 27017

# daemon
fork = true

# Enable journaling, http://www.mongodb.org/display/DOCS/Journaling
journal=true

# Turn on/off security.  Off is currently the default
noauth = true
# auth = true
# keyFile = /srv/mongodb/conf/rs.key

# crawler repl
# replSet=rs_crawler

pidfilepath = /opt/mongodb/mongod.pid
```

Service 配置文件 mongod.service 内容如下

```
# mongod.service
[Unit]
Description=mongodb server
After=network.target

[Service]
Type=simple
User=mongodb
Group=mongodb
SyslogIdentifier=mongodb
LimitNOFILE=65536
ExecStart=/opt/mongodb/bin/mongod -f /opt/mongodb/mongod.conf
ExecStop=/bin/kill -s TERM $MAINPID
PIDFile=/opt/mongodb/mongod.pid
Restart=always

[Install]
WantedBy=multi-user.target
```

#### 3. 接下来为了解耦或是方便，我们可以创建一些软链接

```
ln -s /opt/mongodb-linux-x86_64-3.6.4 /opt/mongodb
ln -s /opt/mongodb-linux-x86_64-3.6.4/bin/mongod /usr/local/bin/mongod
ln -s /opt/mongodb-linux-x86_64-3.6.4/bin/mongo /usr/local/bin/mongo

# /opt/mongodb-linux-x86_64-3.6.4/bin 下的其它命令是否创建软链，根据自己需求
```

我们注意到，mongodb 的所有相关文件都在自己的目录内，如果外部引用的话，都是通过软链接的方式，这类似于设计模式中的桥接或是代理。

完成以上步骤后， 执行 `mongod -f /opt/mongodb/mongod.conf`, 启动 mongodb，如果一切正常的话，mongodb 正常启动，此时目录结构如下，所有相关的内容全部聚合到一个目录下。

怎么样，很清晰吧 O(∩\_∩)O

```
# tree /opt
/opt
├── mongodb -> mongodb-linux-x86_64-3.6.4
└── mongodb-linux-x86_64-3.6.4
    ├── bin
    │   ├── bsondump
    │   ├── install_compass
    │   ├── mongo
    │   ├── mongod
    │   ├── mongodump
    │   ├── mongoexport
    │   ├── mongofiles
    │   ├── mongoimport
    │   ├── mongoperf
    │   ├── mongoreplay
    │   ├── mongorestore
    │   ├── mongos
    │   ├── mongostat
    │   └── mongotop
    ├── data
    │   ├── admin
    │   │   ├── collection-0--3307101973996970816.wt
    │   │   └── index-1--3307101973996970816.wt
    │   ├── diagnostic.data
    │   │   ├── metrics.2019-10-15T11-05-02Z-00000
    │   │   └── metrics.2019-10-15T11-05-20Z-00000
    │   ├── journal
    │   │   ├── WiredTigerLog.0000000002
    │   │   ├── WiredTigerPreplog.0000000001
    │   │   └── WiredTigerPreplog.0000000002
    │   ├── local
    │   │   ├── collection-2--3307101973996970816.wt
    │   │   └── index-3--3307101973996970816.wt
    │   ├── _mdb_catalog.wt
    │   ├── mongod.lock
    │   ├── sizeStorer.wt
    │   ├── storage.bson
    │   ├── WiredTiger
    │   ├── WiredTigerLAS.wt
    │   ├── WiredTiger.lock
    │   ├── WiredTiger.turtle
    │   └── WiredTiger.wt
    ├── GNU-AGPL-3.0
    ├── logs
    │   └── mongodb.log
    ├── mongod.conf
    ├── mongod.pid
    ├── mongod.service
    ├── MPL-2
    ├── README
    └── THIRD-PARTY-NOTICES
```

通过 `mongod -f /opt/mongodb/mongod.conf` 启动的服务在后台运行，通过 `ps` 找到 pid 先将其杀掉，然后进行下一步，否则会导致后续配置失败。

#### 4. 注册到系统服务

```
# 创建 mongodb 的用户，通常这种系统服务，出于安全考虑，我们不会直接以 root 用户运行
# 我们首先创建一个 mongodb 用户，将来用这个用户运行 mongodb
# 下面的命令创建了 mongodb user，同时创建了 mongodb group，指定该用户无法登录系统，同时创建了家目录，此处家目录其实可以不创建，因为我们的部署流程中没用到 mongodb 的家目录
useradd mongodb --home-dir /var/lib/mongodb --shell /usr/sbin/nologin --create-home --user-group

# 将 mongodb 的安装目录 全部交给 mongodb 用户
chown -R mongodb:mongodb /opt/mongodb /opt/mongodb-linux-x86_64-3.6.4

# 将 service 文件软链到 /lib/systemd/system， 注册成系统服务
ln -s /opt/mongodb/mongod.service /lib/systemd/system/mongod.service
# 重载 systemd 列表
systemctl daemon-reload

# 清除掉刚刚启动 mongodb 在安装目录下创建的文件，因为刚才是以 root 用户启动的，里面创建的文件都属于 root，不删除的话，服务起不来
rm -rf /opt/mongodb/mongod.pid /opt/mongodb/logs/* /opt/mongodb/data/* 

# 开启服务， 如果一切正常，显示如下
systemctl start mongod.service

    ● mongod.service - mongodb server
       Loaded: loaded (/opt/mongodb/mongod.service; disabled; vendor preset: enabled)
       Active: active (running) since Tue 2019-10-15 19:29:10 CST; 2s ago
     Main PID: 21850 (mongod)
        Tasks: 23 (limit: 478)
       CGroup: /system.slice/mongod.service
               └─21850 /opt/mongodb/bin/mongod -f /opt/mongodb/mongod.conf

    Oct 15 19:29:10 iZ8vb9wvg14ypn9rjj3sdpZ systemd[1]: Started mongodb server.

# 设置开机启动
systemctl enable mongod.service
```

### 结语

至此，我们尽量模拟了 `apt install ...` 的方式部署好了 mongodb，并逐步拆解成单一步骤，感觉一切尽在掌握。\
&#x20;但是其实，其中还是有些不同的。\
&#x20;上面我们在注册系统服务的时候提到了 systemd， init.d, 还用到了 `systemctl` 命令， 这到底是什么呢？\
&#x20;其中的不同恰好涉及到这些知识点，我们 [下篇文章](https://www.jianshu.com/p/d690f348e261) 详细说明。
