C++开发:rpc_client 客户端解析2

【链接】GitHub - qicosmos/rest_rpc: modern C++(C++11), simple, easy to use rpc framework

       rpc_client 是一个用于实现远程过程调用(Remote Procedure Call, RPC)的客户端类。利用 Asio 库进行网络通信,并支持异步操作、SSL 安全连接(如果启用)、超时控制等功能。这个类允许客户端以不同的方式初始化,并提供了灵活的消息处理机制。

核心功能

  • 异步网络通信:使用 Asio 提供的异步 I/O 操作来执行非阻塞网络请求。
  • SSL 支持:在定义了 CINATRA_ENABLE_SSL 的情况下,可以支持安全的 SSL/TLS 连接。
  • 超时管理:通过 asio::steady_timer 实现超时控制,确保在网络异常时能够及时响应。
  • 消息队列与缓冲区:维护待发送消息的队列和接收响应数据的缓冲区,保证数据的有序处理。
  • 回调机制:通过设置回调函数来处理来自服务器的响应或错误情况。
  • 重连机制:提供重连尝试次数配置,增强连接稳定性。

核心成员函数解析

1. 线程管理函数

void run() {
    if (thd_ != nullptr && thd_->joinable()) {
      thd_->join();
    }
}
  • 作用:阻塞等待IO线程完成

  • 特点

    • 检查线程指针非空且可join

    • 安全地等待后台线程结束

    • 通常用于客户端销毁前的清理

2. 连接配置函数

void set_connect_timeout(size_t milliseconds) {
    connect_timeout_ = milliseconds;
}
  • 作用:设置连接超时时间(毫秒)

  • 使用场景:在connect前调用配置超时

void set_reconnect_count(int reconnect_count) {
    reconnect_cnt_ = reconnect_count;
}
  • 作用:设置最大重连次数

  • 特殊值:-1表示无限重连

void enable_auto_reconnect(bool enable = true) {
    enable_reconnect_ = enable;
    reconnect_cnt_ = std::numeric_limits<int>::max();
}
  • 作用:启用/禁用自动重连

  • 特点:启用时设置极大重连次数,模拟无限重连

void enable_auto_heartbeat(bool enable = true) {
    if (enable) {
      reset_deadline_timer(5);
    } else {
      deadline_.cancel();
    }
}
  • 作用:启用/禁用心跳机制

  • 参数enable为true时,每5秒发送心跳

3. 连接管理函数

bool connect(size_t timeout = 3, bool is_ssl = false);
bool connect(const std::string &host, unsigned short port, 
            bool is_ssl = false, size_t timeout = 3);
  • 作用:同步建立连接

  • 流程

    1. 检查是否已连接

    2. 必要时升级SSL

    3. 异步发起连接

    4. 等待连接结果

  • 参数

    • timeout:等待连接完成的超时(秒)

    • is_ssl:是否使用SSL加密

  • 返回:连接是否成功

void async_connect(const std::string &host, unsigned short port);
  • 作用:异步建立连接

  • 特点:不阻塞调用线程,通过回调通知结果

bool wait_conn(size_t timeout) {
    if (has_connected_) return true;
    
    has_wait_ = true;
    std::unique_lock<std::mutex> lock(conn_mtx_);
    bool result = conn_cond_.wait_for(lock, std::chrono::seconds(timeout),
                                    [this] { return has_connected_.load(); });
    has_wait_ = false;
    return has_connected_;
}
  • 作用:等待连接完成

  • 机制

    • 使用条件变量等待连接状态变化

    • 带超时的等待避免永久阻塞

    • 原子变量保证线程安全

4. 连接维护函数

void update_addr(const std::string &host, unsigned short port) {
    host_ = host;
    port_ = port;
}
  • 作用:更新服务器地址

  • 注意:不会主动断开现有连接

void close(bool close_ssl = true) {
    asio::error_code ec;
    if (close_ssl) {
#ifdef CINATRA_ENABLE_SSL
      if (ssl_stream_) {
        ssl_stream_->shutdown(ec);
        ssl_stream_ = nullptr;
      }
#endif
    }

    if (!has_connected_) return;

    has_connected_ = false;
    socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
    socket_.close(ec);
    clear_cache();
}
  • 作用:关闭连接

  • 流程

    1. 可选关闭SSL连接

    2. 关闭TCP socket

    3. 清理内部缓存

  • 参数close_ssl控制是否关闭SSL连接

