Istio 中实现客户端源 IP 的保持

作者

尹烨,腾讯专家工程师, 腾讯云 TCM 产品负责人。在 K8s、Service Mesh 等方面有多年的实践经验。

导语

对于很多后端服务业务,我们都希望得到客户端源 IP。云上的负载均衡器,比如,腾讯云 CLB 支持将客户端源IP传递到后端服务。但在使用 istio 的时候,由于 istio ingressgateway 以及 sidecar 的存在,后端服务如果需要获取客户端源 IP,特别是四层协议,情况会变得比较复杂。

正文

很多业务场景,我们都希望得到客户端源 IP。云上负载均衡器,比如,腾讯云 CLB支持将客户端 IP 传递到后端服务。TKE/TCM 也对该能力做了很好的集成。

但在使用 istio 的时候,由于中间链路上,istio ingressgateway 以及 sidecar 的存在,后端服务如果需要获取客户端 IP,特别是四层协议,情况会变得比较复杂。

对于应用服务来说,它只能看到 Envoy 过来的连接。

一些常见的源 IP 保持方法

先看看一些常见 Loadbalancer/Proxy 的源 IP 保持方法。我们的应用协议一般都是四层、或者七层协议。

七层协议的源 IP 保持

七层的客户端源 IP 保持方式比较简单,最具代表性的是 HTTP 头XFF(X-Forwarded-For),XFF 保存原始客户端的源 IP,并透传到后端,应用可以解析 XFF 头,得到客户端的源 IP。常见的七层代理组件,比如 Nginx、Haproxy,包括 Envoy 都支持该功能。

四层协议的源 IP 保持

DNAT

IPVS/iptables都支持 DNAT,客户端通过 VIP 访问 LB,请求报文到达 LB 时,LB 根据连接调度算法选择一个后端 Server,将报文的目标地址 VIP 改写成选定 Server 的地址,报文的目标端口改写成选定 Server 的相应端口,最后将修改后的报文发送给选出的 Server。由于 LB 在转发报文时,没有修改报文的源 IP,所以,后端 Server 可以看到客户端的源 IP。

Transparent Proxy

Nginx/Haproxy 支持透明代理(Transparent Proxy)。当开启该配置时,LB 与后端服务建立连接时,会将 socket 的源 IP 绑定为客户端的 IP 地址,这里依赖内核TPROXY以及 socket 的 IP_TRANSPARENT 选项。

此外,上面两种方式,后端服务的响应必须经过 LB,再回到 Client,一般还需要策略路由的配合。

TOA

TOA(TCP Option Address)是基于四层协议(TCP)获取真实源 IP 的方法,本质是将源 IP 地址插入 TCP 协议的 Options 字段。这需要内核安装对应的TOA内核模块。

Proxy Protocol

Proxy Protocol是 Haproxy 实现的一个四层源地址保留方案。它的原理特别简单,Proxy 在与后端 Server 建立 TCP 连接后,在发送实际应用数据之前,首先发送一个Proxy Protocol协议头(包括客户端源 IP/端口、目标IP/端口等信息)。这样,后端 server 通过解析协议头获取真实的客户端源 IP 地址。

Proxy Protocol需要 Proxy 和 Server 同时支持该协议。但它却可以实现跨多层中间代理保持源 IP。这有点类似七层 XFF 的设计思想。

istio 中实现源 IP 保持

istio 中,由于 istio ingressgateway 以及 sidecar 的存在,应用要获取客户端源 IP 地址,会变得比较困难。但 Envoy 本身为了支持透明代理,它支持Proxy Protocol,再结合 TPROXY,我们可以在 istio 的服务中获取到源 IP。

东西向流量

istio 东西向服务访问时,由于 Sidecar 的注入,所有进出服务的流量均被 Envoy 拦截代理,然后再由 Envoy 将请求转给应用。所以,应用收到的请求的源地址,是 Envoy 访问过来的地址127.0.0.6

# kubectl -n foo apply -f samples/httpbin/httpbin.yaml
# kubectl -n foo apply -f samples/sleep/sleep.yaml
# kubectl -n foo get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE    IP            NODE           NOMINATED NODE   READINESS GATES
httpbin-74fb669cc6-qvlb5   2/2     Running   0          4m9s   172.17.0.57   10.206.2.144   <none>           <none>
sleep-74b7c4c84c-9nbtr     2/2     Running   0          6s     172.17.0.58   10.206.2.144   <none>           <none># kubectl -n foo exec -it deploy/sleep -c sleep -- curl http://httpbin:8000/ip
{"origin": "127.0.0.6"
}

