ubuntu(国内镜像)
由于网内网络环境的问题,ubuntu不换源的话基本没法用,我们首先参考普通
ubuntu 系统如何换源,然后将其转换成命令形式,便于应用于 Dockerfile
中。
随便在网上搜
"ubuntu换源",可以搜出一大堆教程,这里我们直接参考阿里云开源镜像站的官方教程 ,官方教程中给出了操作办法,就是手动将所有的源地址替换为阿里云的镜像地址:
首先我们使用 docker 跑一个 ubuntu 的镜像看下基本情况:
1 docker run -it --rm ubuntu:20.04 /bin/bash
注:后面也会出写一个笔记记录常用的 docker 命令。
在 ubuntu 下使用 sudo apt-get install xxx 安装软件,所有
apt 相关的配置项存储在 /etc/apt
文件夹下,其内容如下
其中 sources.list 以及 sources.list.d
就涉及软件源的配置信息,sources.list
用来存储默认的软件源信息(或者说官方源),而 sources.list.d
则用来存储第三方软件源,例如 docker 软件源、nvidia
驱动源等,一般安装软件我们只需要配置 sources.list
即可,第三方源的配置和官方源的配置差不太多。
使用 cat 命令查看 sources.list
文件内容(较长,可能显示不全)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 root@302e32342092:/etc/apt# cat sources.list # See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to # newer versions of the distribution. deb http://archive.ubuntu.com/ubuntu/ focal main restricted # deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted ... deb http://security.ubuntu.com/ubuntu/ focal-security main restricted # deb-src http://security.ubuntu.com/ubuntu/ focal-security main restricted deb http://security.ubuntu.com/ubuntu/ focal-security universe # deb-src http://security.ubuntu.com/ubuntu/ focal-security universe deb http://security.ubuntu.com/ubuntu/ focal-security multiverse # deb-src http://security.ubuntu.com/ubuntu/ focal-security multiverse
我们可以看到其软件源就是通过以下格式指定的
从源的URL链接中不难发现,这些链接实际上都是同一个域名底下的,我们换源是可以只更换前面的域名(即最后一个/之前的所有即可)。
既然都是替换了,那么肯定不能使用文本编辑器一个一个替换,最好的办法就是通过
sed 命令进行替换
sed 命令简单介绍
from GNU sed - GNU
Project - Free Software Foundation
sed (stream editor) is a non-interactive command-line text
editor.
其功能十分强大,目前我们只用到了其 s 命令
1 2 3 s/regexp/replacement/[flags] (substitute) Match the regular-expression against the content of the pattern space. If found, replace matched string with replacement.
例如将文本中所所有的foo替换成bar就可以使用如命令
1 sed -i s/foo/bar/g test.txt
测试如下
其中 -i 表示 inplace,即直接在当前文件中替换文本,最后的
g 表示对所有的匹配项进行替换
对于某些特殊字符,我们可以通过随意指定分隔符来避免频繁的转义,其格式如下所示
1 s<delimiter>regexp<delimiter>replacement<delimiter>[flags]
例如我们需要替换文本中所有的 a/b/c 为
e/f/g,就可以使用如下命令:
1 sed -i s@a/f/g@e/f/g@g test.txt
测试如下
替换链接
那么这样我们就可以我们通过文本替换实现换源,命令如下
1 sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list
这条命令将所有的 archive.ubuntu.com 替换为
mirrors.aliyun.com,对于security.ubuntu.com也是一样,虽然我们并不会对容器中的
ubuntu 进行安全更新,但是 security.ubuntu.com
会严重影响软件源的更新速度,因此也需要一同替换掉,最终完整的换源命令如下:
1 2 sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list && \ sed -i s/security.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list
通过 && 将两条命令合并成一条执行,最后执行
apt-get update 更新软件源即可
此时可以看到软件源更新速度就很快了。
注:上面的方法仅适用于 ubuntu x86_64
架构的版本,对于其他版本(arm64、RISC-V等)则需要使用另外的软件源,不过替换方法都是一致的,找到需要替换的链接,然后通过
sed 进行替换即可。
例如在 arm 版的 ubuntu 中,其软件源为
http://ports.ubuntu.com/ubuntu-ports/,其对应的阿里云镜像为
https://mirrors.aliyun.com/ubuntu-ports/,我们同样使用
sed 进行替换
1 sed -i s/ports.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list
(阿里云镜像地址来自: https://developer.aliyun.com/mirror/ubuntu-ports )
ssh 服务器
启动 docker
容器后,有可能需要连接到容器中修改代码什么的(一般用于深度学习模型训练环境,部署代码的话最好不要这么做),最朴素的办法就是安装一个命令行的文本编辑器,例如
vi,vim,nano 等,命令用熟了还是很轻松的,但是学习成本太高了,后面发现
vscode 的 Remote SSH 插件可以通过 SSH
连接到主机,之后就像本地代码一样进行调试运行等,简直不要太方便(实际上还有更好的办法,直接通过
vscode 的 Dev Containers 插件直接连接到容器中),不过设置 SSH
通用性更高,例如搭建 hadoop 等环境进行测试时就可以使用。
在配置脚本安装之前,我们首先看看手动如何安装 ssh
服务器(openssh-server),直接通过apt安装即可,运行如下命令
1 apt-get install openssh-server
之后提示我们具体需要安装哪些包,以及是否确定安装
但是我们可以看到,建议安装了一堆包,实际上有些包我们可以不装的,可以通过参数
--no-install-recommends 来取消安装这些包,运行命令
1 apt-get install openssh-server --no-install-recommends
结果如下
相比之前我们所需要安装的依赖就少多了
但是还有一个问题,安装过程中有一个
Do you want to continue? [Y/n]
,提示我们是否确定,但是我们需要用命令完成自动化安装过程,安装过程中是完全无交互的,因此需要默认安装,可以通过
-y 来进行默认安装(具体可以参考 apt-get 的 man 页面 )
1 apt-get install openssh-server --no-install-recommends -y
这样当我们输入命令后就会自动安装了。
可以编写一个完整的安装 ssh 的 Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 FROM ubuntu:20.04 RUN sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list && \ sed -i s/security.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list && \ apt-get clean && apt-get update && \ apt-get install --no-install-recommends -y -q \ openssh-server && \ rm -rf /var/lib/apt/lists/* EXPOSE 22 CMD [ "service" ,"ssh" ,"start" ,"-D" ]
其暴露一个端口 22(SSH的默认端口),而且使用
service ssh start -D
来确保命令前台运行(保证容器不会被杀死)
我们先尝试能否成功构建镜像,使用命令
1 docker build -t openssh-server:ubuntu20.04 .
进行构建,然后通过以下命令创建容器
1 docker run -d --rm -p 54132:22 openssh-server:ubuntu20.04
其中 -d 表示 detach,将容器列入后台运行,由于我们在
CMD
中已经指定了一个前台应用,容器不会立刻被docker杀死,--rm
表示容器运行结束后会被直接删除(常用于测试场景),-p 54132:22
表示将宿主机的54132端口绑定到容器的22端口(即SSH端口上,这样我们就可以通过
54132端口连接到容器的SSH服务了)。
最后我们使用 ssh 连接命令尝试连接到容器中
1 ssh -p 54132 root@localhost
首先会提示我们是否需要建立连接(这一步可以说明SSH端口是通的)
但是此时直接提示我们输入密码,但是我们似乎并没有设置过 root
账户的密码,这就意味着我们永远无法连接到容器
我们可以通过一下命令连接到容器进行修改
1 docker exec -it <container-id> /bin/bash
然后输入一下命令设置明文密码
1 echo "root:123" | chpasswd
之后我们再尝试通过 SSH 进行连接,结果如下
提示我们 Permission denied,这是因为 SSH 默认是不允许 root
用户通过密码登录的,我们需要修改 /etc/ssh/sshd_config
通过 cat /etc/ssh/sshd_config 可以看到这一点
再查看 sshd_config 的相关配置选项(查看其相关的 man
页面 即可,注意查看版本,我们使用的 ubuntu
版本比较新,老的配置文档不一定适用)
可以看到 PermitRootLogin
的可选参数有四个:yes,prohibit-password,forced-commands-only,no
注:其中 yes,prohibit-password以及
no 的含义比较直观,但是 forced-commands-only
还不太理解,其限定 root
用户通过秘钥进行登录,但是只能执行特定的 command ,这个
command 实际上需要在 ssh 登录的时候指定,即
authorized_keys 中指定,例如
1 command=”/bin/ps” ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAt0BETg9J6hZb5Kqxy+yfNtKHfwxUELz7PqGtGiM5eNb8DHC8kj02SCFoql5rpaecMGybWRiSK8/k+EsK7TMgd4O+p6WkNyLD3WZrmVzUEPaxAdYf1eeCQooTJ+B1TKXDNlF9t8xTVsHd67HmPWYU6i3+kaDSX7cbrz2ds2zUGSozj1UQ8AJDJMbGOqpjs3nVh2EpSDgY7znqmUDnygVPiM4c3OfEzs5iCxVd4ggpPhH8d0bwy8RmPsooxJYUY4rE1C5iWCvB7P810yUFB0OilxiX9AfZa9shC3n5bqaX0ioY1eC44hFFPL602fJyKMj6w/zxN5aIeFO03Sl9+FU4YQ== root@iZ23wan41azZ
就限定通过 root 私钥登录的时候只能执行
/bin/ps,无法执行其他命令,在运维场景下较为实用
1 echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
然后重启一下 ssh 服务
(此处重启时由于没有前台应用,容器会被 docker
给杀死,再重启一下容器即可)
再尝试登录的时候就可以成功了,提示我们输入密码
每次登录都输入密码还是有点麻烦(可以通过 sshd_config
设置密码可为空来绕过,但是还会提示用户进行交互,无法实现自动化远程登录),为此我们需要配置远程登录,首先通过
ssh-keygen 创建一对秘钥
1 ssh-keygen -t rsa -f ~/key -N ""
其中 -t rsa 指定加密算法为 rsa,-N ""
表示私钥密码为空 ,输出私钥路径为 ~/key
,公钥路径会在私钥后添加 .pub,即 ~/key.pub
,此处创建的秘钥是可以复用的,不过不推荐这么做,安全性较低。
然后将公钥信息拷贝到 ~/.ssh/authorized_keys 里即可
1 2 mkdir ~/.sshcat ~/key.pub >> ~/.ssh/authorized_keys
然后再重启一下 ssh,最终使用私钥登录测试
1 ssh -p 54132 -i key root@localhost
测试结果如下
提示我们权限不对,需要调整 key
的权限,按照下面的方式调整权限即可(适用于Windows,对于 macos 直接使用
chmod 命令修改权限即可)
再尝试登录的时候就可以登陆了
最后给出完整的 Dockerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 FROM ubuntu:20.04 RUN sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list && \ sed -i s/security.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list && \ apt-get clean && apt-get update && \ apt-get install --no-install-recommends -y \ openssh-server && \ rm -rf /var/lib/apt/lists/* COPY public-key.pem /root/public-key.pem COPY private-key.pem /root/private-key.pem RUN mkdir /root/.ssh && \ touch /root/.ssh/authorized_keys && \ chmod 700 /root/.ssh && \ chmod 600 /root/.ssh/authorized_keys && \ cat /root/public-key.pem >> /root/.ssh/authorized_keys EXPOSE 22 CMD [ "service" ,"ssh" ,"start" ,"-D" ]
miniconda(python)
跑深度学习时经常需要配环境,需要安装 cuda 驱动、pytorch
等必要的软件库,虽然使用 anaconda 已经可以很好的管理 python 环境,使用
docker 来运行可以确保代码的可移植性,便于在另外一台机器上跑代码。
anaconda 官方起始有 docker 镜像,但是只有 debian 和
alpine,且通常我们会在 nvidia 的 cuda
容器中进行安装,可以参考官方的安装脚本进行配置,仓库地址:docker-images/miniconda3/debian/Dockerfile
at master · ContinuumIO/docker-images · GitHub
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 FROM debian:bullseye-slimLABEL maintainer="Anaconda, Inc" ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 RUN apt-get update -q && \ apt-get install -q -y --no-install-recommends \ bzip2 \ ca-certificates \ git \ libglib2.0-0 \ libsm6 \ libxext6 \ libxrender1 \ mercurial \ openssh-client \ procps \ subversion \ wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* ENV PATH /opt/conda/bin:$PATHCMD [ "/bin/bash" ] ARG CONDA_VERSION=py310_23.3.1 -0 RUN set -x && \ UNAME_M="$(uname -m) " && \ if [ "${UNAME_M} " = "x86_64" ]; then \ MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION} -Linux-x86_64.sh" ; \ SHA256SUM="aef279d6baea7f67940f16aad17ebe5f6aac97487c7c03466ff01f4819e5a651" ; \ elif [ "${UNAME_M} " = "s390x" ]; then \ MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION} -Linux-s390x.sh" ; \ SHA256SUM="ed4f51afc967e921ff5721151f567a4c43c4288ac93ec2393c6238b8c4891de8" ; \ elif [ "${UNAME_M} " = "aarch64" ]; then \ MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION} -Linux-aarch64.sh" ; \ SHA256SUM="6950c7b1f4f65ce9b87ee1a2d684837771ae7b2e6044e0da9e915d1dee6c924c" ; \ elif [ "${UNAME_M} " = "ppc64le" ]; then \ MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION} -Linux-ppc64le.sh" ; \ SHA256SUM="b3de538cd542bc4f5a2f2d2a79386288d6e04f0e1459755f3cefe64763e51d16" ; \ fi && \ wget "${MINICONDA_URL} " -O miniconda.sh -q && \ echo "${SHA256SUM} miniconda.sh" > shasum && \ if [ "${CONDA_VERSION} " != "latest" ]; then sha256sum --check --status shasum; fi && \ mkdir -p /opt && \ bash miniconda.sh -b -p /opt/conda && \ rm miniconda.sh shasum && \ ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \ echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc && \ echo "conda activate base" >> ~/.bashrc && \ find /opt/conda/ -follow -type f -name '*.a' -delete && \ find /opt/conda/ -follow -type f -name '*.js.map' -delete && \ /opt/conda/bin/conda clean -afy
理解起来很简单,第一步安装 miniconda
的依赖(有些依赖我们并不需要,可根据自己需要调整),第二步根据系统版本选择对应的
miniconda 安装脚本进行安装,将其添加到 /etc/profile.d 和
~/.bashrc中(确保 miniconda
的初始化脚本在用户登录或用户交互前自动执行),最后清理掉无用的数据即可
miniconda 换源
国内 miniconda 也很慢,同样可以对其进行换源,这里参考清华源的换源教程 ,直接在用户目录下创建
.condarc,然后填写如下信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 channels: - defaults show_channel_urls: true default_channels: - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2 custom_channels: conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud pytorch-lts: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud deepmodeling: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/
这一部分最好直接通过 COPY
命令的方式,将本地文件拷贝至容器中进行替换
1 COPY .condarc /root/.condarc
创建新环境并安装包
配置好 miniconda 后我们就可以安装指定版本的 python
并配置我们需要的依赖包了,现在的问题在于,创建新环境后需要激活环境,然后再进行安装,那么如何在
dockerfile 中激活环境呢?可以通过 Dockerfile 中的
SHELL 命令指定所需要使用的 shell。
首先给出完整的 Dockerfile,然后再进行解析
1 2 3 4 5 6 7 8 FROM continuumio/miniconda3:23.3 .1 -0 COPY .condarc /root/.condarc SHELL [ "/bin/bash" ,"--login" ,"-c" ] RUN conda create -n test python=3.9 -y && \ conda activate test && \ conda install numpy -y && \ echo 'conda activate test' >> ~/.bashrc CMD ["/bin/bash" ]
其中的关键在于下面这条语句:
1 SHELL [ "/bin/bash" ,"--login" ,"-c" ]
这一条语句的含义就是我们后面所执行的所有命令,都会通过 SHELL 指定的
shell 进行调用,例如我们后面有语句
RUN conda activate base,其会就等价于
1 /bin/bash --login -c "conda activate base"
关键在于 --login 参数,其强制 Shell
以登录模式调用命令,其关键在于 --login 在启动 shell
时会加载 /etc/profile 以及 /etc/profile.d
中指定的脚本文件,从前面的 Dockerfile 中我们也可以看到
1 ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh
这个 conda.sh 就是用来激活环境的脚本,我们可以通过
/bin/bash --noprofile --norc 来测试
可以看到当我们加载 /etc/profile
后就可以激活环境了,在非交互模式下也是如此。
下面给出一个完整的 pytorch + cuda 的环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 FROM nvidia/cuda:11.3 .1 -cudnn8-runtime-ubuntu20.04 ENV LANG=${LANG:-C.UTF-8 } LC_ALL=${LC_ALL:-C.UTF-8 } TZ=${TZ:-Asia/Shanghai} \ NVIDIA_DRIVER_CAPABILITIES=${NVIDIA_DRIVER_CAPABILITIES:-compute,utility} RUN set -ex && \ sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \ sed -i s@/security.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \ rm -rf /etc/apt/sources.list.d && \ apt-get clean -q && \ apt-get update -q && \ apt-get install -q -y --no-install-recommends \ bzip2 \ ca-certificates \ git \ wget \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* ENV PATH /opt/conda/bin:$PATHARG CONDA_VERSION=py310_23.3.1 -0 RUN set -x && \ UNAME_M="$(uname -m) " && \ if [ "${UNAME_M} " = "x86_64" ]; then \ MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION} -Linux-x86_64.sh" ; \ SHA256SUM="aef279d6baea7f67940f16aad17ebe5f6aac97487c7c03466ff01f4819e5a651" ; \ elif [ "${UNAME_M} " = "s390x" ]; then \ MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION} -Linux-s390x.sh" ; \ SHA256SUM="ed4f51afc967e921ff5721151f567a4c43c4288ac93ec2393c6238b8c4891de8" ; \ elif [ "${UNAME_M} " = "aarch64" ]; then \ MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION} -Linux-aarch64.sh" ; \ SHA256SUM="6950c7b1f4f65ce9b87ee1a2d684837771ae7b2e6044e0da9e915d1dee6c924c" ; \ elif [ "${UNAME_M} " = "ppc64le" ]; then \ MINICONDA_URL="https://repo.anaconda.com/miniconda/Miniconda3-${CONDA_VERSION} -Linux-ppc64le.sh" ; \ SHA256SUM="b3de538cd542bc4f5a2f2d2a79386288d6e04f0e1459755f3cefe64763e51d16" ; \ fi && \ wget "${MINICONDA_URL} " -O miniconda.sh -q && \ echo "${SHA256SUM} miniconda.sh" > shasum && \ if [ "${CONDA_VERSION} " != "latest" ]; then sha256sum --check --status shasum; fi && \ mkdir -p /opt && \ bash miniconda.sh -b -p /opt/conda && \ rm miniconda.sh shasum && \ ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \ echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc && \ echo "conda activate base" >> ~/.bashrc && \ find /opt/conda/ -follow -type f -name '*.a' -delete && \ find /opt/conda/ -follow -type f -name '*.js.map' -delete && \ /opt/conda/bin/conda clean -afy ARG CONDA_ENV_NAME=mlARG CONDA_ENV_PY_VER=3.9 COPY ./condarc /root/.condarc RUN set -x && \ conda create -n ${CONDA_ENV_NAME} python=${CONDA_ENV_PY_VER} -y && \ echo "conda activate ${CONDA_ENV_NAME} " >> ~/.bashrc && \ conda clean -afy SHELL ["/bin/bash" , "--login" , "-c" ] RUN conda activate ${CONDA_ENV_NAME} && \ pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple && \ pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 \ torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113 && \ rm -rf /root/.cache/pip && \ conda clean -afy CMD ["/bin/bash"
构建时间可能较长(pytorch 包太大了),启动时注意参数
--shm_size ,尤其是在多线程数据加载中
总结
个人理解 dockerfile
实际上就是运维脚本的另外一种形式,关键还是在于脚本语言的熟悉程度,linux
命令较为熟悉的话就可以很轻松的构建出合适的镜像。
参考资料
ubuntu镜像_ubuntu下载地址_ubuntu安装教程-阿里巴巴开源镜像站
(aliyun.com)
apt-get(8) - Linux man
page (die.net)
sshd_config(5)
- Linux manual page (man7.org)
sshd_config
中 PermitRootLogin 的探讨_huigher的博客-CSDN博客
Linux下service
xxx start/stop/restart启动服务、关闭服务、重启服务深入理解@_service
start_King-Long的博客-CSDN博客
windows
ssh Permissions for “xxx“ are too
open错误详细解决方案_菜到不知所措的博客-CSDN博客
anaconda |
镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source
Mirror
LPIC Shell 基础:105.1
Lesson 1 (lpi.org)
共享内存简介及docker容器的shm设置与修改_docker
shm_Adenialzz的博客-CSDN博客