5. 回调设置函数

void set_error_callback(std::function<void(asio::error_code)> f) {
    err_cb_ = std::move(f);
}
  • 作用:设置全局错误回调

  • 回调参数:ASIO错误码

  • 触发场景:网络错误、连接断开等

6. 同步调用函数族

6.1 无返回值同步调用

template <size_t TIMEOUT, typename T = void, typename... Args>
typename std::enable_if<std::is_void<T>::value>::type
call(const std::string &rpc_name, Args &&...args) {
    auto future_result = async_call<FUTURE>(rpc_name, std::forward<Args>(args)...);
    auto status = future_result.wait_for(std::chrono::milliseconds(TIMEOUT));
    if (status == std::future_status::timeout || 
        status == std::future_status::deferred) {
        throw std::out_of_range("timeout or deferred");
    }
    future_result.get().as();
}

template <typename T = void, typename... Args>
typename std::enable_if<std::is_void<T>::value>::type
call(const std::string &rpc_name, Args &&...args) {
    call<DEFAULT_TIMEOUT, T>(rpc_name, std::forward<Args>(args)...);
}
  • 机制

    • 使用SFINAE技术限定只适用于返回void的RPC调用

    • 通过async_call转换为future-based异步调用

    • 等待结果并检查超时

    • 默认超时版本委托给具体超时版本实现

  • 特点

    • 阻塞式调用,直到收到响应或超时

    • 超时或异常时抛出std::out_of_range

    • 不关心返回值,只确保调用成功

6.2 有返回值同步调用

template <size_t TIMEOUT, typename T, typename... Args>
typename std::enable_if<!std::is_void<T>::value, T>::type
call(const std::string &rpc_name, Args &&...args) {
    auto future_result = async_call<FUTURE>(rpc_name, std::forward<Args>(args)...);
    auto status = future_result.wait_for(std::chrono::milliseconds(TIMEOUT));
    if (status == std::future_status::timeout || 
        status == std::future_status::deferred) {
        throw std::out_of_range("timeout or deferred");
    }
    return future_result.get().template as<T>();
}

template <typename T, typename... Args>
typename std::enable_if<!std::is_void<T>::value, T>::type
call(const std::string &rpc_name, Args &&...args) {
    return call<DEFAULT_TIMEOUT, T>(rpc_name, std::forward<Args>(args)...);
}
  • 机制

    • 同样基于future的等待机制

    • 使用模板参数T指定返回值类型

    • 通过req_result的as<T>()方法转换结果

  • 特点

    • 返回指定类型的RPC调用结果

    • 自动类型转换保证类型安全

    • 默认超时版本简化调用

7. 异步调用函数族

7.1 基于Future的异步调用

template <CallModel model, typename... Args>
future_result<req_result> async_call(const std::string &rpc_name, Args &&...args) {
    auto p = std::make_shared<std::promise<req_result>>();
    std::future<req_result> future = p->get_future();

    uint64_t fu_id = 0;
    {
        std::unique_lock<std::mutex> lock(cb_mtx_);
        fu_id_++;
        fu_id = fu_id_;
        future_map_.emplace(fu_id, std::move(p));
    }

    rpc_service::msgpack_codec codec;
    auto ret = codec.pack_args(std::forward<Args>(args)...);
    write(fu_id, request_type::req_res, std::move(ret),
          MD5::MD5Hash32(rpc_name.data()));
    return future_result<req_result>{fu_id, std::move(future)};
}
  • 机制

    • 创建promise/future对存储结果

    • 生成唯一请求ID并注册到future_map_

    • 使用msgpack序列化参数

    • 通过MD5哈希函数名生成32位标识

    • 写入发送队列

  • 特点

    • 返回包含future和请求ID的future_result对象

    • 线程安全的请求ID生成和映射管理

    • 支持链式异步操作

7.2 多语言专用异步调用