可以看到,httpbin 看到的源 IP 是127.0.0.6。从 socket 信息,也可以确认这一点。

# kubectl -n foo exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 80
tcp        0      0 172.17.0.57:80          127.0.0.6:56043         TIME_WAIT   -
  • istio 开启 TPROXY

我们修改 httpbin deployment,使用 TPROXY(注意httpbin的 IP 变成了172.17.0.59):

# kubectl patch deployment -n foo httpbin -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar.istio.io/interceptionMode":"TPROXY"}}}}}'
# kubectl -n foo get pods -l app=httpbin  -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP            NODE           NOMINATED NODE   READINESS GATES
httpbin-6565f59ff8-plnn7   2/2     Running   0          43m   172.17.0.59   10.206.2.144   <none>           <none># kubectl -n foo exec -it deploy/sleep -c sleep -- curl http://httpbin:8000/ip
{"origin": "172.17.0.58"
}

可以看到,httpbin 可以得到 sleep 端的真实 IP。

socket 的状态:

# kubectl -n foo exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 80                  
tcp        0      0 172.17.0.59:80          172.17.0.58:35899       ESTABLISHED 9/python3           
tcp        0      0 172.17.0.58:35899       172.17.0.59:80          ESTABLISHED -

第一行是 httpbin 的接收端 socket,第二行是 envoy 的发送端 socket。

httpbin envoy日志:

{"bytes_received":0,"upstream_local_address":"172.17.0.58:35899",
"downstream_remote_address":"172.17.0.58:46864","x_forwarded_for":null,
"path":"/ip","istio_policy_status":null,
"response_code":200,"upstream_service_time":"1",
"authority":"httpbin:8000","start_time":"2022-05-30T02:09:13.892Z",
"downstream_local_address":"172.17.0.59:80","user_agent":"curl/7.81.0-DEV","response_flags":"-",
"upstream_transport_failure_reason":null,"request_id":"2b2ab6cc-78da-95c0-b278-5b3e30b514a0",
"protocol":"HTTP/1.1","requested_server_name":null,"duration":1,"bytes_sent":30,"route_name":"default",
"upstream_cluster":"inbound|80||","upstream_host":"172.17.0.59:80","method":"GET"}

可以看到,

  • downstream_remote_address: 172.17.0.58:46864 ## sleep的地址
  • downstream_local_address: 172.17.0.59:80 ## sleep访问的目标地址
  • upstream_local_address: 172.17.0.58:35899 ## httpbin envoy连接httpbin的local address(为sleep的IP)
  • upstream_host: 172.17.0.59:80 ## httpbin envoy访问的目标地址

httpbin envoy 连接 httpbin 的 local address 为 sleep 的 IP 地址。

南北向流量

对于南北向流量,客户端先请求 CLB,CLB 将请求转给 ingressgateway,再转到后端服务,由于中间多了 ingressgateway 一跳,想要获取客户端源 IP,变得更加困难。

我们以 TCP 协议访问 httpbin:

apiVersion: v1
kind: Service
metadata:name: httpbinnamespace: foolabels:app: httpbinservice: httpbin
spec:ports:- name: tcpport: 8000targetPort: 80selector:app: httpbin
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:name: httpbin-gwnamespace: foo
spec:selector:istio: ingressgateway # use istio default controllerservers:- port:number: 8000name: tcpprotocol: TCPhosts:- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:name: httpbinnamespace: foo
spec:hosts:- "*"gateways:- httpbin-gwtcp:- match:- port: 8000route:- destination:port:number: 8000host: httpbin

通过 ingressgateway 访问 httpbin:

# export GATEWAY_URL=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
# curl http://$GATEWAY_URL:8000/ip
{"origin": "172.17.0.54"
}

可以看到,httpbin 看到的地址是ingressgateway的地址:

# kubectl -n istio-system get pods -l istio=ingressgateway -o wide
NAME                                    READY   STATUS    RESTARTS   AGE     IP            NODE           NOMINATED NODE   READINESS GATES
istio-ingressgateway-5d5b776b7b-pxc2g   1/1     Running   0          3d15h   172.17.0.54   10.206.2.144   <none>           <none>

