总体来说 19 年到现在大环境不太好,各行各业都受影响,前段时间听说苏宁开启全员卖货模式,连副总裁都开始在朋友圈卖内裤了。
疫情期间让员工主动离职,不给赔偿,更可恶的是 HR 私自登录员工系统提交离职报告。我是比较幸运的,有稳定的工作,有时候也会做一些游戏代练挣一些外快,反正吃饱饭是没问题了,哈哈。
本文盘点一下,我在疫情参加的面试被问的问题。
总体来说 19 年到现在大环境不太好,各行各业都受影响,前段时间听说苏宁开启全员卖货模式,连副总裁都开始在朋友圈卖内裤了,哈哈哈,真是患难见忠臣啊,当然也听说 xx 二手车强制转岗、降薪,变相裁员。
疫情期间让员工主动离职,不给赔偿,更可恶的是 HR 私自登录员工系统提交离职报告。我微信里的一个做二手服务器回收的老哥,18 年收了两千多万的服务器,去年一半都不到;还有的朋友,单位开不出来工资,生活也受到很大的影响。总而言之,我是比较幸运的,有稳定的工作,有时候也会做一些游戏代练挣一些外快,反正吃饱饭是没问题了,哈哈。
来说说自己的情况吧,2.3 号回来就一直在工作,偶尔也会登录 Boss 直聘投投简历,看看用人单位有哪些技能要求,但是很无奈,要么多半是外包,要么就是已读不回,还有的拿了你的简历就没影了,曾经我手机上唯一的求职软件,我也要卸载了。想想这些年,3584 次沟通,投递 779 份简历,也算是给我的运维经历画上完美的句号了,如图所示:
前一段时间正式开始启用智联招聘,哇,真的是让我眼前一亮,以前不可以和 HR 沟通,现在也可以沟通了,而且每日签到功能还有各种福利,例如简历超级曝光、优先推荐、offer 迷你吸铁石、精美简历模板、超级面霸养成,而且你一投递简历,很快就有 HR 联系你,总体体验感是非常棒的,我也来说说我找工作的要求吧:
接下来咱们就开始聊技术吧,我把面试题发出来,大家参考一下:
Git 是分布式的,而 Svn 不是分布的;
Git 把内容按元数据方式存储,而 SVN 是按文件;
Git 没有一个全局版本号,而 SVN 有:目前为止这是跟 SVN 相比 Git 缺少的最大的一个特征;
Git 的内容的完整性要优于 SVN: GIT 的内容存储使用的是 SHA-1 哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏;
Git 下载下来后,在 OffLine 状态下可以看到所有的 Log,SVN 不可以;SVN 必须先 Update 才能 Commit,忘记了合并时就会出现一些错误,git 还是比较少的出现这种情况。
克隆一份全新的目录以同样拥有五个分支来说,SVN 是同时复製 5 个版本的文件,也就是说重复五次同样的动作。而 Git 只是获取文件的每个版本的 元素,然后只载入主要的分支(master)在我的经验,克隆一个拥有将近一万个提交(commit),五个分支,每个分支有大约 1500 个文件的 SVN,耗了将近一个小时!而 Git 只用了区区的 1 分钟!
版本库(repository):SVN 只能有一个指定中央版本库。当这个中央版本库有问题时,所有工作成员都一起瘫痪直到版本库维修完毕或者新的版本库设立完成。而 Git 可以有无限个版本库。或者,更正确的说法,每一个 Git 都是一个版本库,区别是它们是否拥有活跃目录(Git Working Tree)。如果主要版本库(例如:置於 GitHub 的版本库)发生了什麼事,工作成员仍然可以在自己的本地版本库(local repository)提交,等待主要版本库恢复即可。工作成员也可以提交到其他的版本库!
分支(Branch)在 SVN,分支是一个完整的目录。且这个目录拥有完整的实际文件。如果工作成员想要开啟新的分支,那将会影响“全世界”!每个人都会拥有和你一样的分支。如果你的分支是用来进行破坏工作(安检测试),那将会像传染病一样,你改一个分支,还得让其他人重新切分支重新下载,十分狗血。而 Git,每个工作成员可以任意在自己的本地版本库开啟无限个分支。
Git 的分支名是可以使用不同名字的。例如:我的本地分支名为 OK,而在主要版本库的名字其实是 master。
提交(Commit)在 SVN,当你提交你的完成品时,它将直接记录到中央版本库。当你发现你的完成品存在严重问题时,你已经无法阻止事情的发生了。如果网路中断,你根本没办法提交!而 Git 的提交完全属於本地版本库的活动。而你只需“推”(git push)到主要版本库即可。Git 的“推”其实是在执行“同步”(Sync)。
总结:SVN 的特点是简单,只是需要一个放代码的地方时用是 OK 的。
Git 的特点版本控制可以不依赖网络做任何事情,对分支和合并有更好的支持(当然这是开发者最关心的地方),不过想各位能更好使用它,需要花点时间尝试一下。
master 将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events)slave 将 master 的 binary log events 拷贝到它的中继日志 (relay log) slave 重做中继日志中的事件,将改变反映它自己的数据。或从库生成两个线程,一个 I/O 线程,一个 SQL 线程;
i/o 线程去请求主库 的 binlog,并将得到的 binlog 日志写到 relay log(中继日志) 文件中;
主库会生成一个 log dump 线程,用来给从库 i/o 线程传 binlog;
SQL 线程,会读取 relay log 文件中的日志,并解析成具体操作,来实现主从的操作一致,而最终数据一致;
先上 Master 库:
mysql>show processlist;
查看下进程是否 Sleep 太多。发现很正常。
show master status;
也正常。
mysql> show master status;+-------------------+----------+--------------+-------------------------------+| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |+-------------------+----------+--------------+-------------------------------+| mysqld-bin.000001 | 3260 | | mysql,test,information_schema |+-------------------+----------+--------------+-------------------------------+1 row in set (0.00 sec)
再到 Slave 上查看
mysql> show slave status\G Slave_IO_Running: YesSlave_SQL_Running: No
可见是 Slave 不同步
下面介绍两种解决方法:
解决:
stop slave;#表示跳过一步错误,后面的数字可变set global sql_slave_skip_counter =1;start slave;
之后再用mysql> show slave status\G
查看:
Slave_IO_Running: YesSlave_SQL_Running: Yes
ok,现在主从同步状态正常了。。。
该方法适用于主从库数据相差较大,或者要求数据完全统一的情况解决步骤如下:
mysql> flush tables with read lock;
注意:该处是锁定为只读状态,语句不区分大小写
#把数据备份到 mysql.bak.sql 文件[root@server01 mysql]#mysqldump -uroot -p -hlocalhost > mysql.bak.sql
这里注意一点:数据库备份一定要定期进行,可以用 shell 脚本或者 python 脚本,都比较方便,确保数据万无一失
mysql> show master status;+-------------------+----------+--------------+-------------------------------+| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |+-------------------+----------+--------------+-------------------------------+| mysqld-bin.000001 | 3260 | | mysql,test,information_schema |+-------------------+----------+--------------+-------------------------------+1 row in set (0.00 sec)
#使用 scp 命令[root@server01 mysql]# scp mysql.bak.sql [email protected]:/tmp/
mysql> stop slave;
mysql> source /tmp/mysql.bak.sql
change master to master_host = '192.168.128.100', master_user = 'rsync', master_port=3306, master_password='', master_log_file = 'mysqld-bin.000001', master_log_pos=3260;
mysql> start slave;
mysql> show slave status\G
查看:
Slave_IO_Running: YesSlave_SQL_Running: Yes
好了,同步完成啦。
如果延迟比较大,就先确认以下几个因素:
另外,2 个可以减少延迟的参数:
–slave-net-timeout=seconds 单位为秒,默认设置为 3600 秒#参数含义:当 slave 从主数据库读取 log 数据失败后,等待多久重新建立连接并获取数据– master-connect-retry=seconds?单位为秒?默认设置为 60 秒#参数含义:当重新建立主从连接时,如果连接建立失败,间隔多久后重试。
通常配置以上 2 个参数可以减少网络问题导致的主从数据同步延迟MySQL 数据库主从同步延迟解决方案;
最简单的减少 slave 同步延时的方案就是在架构上做优化,尽量让主库的 DDL 快速执行。还有就是主库是写,对数据安全性较高,比如sync_binlog=1,innodb_flush_log_at_trx_commit= 1
之类的设置,而 slave 则不需要这么高的数据安全,完全可以讲sync_binlog
设置为 0 或者关闭 binlog,innodb_flushlog
也可以设置为 0 来提高 sql 的执行效率。另外就是使用比主库更好的硬件设备作为 slave。
作为消息队列来说,企业中选择 mq 的还是多数,因为像 Rabbit,Rocket 等 mq 中间件都属于很成熟的产品,性能一般但可靠性较强,而 kafka 原本设计的初衷是日志统计分析,现在基于大数据的背景下也可以做运营数据的分析统计,而 redis 的主要场景是内存数据库,作为消息队列来说可靠性太差,而且速度太依赖网络 IO,在服务器本机上的速度较快,且容易出现数据堆积的问题,在比较轻量的场合下能够适用。
RabbitMQ,遵循 AMQP 协议,由内在高并发的 erlanng 语言开发,用在实时的对可靠性要求比较高的消息传递上。
kafka 是 Linkedin 于 2010 年 12 月份开源的消息发布订阅系统,它主要用于处理活跃的流式数据,大数据量的数据处理上。
ClusterIP集群内部容器访问地址,会生成一个虚拟 IP 与 pod 不在一个网段。
NodePort会在宿主机上映射一个端口,供外部应用访问模式。
Headless CluserIP无头模式,无 serviceip,即把 spec.clusterip 设置为 None 。
LoadBalancer使用外部负载均衡。
pod 内部容器之间,这种情况下容器通讯比较简单,因为 k8s pod 内部容器是共享网络空间的,所以容器直接可以使用 localhost 访问其他容器。k8s 在启动容器的时候会先启动一个 pause 容器,这个容器就是实现这个功能的。
pod 与 pod 容器之间,这种类型又可以分为两种情况:
两个 pod 在一台主机上面,两个 pod 分布在不同主机之上针对第一种情况,就比较简单了,就是 docker 默认的 docker 网桥互连容器。
第二种情况需要更为复杂的网络模型了,k8s 官方推荐的是使用 flannel 组建一个大二层扁平网络,pod 的 ip 分配由 flannel 统一分配,通讯过程也是走 flannel 的网桥。
存活性探针(liveness probes)和就绪性探针(readiness probes)用户通过 Liveness 探测可以告诉 Kubernetes 什么时候通过重启容器实现自愈;Readiness 探测则是告诉 Kubernetes 什么时候可以将容器加入到 Service 负载均衡池中,对外提供服务。语法是一样的。
Pod --Pending 状态
Pending 说明 Pod 还没有调度到某个 Node 上面。可以通过 kubectl describe pod
资源不足,集群内所有的 Node 都不满足该 Pod 请求的 CPU、内存、GPU 等资源。
HostPort 已被占用,通常推荐使用 Service 对外开放服务端口
Pod --Waiting 或 ContainerCreating 状态
首先还是通过 kubectl describe pod
镜像拉取失败,比如配置了镜像错误、Kubelet 无法访问镜像、私有镜像的密钥配置错误、镜像太大,拉取超时等。
CNI 网络错误,一般需要检查 CNI 网络插件的配置,比如无法配置 Pod 、无法分配 IP 地址
容器无法启动,需要检查是否打包了正确的镜像或者是否配置了正确的容器参数。
Pod -- ImagePullBackOff 状态
这也是我们测试环境常见的,通常是镜像拉取失败。这种情况可以使用 docker pull
或者 docker images | grep
Pod -- CrashLoopBackOff 状态
CrashLoopBackOff 状态说明容器曾经启动了,但可能又异常退出了。此时可以先查看一下容器的日志kubectl logs
这里可以发现一些容器退出的原因,比如
Pod --Error 状态
通常处于 Error 状态说明 Pod 启动过程中发生了错误。常见的原因包括
依赖的 ConfigMap、Secret 或者 PV 等不存在。请求的资源超过了管理员设置的限制,比如超过了 LimitRange 等。
违反集群的安全策略,比如违反了 PodSecurityPolicy 等。
容器无权操作集群内的资源,比如开启 RBAC 后,需要为 ServiceAccount 配置角色绑定。
Pod --Terminating 或 Unknown 状态从 v1.5 开始,Kubernetes 不会因为 Node 失联而删除其上正在运行的 Pod,而是将其标记为 Terminating 或 Unknown 状态。想要删除这些状态的 Pod 有三种方法:
从集群中删除该 Node。使用公有云时,kube-controller-manager 会在 VM 删除后自动删除对应的 Node。而在物理机部署的集群中,需要管理员手动删除 Node(如 kubectl delete node
Pod -- Evicted 状态
出现这种情况,多见于系统内存或硬盘资源不足,可 df-h 查看 docker 存储所在目录的资源使用情况,如果百分比大于 85%,就要及时清理下资源,尤其是一些大文件、docker 镜像。
对于一个 pod 来说,资源最基础的 2 个的指标就是:CPU 和内存。Kubernetes 提供了个采用 requests 和 limits 两种类型参数对资源进行预分配和使用限制。
limit 会限制 pod 的资源利用:
软连接,其实就是新建立一个文件,这个文件就是专门用来指向别的文件的(那就和 windows 下的快捷方式的那个文件有很接近的意味)。软链接产生的是一个新的文件,但这个文件的作用就是专门指向某个文件的,删了这个软连接文件,那就等于不需要这个连接,和原来的存在的实体原文件没有任何关系,但删除原来的文件,则相应的软连接不可用(cat 那个软链接文件,则提示“没有该文件或目录“)
硬连接是不会建立 inode 的,他只是在文件原来的 inode link count 域再增加 1 而已,也因此硬链接是不可以跨越文件系统的。相反是软连接会重新建立一个 inode,当然 inode 的结构跟其他的不一样,他只是一个指明源文件的字符串信息。一旦删除源文件,那么软连接将变得毫无意义。而硬链接删除的时候,系统调用会检查 inode link count 的数值,如果他大于等于 1,那么 inode 不会被回收。因此文件的内容不会被删除。
硬链接实际上是为文件建一个别名,链接文件和原文件实际上是同一个文件。可以通过 ls -i 来查看一下,这两个文件的 inode 号是同一个,说明它们是同一个文件;而软链接建立的是一个指向,即链接文件内的内容是指向原文件的指针,它们是两个文件。
软链接可以跨文件系统,硬链接不可以;软链接可以对一个不存在的文件名(filename)进行链接(当然此时如果你 vi 这个软链接文件,linux 会自动新建一个文件名为 filename 的文件),硬链接不可以(其文件必须存在,inode 必须存在);软链接可以对目录进行连接,硬链接不可以。两种链接都可以通过命令 ln 来创建。ln 默认创建的是硬链接。使用 -s 开关可以创建软链接
vi /etc/fstabUUID=904C23B64C23964E /media/aborn/data ntfs defaults 0 2
其中第一列为 UUID, 第二列为挂载目录(该目录必须为空目录,必须存在),第三列为文件系统类型,第四列为参数,第五列 0 表示不备份,最后一列必须为2或 0(除非引导分区为 1)
例如/目录
grep -rn "A" ./
或
find ./ -name "*.*" | xargs grep "A"
例如 a 进程ps -ef | grep "^a" | grep -v grep | cut -c 9-15 | xargs kill -9
或
ps x | grep a | grep -v grep | awk '{print $1}' | xargs kill -9
Linux 服务管理两种方式 service 和 systemctl
systemd 是 Linux 系统最新的初始化系统(init),作用是提高系统的启动速度,尽可能启动较少的进程,尽可能更多进程并发启动。
systemd 对应的进程管理命令是 systemctl
systemctl 命令兼容了 service, systemctl 命令管理 systemd 的资源 Unit
iptables 其实不是真正的防火墙,我们可以把它理解成一个客户端代理,用户通过 iptables 这个代理,将用户的安全设定执行到对应的"安全框架"中,这个"安全框架"才是真正的防火墙,这个框架的名字叫 netfilteriptables 其实是一个命令行工具,位于用户空间,我们用这个工具操作真正的框架。
所以说,虽然我们使用 service iptables start 启动 iptables"服务",但是其实准确的来说,iptables 并没有一个守护进程,所以并不能算是真正意义上的服务,而应该算是内核提供的功能。
iptables 有 4 表 5 链:
用 UltraISO PE (光软碟通)软件打开 iso 镜像文件就可以填加了
场景一:无错误状态码无错误状态码,多数情况下是“ERRCONNECTIONTIMED_OUT”问题。
出现 ERR_CONNECTION_TIMED_OUT
错误原因,可以总结为以下 5 点:
排查思路说明:
使用命令 telnet IP Port 进行测试
如果端口是通的,则排查查看服务器带宽是否跑满、是否有***是否使用的账号处于欠费状态
如果端口不通,则排查:
场景二:网站访问异常代码 4XX。
排查思路:
例如:网站数据目录等(本案例是客户机器迁移之后,由于阿里磁盘的特性导致盘符改变,客户的数据盘挂载不上,etcfstab 和盘符不匹配)
问题定位到之后,重新以正确的方式挂载客户网站数据;重启服务,问题得以圆满解决;
基于类似问题还可以关注下目录权限等问题。
经验汇总:
针对网站访问报错问题几点排查建议:
案例:报错“404 Not Found”
问题原因:
404 报错的具体原因是访问的路径 url 目录在服务上没有找到,如果直接使用 ip 或者域名访问,那么实际访问的页面是站点根目录下的默认文件(配置文件中 index 后指定的文件),如果服务器上站点根目录上没有这个文件,则会出现 404 错误。
排查步骤:
测试环境:Nginx 环境
站点跟目录/www/下没有默认 index.html 文件,访问 ip/inde.html,会报 Not Found 错误。
备注:Apache 环境下,404 错误也是同样的原理进行排查。
Service 是一种抽象的对象,它定义了一组 Pod 的逻辑集合和一个用于访问它们的策略,一个 Serivce 下面包含的 Pod 集合一般是由 Label Selector 来决定的。假如我们后端运行了 3 个副本,这些副本都是可以替代的,因为前端并不关心它们使用的是哪一个后端服务。
尽管由于各种原因后端的 Pod 集合会发生变化,但是前端却不需要知道这些变化,也不需要自己用一个列表来记录这些后端的服务,Service 的这种抽象就可以帮我们达到这种解耦的目的。
service 为后端 pod 提供一组负载均衡代理
三种 IP:
首先,Node IP 是 Kubernetes 集群中节点的物理网卡 IP 地址(一般为内网),所有属于这个网络的服务器之间都可以直接通信,所以 Kubernetes 集群外要想访问 Kubernetes 集群内部的某个节点或者服务,肯定得通过 Node IP 进行通信(这个时候一般是通过外网 IP 了)
然后 Pod IP 是每个 Pod 的 IP 地址,它是 Docker Engine 根据 docker0 网桥的 IP 地址段进行分配的(我们这里使用的是 flannel 这种网络插件保证所有节点的 Pod IP 不会冲突)
最后 Cluster IP 是一个虚拟的 IP,仅仅作用于 Kubernetes Service 这个对象,由 Kubernetes 自己来进行管理和分配地址,当然我们也无法 ping 这个地址,他没有一个真正的实体对象来响应,他只能结合 Service Port 来组成一个可以通信的服务。
定义 Service
定义 Service 的方式和各种资源对象的方式类型一样,假定我们有一组 Pod 服务,它们对外暴露了 80 端口,同时都被打上了 app=myapp 这样的标签,那么我们就可以像下面这样来定义一个 Service 对象:
pod 示例:
apiVersion: apps/v1kind: Deploymentmetadata: name: testspec: selector: matchLabels: app: myapp replicas: 3 template: metadata: labels: app: myapp spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80
service 基于 pod 的示例:
apiVersion: v1kind: Servicemetadata: name: myservicespec: selector: app: myapp ports: - protocol: TCP port: 80 targetPort: 80
然后通过的使用 kubectl create -f myservice.yaml 就可以创建一个名为 myservice 的 Service 对象,它会将请求代理到使用 TCP 端口为 80,具有标签 app=myapp 的 Pod 上,这个 Service 会被系统分配一个我们上面说的 Cluster IP,该 Service 还会持续的监听 selector 下面的 Pod,会把这些 Pod 信息更新到一个名为 myservice 的 Endpoints 对象上去,这个对象就类似于我们上面说的 Pod 集合了。
需要注意的是,Service 能够将一个接收端口映射到任意的 targetPort。 默认情况下,targetPort 将被设置为与 port 字段相同的值。 可能更有趣的是,targetPort 可以是一个字符串,引用了 backend Pod 的一个端口的名称。 因实际指派给该端口名称的端口号,在每个 backend Pod 中可能并不相同,所以对于部署和设计 Service ,这种方式会提供更大的灵活性。另外 Service 能够支持 TCP 和 UDP 协议,默认是 TCP 协议。
kube-proxy
在 Kubernetes 集群中,每个 Node 会运行一个 kube-proxy 进程, 负责为 Service 实现一种 VIP(虚拟 IP,就是我们上面说的 clusterIP)的代理形式,现在的 Kubernetes 中默认是使用的 iptables 这种模式来代理。这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。
对每个 Service,它会添加上 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某一个个上面。 对于每个 Endpoints 对象,它也会安装 iptables 规则,这个规则会选择一个 backend Pod。
默认的策略是,随机选择一个 backend。 我们也可以实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为 "ClientIP" (默认值为 "None")。
另外需要了解的是如果最开始选择的 Pod 没有响应,iptables 代理能够自动地重试另一个 Pod,所以它需要依赖 readiness probes。
Service 类型
在定义 Service 的时候可以指定一个自己需要的类型的 Service,如果不指定的话默认是 ClusterIP 类型。
可以使用的服务类型如下:
NodePort 类型
如果设置 type 的值为 "NodePort",Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口(每个 Node 上的同一端口)代理到 Service。该端口将通过 Service 的 spec.ports[*].nodePort 字段被指定,如果不指定的话会自动生成一个端口。
需要注意的是,Service 将能够通过 :spec.ports[].nodePort 和 spec.clusterIp:spec.ports[].port 而对外可见。
接下来创建一个 NodePort 的服务
apiVersion: v1kind: Servicemetadata: name: myservicespec: selector: app: myapp type: NodePort ports: - protocol: TCP port: 80 targetPort: 80 name: myapp-http nodePort: 32560
创建该 Service:
$ kubectl create -f service-demo.yaml
然后我们可以查看 Service 对象信息:
$ kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEkubernetes ClusterIP 10.96.0.1 443/TCP 27dmyservice NodePort 10.104.57.198 80:32560/TCP 14h
可以看到 myservice 的 TYPE 类型已经变成了 NodePort,后面的 PORT(S)部分也多了一个 32560 的随机映射端口。
ExternalName
ExternalName 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。 对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。
kind: ServiceapiVersion: v1metadata: name: my-service namespace: prodspec: type: ExternalName externalName: my.database.example.com
当查询主机 my-service.prod.svc.cluster.local 时,集群的 DNS 服务将返回一个值为 my.database.example.com 的 CNAME 记录。 访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。
如果后续决定要将数据库迁移到 Kubernetes 集群中,可以启动对应****的 Pod,增加合适的 Selector 或 Endpoint,修改 Service 的 type,完全不需要修改调用的代码,这样就完全解耦了。
k8s 端口被占用报错执行以下命令:kubeadm reset
笔者回答:
jenkins 配置好代码路径(SVN 或 GIT),然后拉代码,打 tag。需要编译就编译,编译之后推送到发布服务器(jenkins 里面可以调脚本),然后从分发服务器往下分发到业务服务器上。
如果是 php 的项目,可以 rsync 上线,但是 php 也可以用 Jenkins 来操作,php7 之后也是支持编译运行,这样上线之后运行效率更快,而且一定程度上保证了代码的安全。
前段时间,已经确定入职单位了,然后石墨文档给我发来入职邀请,我何德何能啊,真的是很感动,以下是他们技术面试官对我的评价,哈哈,装个 13。
阅读全文: http://gitbook.cn/gitchat/activity/5ebb6dfbbea81111dcedd92c
您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。