long internal_async_call(const std::string &encoded_func_name_and_args) {
    auto p = std::make_shared<std::promise<req_result>>();
    uint64_t fu_id = 0;
    {
        std::unique_lock<std::mutex> lock(cb_mtx_);
        fu_id_++;
        fu_id = fu_id_;
    }
    msgpack::sbuffer sbuffer;
    sbuffer.write(encoded_func_name_and_args.data(),
                 encoded_func_name_and_args.size());
    write(fu_id, request_type::req_res, std::move(sbuffer),
          MD5::MD5Hash32(encoded_func_name_and_args.data()));
    return fu_id;
}
  • 特点

    • 专为其他语言(如Java)集成设计

    • 接收预编码的函数名和参数

    • 只返回请求ID,不管理future

    • 结果通过回调机制返回

7.3 基于回调的异步调用

template <size_t TIMEOUT = DEFAULT_TIMEOUT, typename... Args>
void async_call(const std::string &rpc_name,
               std::function<void(asio::error_code, string_view)> cb,
               Args &&...args) {
    if (!has_connected_) {
        if (cb)
            cb(asio::error::make_error_code(asio::error::not_connected),
               "not connected");
        return;
    }

    uint64_t cb_id = 0;
    {
        std::unique_lock<std::mutex> lock(cb_mtx_);
        callback_id_++;
        callback_id_ |= (uint64_t(1) << 63);
        cb_id = callback_id_;
        auto call = std::make_shared<call_t>(ios_, std::move(cb), TIMEOUT);
        call->start_timer();
        callback_map_.emplace(cb_id, call);
    }

    rpc_service::msgpack_codec codec;
    auto ret = codec.pack_args(std::forward<Args>(args)...);
    write(cb_id, request_type::req_res, std::move(ret),
          MD5::MD5Hash32(rpc_name.data()));
}
  • 机制

    • 检查连接状态,未连接立即回调错误

    • 生成带标志位的回调ID(最高位设为1)

    • 创建带超时的call_t对象管理回调

    • 注册回调到callback_map_

    • 序列化参数并发送请求

  • 特点

    • 完全异步的callback-based接口

    • 内置超时处理机制

    • 错误优先的回调设计

    • 线程安全的回调管理

私有成员函数深度解析

1. 客户端生命周期管理

停止客户端 

void stop() {
    if (thd_ != nullptr) {
      work_ = nullptr;  // 允许io_context自然退出
      if (thd_->joinable()) {
        thd_->join();  // 等待IO线程结束
      }
      thd_ = nullptr;  // 释放线程资源
    }
}
  • 机制

    1. 清除work对象使io_context可退出

    2. 等待IO线程完成

    3. 释放线程资源

  • 注意:应在析构函数前调用确保资源有序释放

2. 发布-订阅功能

订阅相关函数

template <typename Func> 
void subscribe(std::string key, Func f) {
    // 检查重复订阅
    sub_map_.emplace(key, std::move(f));  // 存储回调
    send_subscribe(key, "");  // 发送订阅请求
    key_token_set_.emplace(std::move(key), "");  // 记录订阅关系
}

template <typename Func>
void subscribe(std::string key, std::string token, Func f) {
    auto composite_key = key + token;  // 构造复合键
    sub_map_.emplace(std::move(composite_key), std::move(f));
    send_subscribe(key, token);
    key_token_set_.emplace(std::move(key), std::move(token));
}

void send_subscribe(const std::string &key, const std::string &token) {
    auto ret = codec.pack_args(key, token);  // 序列化参数
    write(0, request_type::sub_pub, std::move(ret), MD5::MD5Hash32(key.data()));
}

void resend_subscribe() {
    // 重连后重新发送所有订阅
    for (auto &pair : key_token_set_) {
      send_subscribe(pair.first, pair.second);
    }
}
  • 特点

    • 支持带token和不带token的订阅

    • 自动管理订阅关系

    • 重连后自动恢复订阅

发布相关函数

template <typename T, size_t TIMEOUT = 3>
void publish(std::string key, T &&t) {
    auto buf = codec.pack(std::move(t));  // 序列化消息
    call<TIMEOUT>("publish", std::move(key), "", 
                 std::string(buf.data(), buf.size()));
}

template <typename T, size_t TIMEOUT = 3>
void publish_by_token(std::string key, std::string token, T &&t) {
    auto buf = codec.pack(std::move(t));
    call<TIMEOUT>("publish_by_token", std::move(key), std::move(token),
                 std::string(buf.data(), buf.size()));
}
  • 特点

    • 支持带超时的发布操作

    • 自动序列化发布内容

    • 区分普通发布和带token发布

3. 连接管理