虽然我们在httpbin envoy开启了透明代理,但 ingressgateway 并不能把 client 的源地址传到httpbin envoy。基于 envoy 实现的Proxy Protocol,可以解决这个问题。

通过 EnvoyFilter 在 ingressgateway 和 httpbin 同时开启Proxy Protocol支持。

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:name: ingressgw-ppnamespace: istio-system
spec:configPatches:- applyTo: CLUSTERpatch:operation: MERGEvalue:transport_socket:name: envoy.transport_sockets.upstream_proxy_protocoltyped_config:"@type": type.googleapis.com/envoy.extensions.transport_sockets.proxy_protocol.v3.ProxyProtocolUpstreamTransportconfig:version: V1transport_socket:name: "envoy.transport_sockets.raw_buffer"workloadSelector:labels:istio: ingressgateway
---
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:name: httpbin-ppnamespace: foo
spec:configPatches:- applyTo: LISTENERmatch:context: SIDECAR_INBOUNDpatch:operation: MERGEvalue:listener_filters:- name: envoy.filters.listener.proxy_protocoltyped_config:"@type": type.googleapis.com/envoy.extensions.filters.listener.proxy_protocol.v3.ProxyProtocol- name: envoy.filters.listener.original_dst- name: envoy.filters.listener.original_srcworkloadSelector:labels:app: httpbin

再次通过 LB 访问 httpbin:

# curl http://$GATEWAY_URL:8000/ip
{"origin": "106.52.131.116"
}

httpbin 得到了客户端的源 IP。

  • ingressgateway envoy 日志
{"istio_policy_status":null,"protocol":null,"bytes_sent":262,"downstream_remote_address":"106.52.131.116:6093","start_time":"2022-05-30T03:33:33.759Z",
"upstream_service_time":null,"authority":null,"requested_server_name":null,"user_agent":null,"request_id":null,
"upstream_cluster":"outbound|8000||httpbin.foo.svc.cluster.local","upstream_transport_failure_reason":null,"duration":37,"response_code":0,
"method":null,"downstream_local_address":"172.17.0.54:8000","route_name":null,"upstream_host":"172.17.0.59:80","bytes_received":83,"path":null,
"x_forwarded_for":null,"upstream_local_address":"172.17.0.54:36162","response_flags":"-"}

可以看到,

  • downstream_remote_address: 106.52.131.116:6093 ## 客户端源地址

  • downstream_local_address: 172.17.0.54:8000

  • upstream_local_address: 172.17.0.54:42122 ## ingressgw local addr

  • upstream_host: 172.17.0.59:80 ## httpbin 地址

  • httpbin envoy日志
{"istio_policy_status":null,"response_flags":"-","protocol":null,"method":null,"upstream_transport_failure_reason":null,"authority":null,"duration":37,
"x_forwarded_for":null,"user_agent":null,"downstream_remote_address":"106.52.131.116:6093","downstream_local_address":"172.17.0.59:80",
"bytes_sent":262,"path":null,"requested_server_name":null,"upstream_service_time":null,"request_id":null,"bytes_received":83,"route_name":null,
"upstream_local_address":"106.52.131.116:34431","upstream_host":"172.17.0.59:80","response_code":0,"start_time":"2022-05-30T03:33:33.759Z","upstream_cluster":"inbound|80||"}

可以看到,

  • downstream_remote_address: 106.52.131.116:6093 ## 客户端源地址
  • downstream_local_address: 172.17.0.59:80 ## httpbin地址
  • upstream_local_address: 106.52.131.116:34431 ## 保留了客户端IP,port不一样
  • upstream_host: 172.17.0.59:80 ## httpbin地址

值得注意的是,httpbin envoyupstream_local_address保留了客户端的 IP,这样,httpbin 看到的源地址 IP,就是客户端的真实 IP。

  • 数据流

相关实现分析

TRPOXY

TPROXY 的内核实现参考net/netfilter/xt_TPROXY.c。

istio-iptables会设置下面的 iptables 规则,给数据报文设置标记。

-A PREROUTING -p tcp -j ISTIO_INBOUND
-A PREROUTING -p tcp -m mark --mark 0x539 -j CONNMARK --save-mark --nfmask 0xffffffff --ctmask 0xffffffff
-A OUTPUT -p tcp -m connmark --mark 0x539 -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff
-A ISTIO_DIVERT -j MARK --set-xmark 0x539/0xffffffff
-A ISTIO_DIVERT -j ACCEPT
-A ISTIO_INBOUND -p tcp -m conntrack --ctstate RELATED,ESTABLISHED -j ISTIO_DIVERT
-A ISTIO_INBOUND -p tcp -j ISTIO_TPROXY
-A ISTIO_TPROXY ! -d 127.0.0.1/32 -p tcp -j TPROXY --on-port 15006 --on-ip 0.0.0.0 --tproxy-mark 0x539/0xffffffff

值得一提的是,TPROXY 不用依赖 NAT,本身就可以实现数据包的重定向。另外,结合策略路由,将非本地的数据包通过本地 lo 路由:

# ip rule list
0:    from all lookup local 
32765:    from all fwmark 0x539 lookup 133 
32766:    from all lookup main 
32767:    from all lookup default # ip route show table 133
local default dev lo scope host

TPROXY 的更多详细介绍参考这里。

Envoy 中 Proxy Protocol 的实现

  • proxy protocol header format

这里使用了Version 1(Human-readable header format),如下:

0000   50 52 4f 58 59 20 54 43 50 34 20 31 30 36 2e 35   PROXY TCP4 106.5
0010   32 2e 31 33 31 2e 31 31 36 20 31 37 32 2e 31 37   2.131.116 172.17
0020   2e 30 2e 35 34 20 36 30 39 33 20 38 30 30 30 0d   .0.54 6093 8000.
0030   0a                                                .

可以看到,header 包括 client 和 ingressgateway 的IP:PORT信息。更加详细的介绍参考这里。

  • ProxyProtocolUpstreamTransport

ingressgateway 作为发送端,使用ProxyProtocolUpstreamTransport,构建Proxy Protocol头部:

/// source/extensions/transport_sockets/proxy_protocol/proxy_protocol.ccvoid UpstreamProxyProtocolSocket::generateHeaderV1() {// Default to local addresses (used if no downstream connection exists e.g. health checks)auto src_addr = callbacks_->connection().addressProvider().localAddress(); auto dst_addr = callbacks_->connection().addressProvider().remoteAddress();if (options_ && options_->proxyProtocolOptions().has_value()) {const auto options = options_->proxyProtocolOptions().value();src_addr = options.src_addr_;dst_addr = options.dst_addr_;}Common::ProxyProtocol::generateV1Header(*src_addr->ip(), *dst_addr->ip(), header_buffer_);
}
  • envoy.filters.listener.proxy_protocol

httpbin envoy作为接收端,配置ListenerFilter(envoy.filters.listener.proxy_protocol)解析Proxy Protocol头部:

/// source/extensions/filters/listener/proxy_protocol/proxy_protocol.ccReadOrParseState Filter::onReadWorker() {Network::ConnectionSocket& socket = cb_->socket(); /// ConnectionHandlerImpl::ActiveTcpSocket
...if (proxy_protocol_header_.has_value() && !proxy_protocol_header_.value().local_command_) {
...// Only set the local address if it really changed, and mark it as address being restored.if (*proxy_protocol_header_.value().local_address_ !=*socket.addressProvider().localAddress()) { /// proxy protocol header: 172.17.0.54:8000socket.addressProvider().restoreLocalAddress(proxy_protocol_header_.value().local_address_); /// => 172.17.0.54:8000} /// Network::ConnectionSocketsocket.addressProvider().setRemoteAddress(proxy_protocol_header_.value().remote_address_); /// 修改downstream_remote_address为106.52.131.116}// Release the file event so that we do not interfere with the connection read events.socket.ioHandle().resetFileEvents();cb_->continueFilterChain(true); /// ConnectionHandlerImpl::ActiveTcpSocketreturn ReadOrParseState::Done;
}

这里值得注意的,envoy.filters.listener.proxy_protocol在解析proxy protocol header时,local_address为发送端的dst_addr(172.17.0.54:8000)remote_address为发送端的src_addr(106.52.131.116)。顺序刚好反过来了。

经过proxy_protocol的处理,连接的downstream_remote_address被修改为client的源地址。

  • envoy.filters.listener.original_src

对于sidecar.istio.io/interceptionMode: TPROXYvirtualInbound listener会增加envoy.filters.listener.original_src:

# istioctl -n foo pc listeners deploy/httpbin --port 15006 -o json
[{"name": "virtualInbound","address": {"socketAddress": {"address": "0.0.0.0","portValue": 15006}},"filterChains": [...],"listenerFilters": [{"name": "envoy.filters.listener.original_dst","typedConfig": {"@type": "type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst"}},{"name": "envoy.filters.listener.original_src","typedConfig": {"@type": "type.googleapis.com/envoy.extensions.filters.listener.original_src.v3.OriginalSrc","mark": 1337}}...]"listenerFiltersTimeout": "0s","continueOnListenerFiltersTimeout": true,"transparent": true,"trafficDirection": "INBOUND","accessLog": [...]}
]

envoy.filters.listener.original_src通过tcp option实现修改upstream_local_addressdownstream_remote_address,实现透传client IP。

/// source/extensions/filters/listener/original_src/original_src.ccNetwork::FilterStatus OriginalSrcFilter::onAccept(Network::ListenerFilterCallbacks& cb) {auto& socket = cb.socket(); /// ConnectionHandlerImpl::ActiveTcpSocket.socket()auto address = socket.addressProvider().remoteAddress();   /// get downstream_remote_addressASSERT(address);ENVOY_LOG(debug,"Got a new connection in the original_src filter for address {}. Marking with {}",address->asString(), config_.mark());...auto options_to_add =Filters::Common::OriginalSrc::buildOriginalSrcOptions(std::move(address), config_.mark()); socket.addOptions(std::move(options_to_add)); /// Network::Socket::Optionsreturn Network::FilterStatus::Continue;
}
  • envoy.filters.listener.original_dst

另外,httbin envoy作为 ingressgateway 的接收端,virtualInbound listener还配置了 ListenerFilter(envoy.filters.listener.original_dst),来看看它的作用。

// source/extensions/filters/listener/original_dst/original_dst.ccNetwork::FilterStatus OriginalDstFilter::onAccept(Network::ListenerFilterCallbacks& cb) {ENVOY_LOG(debug, "original_dst: New connection accepted");Network::ConnectionSocket& socket = cb.socket();if (socket.addressType() == Network::Address::Type::Ip) { /// socket SO_ORIGINAL_DST optionNetwork::Address::InstanceConstSharedPtr original_local_address = getOriginalDst(socket); /// origin dst address// A listener that has the use_original_dst flag set to true can still receive// connections that are NOT redirected using iptables. If a connection was not redirected,// the address returned by getOriginalDst() matches the local address of the new socket.// In this case the listener handles the connection directly and does not hand it off.if (original_local_address) { /// change local address to origin dst address// Restore the local address to the original one.socket.addressProvider().restoreLocalAddress(original_local_address);}}return Network::FilterStatus::Continue;
}

对于 istio,由 iptable 截持原有 request,并转到15006(in request),或者15001(out request)端口,所以,处理 request 的 socket 的local address,并不请求的original dst addressoriginal_dst ListenerFilter负责将 socket 的 local address 改为original dst address

对于virtualOutbound listener,不会直接添加envoy.filters.listener.original_dst,而是将use_original_dst设置为 true,然后 envoy 会自动添加envoy.filters.listener.original_dst。同时,virtualOutbound listener会将请求,转给请求原目的地址关联的 listener 进行处理。

对于virtualInbound listener,会直接添加envoy.filters.listener.original_dst。与virtualOutbound listener不同的是,它只是将地址改为original dst address,而不会将请求转给对应的 listener 处理(对于入请求,并不存在 dst address 的 listener)。实际上,对于入请求是由 FilterChain 完成处理。

参考 istio 生成virtualInbound listener的代码:

// istio/istio/pilot/pkg/networking/core/v1alpha3/listener_builder.gofunc (lb *ListenerBuilder) aggregateVirtualInboundListener(passthroughInspectors map[int]enabledInspector) *ListenerBuilder {// Deprecated by envoyproxy. Replaced// 1. filter chains in this listener// 2. explicit original_dst listener filter// UseOriginalDst: proto.BoolTrue,lb.virtualInboundListener.UseOriginalDst = nillb.virtualInboundListener.ListenerFilters = append(lb.virtualInboundListener.ListenerFilters,xdsfilters.OriginalDestination, /// 添加envoy.filters.listener.original_dst)if lb.node.GetInterceptionMode() == model.InterceptionTproxy { /// TPROXY modelb.virtualInboundListener.ListenerFilters =append(lb.virtualInboundListener.ListenerFilters, xdsfilters.OriginalSrc)}
...

小结