void async_connect() {
    socket_.async_connect({addr, port_}, [this](const asio::error_code &ec) {
      if (!ec) {
        if (is_ssl()) {
          handshake();  // SSL连接需额外握手
          return;
        }
        has_connected_ = true;
        do_read();  // 开始接收数据
        resend_subscribe();  // 恢复订阅
        conn_cond_.notify_one();  // 通知等待线程
      } else {
        if (reconnect_cnt_ != 0) {
          async_reconnect();  // 自动重连
        }
      }
    });
}
  • 重连机制

    • 指数退避重连策略

    • 可配置最大重连次数

    • 线程安全的连接状态管理

void reset_deadline_timer(size_t timeout) {
    deadline_.expires_from_now(std::chrono::seconds(timeout));
    deadline_.async_wait([this, timeout](const asio::error_code &ec) {
      if (!ec && has_connected_) {
        write(0, request_type::req_res, buffer_type(0), 0);  // 发送心跳
      }
      reset_deadline_timer(timeout);  // 递归设置下一次心跳
    });
}
  • 心跳机制

    • 定时发送空包保持连接

    • 自动重置定时器形成周期

    • 连接断开时自动停止

4. 数据读写核心

写操作体系

void write(uint64_t req_id, request_type type, 
          rpc_service::buffer_type &&message, uint32_t func_id) {
    // 构造消息并加入队列
    if (outbox_.size() == 1) {
      write();  // 直接发送
    }
}

void write() {
    // 组装消息头
    async_write(write_buffers, [this](const asio::error_code &ec, size_t length) {
      if (!ec) {
        ::free(outbox_.front().content.data());  // 释放内存
        outbox_.pop_front();
        if (!outbox_.empty()) write();  // 继续发送下一条
      } else {
        handle_write_error(ec);  // 错误处理
      }
    });
}
  • 特点

    • 消息队列保证发送顺序

    • 零拷贝优化(移动语义)

    • 异步发送不阻塞IO线程

读操作体系

void do_read() {
    async_read_head([this](const asio::error_code &ec, size_t length) {
      if (!ec) {
        rpc_header *header = (rpc_header *)(head_);
        if (valid_body_len(header->body_len)) {
          read_body(header->req_id, header->req_type, header->body_len);
        }
      } else {
        handle_read_error(ec);
      }
    });
}

void read_body(uint64_t req_id, request_type req_type, size_t body_len) {
    async_read(body_len, [this, req_id, req_type, body_len](...) {
      if (!ec) {
        if (req_type == request_type::req_res) {
          call_back(req_id, ec, {body_.data(), body_len});  // RPC响应
        } else {
          callback_sub(ec, {body_.data(), body_len});  // 订阅消息
        }
        do_read();  // 继续读下一条
      } else {
        handle_read_error(ec);
      }
    });
}
  • 特点

    • 分两阶段读取(头+体)

    • 自动处理不同类型消息

    • 循环读取实现持续通信

5. 回调处理

void call_back(uint64_t req_id, const asio::error_code &ec, string_view data) {
    if (client_language_ == JAVA) {
      on_result_received_callback_(req_id, std::string(data));  // Java回调
    } else {
      if (req_id >> 63) {  // 回调模式
        auto call = std::move(callback_map_[req_id]);
        if (!call->has_timeout()) {
          call->callback(ec, data);  // 执行用户回调
        }
      } else {  // Future模式
        future_map_[req_id]->set_value(req_result{data});
      }
    }
}
  • 分发机制

    • 高位区分回调类型

    • 超时检查保证及时释放

    • 多语言支持统一入口

6. 资源管理

void clear_cache() {
    // 清空发送队列
    while (!outbox_.empty()) {
      ::free(outbox_.front().content.data());
      outbox_.pop_front();
    }
    // 清空回调映射
    callback_map_.clear();
    future_map_.clear();
}
  • 作用:连接断开时清理所有待处理请求

void reset_socket() {
    socket_.close();
    socket_ = decltype(socket_)(ios_);  // 重建socket
    if (!socket_.is_open()) {
      socket_.open(asio::ip::tcp::v4());  // 重新打开
    }
}
  • 作用:重连前重置socket状态

7. 错误处理机制