基于 TPROXY 以及 Proxy Protocol,我们可以在 istio 中,实现四层协议的客户端源 IP 的保持。

参考

  • istio doc: Configuring Gateway Network Topology
  • IP Transparency and Direct Server Return with NGINX and NGINX Plus as Transparent Proxy
  • Kernel doc: Transparent proxy support
  • Haproxy doc: The PROXY protocol
  • Envoy doc: IP Transparency
  • 【IstioCon 2021】如何在Istio中进行源地址保持?

关于我们

更多关于云原生的案例和知识,可关注同名【腾讯云原生】公众号~

福利:

①公众号后台回复【手册】,可获得《腾讯云原生路线图手册》&《腾讯云原生最佳实践》~

②公众号后台回复【系列】,可获得《15个系列100+篇超实用云原生原创干货合集》,包含Kubernetes 降本增效、K8s 性能优化实践、最佳实践等系列。

③公众号后台回复【白皮书】,可获得《腾讯云容器安全白皮书》&《降本之源-云原生成本管理白皮书v1.0》

④公众号后台回复【光速入门】,可获得腾讯云专家5万字精华教程,光速入门Prometheus和Grafana。

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/24963.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【读书笔记】万物原理——打开客观世界与主观情感的大门

被尹烨老师推荐种草的&#xff0c;以为是一本讲生命科学的科普书&#xff0c;看上了又以为是说量子物理等高端科学研究的&#xff0c;最后被互补性理论惊到了。这哪里只是一本打开认知客观世界的大门&#xff0c;还让我重识内心。那些看不见摸不着的情感&#xff0c;比如同情心…

屌丝评:阿里云计算总裁胡晓明《让计算成为中国的能力》

2015年12月23日有幸参加由广东省人民政府和阿里巴巴集团举行的“数据引领&#xff0c;飞粤云端”2015年云栖大会广东峰会暨广东省云计算大数据开发者大会&#xff0c;也很荣幸现场听了阿里云计算总裁胡晓明先生的精彩演讲《让计算成为中国的能力》&#xff0c;作为IT界非著名的…

《循序渐进学Docker》——1.3 为什么使用Docker

本节书摘来自华章出版社《循序渐进学Docker》一书中的第1章&#xff0c;第1.3节&#xff0c;作者李金榜 尹烨 刘天斯 陈纯&#xff0c;更多章节内容可以访问云栖社区“华章计算机”公众号查看。 1.3 为什么使用Docker 当深入了解Docker后&#xff0c;你想在公司或部门推广Dock…

生命密码:你的第一本基因科普书

内容简介 生命如此美妙&#xff0c;我们却知之甚少。芸芸众生蕴藏哪些造化之妙&#xff1f;基因组学、生命科学为何包含无穷魅力&#xff1f;它有趣、有用&#xff0c;又有科学严谨的态度&#xff0c;用人人都看得懂的语言&#xff0c;轻松地解答那些古怪而让人忧心的问题&…

这一年,这些书:2022年读书笔记

Note: 以下 markdown 格式文本由 json2md 自动转换生成&#xff0c;可参考JSON转Markdown&#xff1a;我把阅读数据从MongoDB中导出转换为.md了了解具体的转换过程。 为什么是中国 作者&#xff1a;金一南[中] ISBN&#xff1a;9787559639134 出版社&#xff1a;北京联合出版…

Docker基础 --循序渐进学Docker(李金榜、尹烨......)学习笔记

docker基础 Docker三个重要的概念&#xff1a;仓库&#xff08;Repository&#xff09;&#xff0c;镜像&#xff08;Image&#xff09;和容器&#xff08;Container&#xff09;,他们是Docker的三大基础组件。 容器都是基于镜像创建的&#xff0c;基于一个镜像可以创建若干个…

华大基因尹烨的一些语录记录

不谋万事者不足谋一时&#xff0c;而不谋全局者不足谋一域。 技术的发展从来不以人的意志为转移。 实际上到那个点上就要认知&#xff0c;我们的人智是有穷尽的&#xff0c;生命是有尽头的。这个时候就一定要升维&#xff0c;不能还是在一个频道上去学了&#xff0c;一定要向上…

教你在Midjourney中直接使用ChatGPT

v ChatGPT云炬学长 ​之前零零碎碎的分享过很多教程&#xff0c; 基础的比较多&#xff0c;进阶的也有不少&#xff0c; 当时是想着将高级技巧收录进高阶教程里来收费的&#xff0c; 所以就有很多东西&#xff0c;一直都没有分享出来。 但现在不想靠卖这种教程来创收了。 …

AI时代的赚钱思路:23岁女网红如何利用AI技术年入4亿?

一、AI技术为网红赚钱创造新途径 23岁美国网红Caryn Marjorie&#xff08;卡琳玛乔丽&#xff09;正同时交往1000多个男朋友。 作为一个在Snapchat上坐拥180万粉丝的美女&#xff0c;她利用人工智能&#xff08;AI&#xff09;技术&#xff0c;打造了一个AI版本的自己&#x…

四端口以太网二层交换机的搭建实录

在大三下学期&#xff0c;我学习了“通信芯片设计基础”这门课。说实话&#xff0c;我最感兴趣的还是Verilog实践部分&#xff0c;对于模电学的不好的我&#xff0c;再去学PN结、mos管电路等等知识实在是遭罪。最后老师布置了一个很难的大作业&#xff0c;具体要求见下图&#…

​AI + 非遗文化传播,人工智能师资培训重磅招募

大语言模型热度空前&#xff0c;诸如文心一言、 ChatGPT 等已经能够与人对话互动、回答问题、协助创作&#xff0c;逐渐应用于人们的工作和生活&#xff0c;也引发了社会热议。为推动大模型及人工智能相关专业人员的培养&#xff0c;同时将人工智能技术融入非遗文化传播&#x…

优质的懒人资源导航工具集合网站

说到「效率工具」&#xff0c;一直是大家关心的热点话题。作者网站的初心就是我自己也很懒&#xff0c;每一次需要什么东西的时候&#xff0c;都是需要话费很大的一个时间去寻找&#xff0c;但是有了这网站会让你方便许多。 今天&#xff0c;我决定重新开始做一个安利 。 这次我…

用好 ChatGPT | Prompt 编写模式:如何将思维框架赋予机器

文章目录 一、前言二、主要内容三、总结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 人类相对于其他动物更擅长于类比、概念抽象、符号化等高级认知活动&#xff0c;这些认知活动可以帮助人类在面对新问题时&#xff0c;从已有的知识和经…

申论视频课

小马哥 申论题型 答题纸&#xff0c;一行25个字努力得到小题的百分之80分 一 归纳概括题 10-15分&#xff0c;一般放在第一题一般都是小题拉开差距不超过200字符合体干&#xff0c;来自材料&#xff0c;是否有发挥空间问啥答啥、理解语义、读出层次、富有逻辑找到到底做了啥…

申论 立意列大纲

作者&#xff1a;一片仙鱼儿 链接&#xff1a;https://www.zhihu.com/question/56957544/answer/521207352 来源&#xff1a;知乎 著作权归作者所有&#xff0c;转载请联系作者获得授权。 最重要也最首要工作&#xff1a;立意列大纲 阅卷人会首先看你的文章结构&#xff0c;也就…

python改写文章脚本,chatgpt4.0api接口测试问题总结

大家好&#xff0c;我是淘小白~ 最近gpt4.0要放开了&#xff0c;有很多朋友想用4.0的接口来改写文章&#xff0c;这几天就做了一个脚本出来&#xff0c;在中间有些测试&#xff0c;趁着还有印象&#xff0c;来总结一下&#xff01; 1、gpt4与gpt3.5比较 4.0的模型输入量更大…

web -【在线聊天】

1.WebSocket&#xff1a; 1.1 什么是WebSocket&#xff1a; WebSocket是HTML5开始提供的一种在单个TCP连接上进行双工通讯的协议。 WebSocket 使得客户端与服务器之间得数据交换变得简单&#xff0c;允许服务器端主动向客户端推送数据。在WebSocket API中&#xff0c;浏览器与服…

合理利用chatGpt之新冠阳性

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;合理利用chatGpt之新冠阳性 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入:…

基于PyQt5实现同一窗口下多界面切换

基于Python和PyQt5实现同一窗口下多界面切换 基于pyqt5控件tabWidget结合按钮点击实现同窗口下固定区域不同界面切换。每个子界面均可拓展相应的功能。 效果 pyqt5同窗口多界面切换

统计平均分:从文本读取成绩并计算平均分,将平均分写入文本文件保存

从文本读取成绩并计算平均分&#xff0c;将平均分写入文本文件保存。 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单…… 地址&#xff1a;htt…