void error_callback(const asio::error_code &ec) {
    if (err_cb_) {
      err_cb_(ec);  // 调用用户自定义错误回调
    }

    if (enable_reconnect_) {
      async_reconnect();  // 启用自动重连时尝试重新连接
    }
}
  • 作用:统一错误处理入口

  • 流程

    1. 优先执行用户注册的错误回调

    2. 根据自动重连标志决定是否重连

  • 特点

    • 集中处理所有网络错误

    • 避免错误处理代码重复

void set_default_error_callback() {
    err_cb_ = [this](asio::error_code) { 
        async_connect();  // 默认行为是尝试重连
    };
}
  • 作用:设置默认错误处理行为

  • 使用场景:当用户未提供自定义错误回调时使用

8. SSL/TLS安全连接

bool is_ssl() const {
#ifdef CINATRA_ENABLE_SSL
    return ssl_stream_ != nullptr;  // 检查SSL流对象是否存在
#else
    return false;  // 未启用SSL编译时始终返回false
#endif
}
  • 作用:判断当前是否使用SSL连接

  • 特点:通过条件编译实现跨平台支持

void handshake() {
#ifdef CINATRA_ENABLE_SSL
    ssl_stream_->async_handshake(asio::ssl::stream_base::client,
        [this](const asio::error_code &ec) {
            if (!ec) {
                // 握手成功后的初始化
                has_connected_ = true;
                do_read();  // 开始接收数据
                resend_subscribe();  // 恢复订阅
                if (has_wait_) 
                    conn_cond_.notify_one();  // 通知等待线程
            } else {
                error_callback(ec);  // 握手失败处理
                close();
            }
        });
#endif
}
  • 作用:执行SSL握手协议

  • 流程

    1. 异步发起SSL握手

    2. 成功则初始化连接状态

    3. 失败则触发错误处理

  • 特点:完全异步的非阻塞实现

void upgrade_to_ssl() {
#ifdef CINATRA_ENABLE_SSL
    if (ssl_stream_) return;  // 避免重复初始化

    // 创建SSL上下文
    asio::ssl::context ssl_context(asio::ssl::context::sslv23);
    ssl_context.set_default_verify_paths();
    
    // 应用自定义SSL配置
    if (ssl_context_callback_) {
        ssl_context_callback_(ssl_context);
    }

    // 创建SSL流对象
    ssl_stream_ = std::make_unique<asio::ssl::stream<asio::ip::tcp::socket &>>(
        socket_, ssl_context);
#endif
}
  • 作用:将普通连接升级为SSL连接

  • 特点

    • 惰性初始化(首次需要时创建)

    • 支持自定义SSL配置回调

    • 显式条件编译保证安全

9. 异步IO操作模板

异步读操作模板

template <typename Handler>
void async_read_head(Handler handler) {
    if (is_ssl()) {
        // SSL模式读取
        asio::async_read(*ssl_stream_, asio::buffer(head_, HEAD_LEN),
                         std::move(handler));
    } else {
        // 普通模式读取
        asio::async_read(socket_, asio::buffer(head_, HEAD_LEN),
                         std::move(handler));
    }
}
template <typename Handler>
void async_read(size_t size_to_read, Handler handler) {
    if (is_ssl()) {
        // SSL模式读取消息体
        asio::async_read(*ssl_stream_, asio::buffer(body_.data(), size_to_read),
                         std::move(handler));
    } else {
        // 普通模式读取消息体
        asio::async_read(socket_, asio::buffer(body_.data(), size_to_read),
                         std::move(handler));
    }
}

异步写操作模板

template <typename BufferType, typename Handler>
void async_write(const BufferType &buffers, Handler handler) {
    if (is_ssl()) {
        // SSL模式写入
        asio::async_write(*ssl_stream_, buffers, std::move(handler));
    } else {
        // 普通模式写入
        asio::async_write(socket_, buffers, std::move(handler));
    }
}

模板设计特点:

  1. 统一接口:为SSL和非SSL连接提供相同调用方式

  2. 类型安全:使用模板参数确保编译期类型检查

  3. 性能优化

    • 完美转发handler避免额外拷贝

    • 运行时条件判断最小化

  4. 灵活性:支持任意符合ASIO要求的缓冲区类型

call_t 类

  call_t 类是 RPC 客户端内部用于管理异步调用超时和回调的核心组件,使得 RPC 客户端能够可靠地管理大量并发异步调用,在保证性能的同时提供精确的超时控制。

1. 类设计基础

继承结构

  • asio::noncopyable:禁止拷贝构造和拷贝赋值,确保资源安全

  • std::enable_shared_from_this:支持返回智能指针的 shared_from_this() 方法

构造函数

call_t(asio::io_service &ios,
       std::function<void(asio::error_code, string_view)> cb,
       size_t timeout)
    : timer_(ios), cb_(std::move(cb)), timeout_(timeout) {}
  • 参数

    • ios:ASIO IO 服务上下文

    • cb:用户回调函数,接收错误码和数据视图

    • timeout:超时时间(毫秒)

  • 初始化

    • 定时器绑定到 IO 服务

    • 回调函数使用移动语义避免拷贝

    • 超时时间保存备用

2. 核心方法实现

定时器管理

void start_timer() {
    if (timeout_ == 0) return;  // 0表示不启用超时
    
    timer_.expires_from_now(std::chrono::milliseconds(timeout_));
    auto self = this->shared_from_this();  // 保持对象存活
    timer_.async_wait([this, self](asio::error_code ec) {
        if (!ec) has_timeout_ = true;  // 标记超时状态
    });
}
  • 机制

    • 设置定时器到期时间

    • 通过 shared_from_this() 保持对象生命周期

    • 异步等待超时事件

void cancel() {
    if (timeout_ == 0) return;
    asio::error_code ec;
    timer_.cancel(ec);  // 取消定时器
}
  • 作用:在正常收到响应时取消超时检测

回调执行

void callback(asio::error_code ec, string_view data) {
    cb_(ec, data);  // 直接转发参数给用户回调
}
  • 特点:简单透明的回调转发,不添加额外逻辑

状态检查

bool has_timeout() const { 
    return has_timeout_;  // 返回超时标志状态
}
  • 用途:供外部检查是否已触发超时

3. 生命周期管理

  1. 对象创建

    • 由 rpc_client 在发起异步调用时创建

    • 存储于 callback_map_ 的 shared_ptr 中

  2. 存活周期

    • 从调用开始到收到响应或超时

    • 通过 shared_from_this() 确保定时器回调期间对象存活

  3. 资源释放

    • 正常响应或超时后从 map 中移除

    • shared_ptr 引用计数归零时自动销毁

 

参考资源链接:[K8s集群部署Docker-Harbor镜像:Node认证详解](https://wenku.csdn.net/doc/2q2vrzz0vq?utm_source=wenku_answer2doc_content) 在Kubernetes集群中部署来自Docker-Harbor私有仓库的镜像并进行节点认证,涉及到的关键步骤包括创建认证所需的Secret以及配置YAML文件。首先,需要创建一个类型为`docker-registry`的Secret来存储Harbor仓库的认证信息。这可以通过`kubectl`命令来完成。例如: ``` kubectl create secret docker-registry my-harbor-secret \ --docker-server=*** \ --docker-username=myuser \ --docker-password=mypassword \ --docker-email=*** ``` 这里`my-harbor-secret`是创建的Secret名称,`***`是你的Harbor仓库地址,后续是你的Harbor账户信息。 接下来,你需要编写一个YAML文件来定义你的Deployment、ServiceIngress资源。在Deployment配置中,通过`spec.template.spec.imagePullSecrets`引用之前创建的Secret: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deployment spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: imagePullSecrets: - name: my-harbor-secret containers: - name: myapp-container image: ***/myproject/myapp:v1.0.0 ports: - containerPort: 8080 ``` 在这个示例中,`my-harbor-secret`是之前创建的Secret,`***/myproject/myapp:v1.0.0`是私有仓库中的镜像路径。 最后,使用`kubectl apply -f deploy.yml`命令将这些配置应用到集群中,Kubernetes将会根据这些配置拉取私有仓库中的镜像,并部署应用。 为了进一步确保集群的安全性,还可以配置LivenessProbeReadinessProbe以监控应用的健康状态,以及利用Ingress对象设置外部访问规则。这些高级配置可以通过查阅《K8s集群部署Docker-Harbor镜像:Node认证详解》来获取更详细的指导最佳实践。 参考资源链接:[K8s集群部署Docker-Harbor镜像:Node认证详解](https://wenku.csdn.net/doc/2q2vrzz0vq?utm_source=wenku_answer2doc_content)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值