Skip to content

Commit 491311b

Browse files
author
gitlayzer
committed
release: prepare v0.0.31
1 parent aaf2005 commit 491311b

27 files changed

Lines changed: 1232 additions & 255 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
/.workflow/
88

99
# Generated copyright application artifacts
10+
/docs/
1011
/docs/copyright/
1112

1213
# IDE settings

README.md

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,53 @@ curl http://10.96.0.12:8080 # Service ClusterIP
380380
curl http://10.244.0.22:3000 # Pod IP
381381
```
382382

383+
### 7. 计费说明
384+
Sealtun 本身不额外单独收取一笔“软件费”,实际成本来自 Sealos Cloud 为隧道远端 Pod 和公网入口分配的云资源。CLI 目前会在 `resources` / `dashboard` 里展示资源占用提示,但那不是账单估算;真实费用仍以 Sealos Cloud 控制台的计费表和账单为准。
385+
386+
结合当前 Sealos Cloud 价格页,你至少可以按下面几个维度理解 Sealtun 隧道成本:
387+
388+
- `CPU`:按核 / 小时计费
389+
- `内存`:按 GB / 小时计费
390+
- `端口`:按个 / 小时计费
391+
- `网络`:按流量计费
392+
393+
Sealtun 的常见成本来源是:
394+
395+
- 一个远端 tunnel Pod:消耗 `CPU + 内存`
396+
- HTTP/HTTPS 隧道和 TCP/SSH NodePort 暴露:会占用 `端口`
397+
- 公网访问流量:会产生 `网络` 成本
398+
399+
不同区域单价不同。根据当前 Sealos Cloud 控制台截图,`杭州 H``新加坡 B``北京 A``广州 G``US West` 的 CPU / 内存 / 端口价格都不一样,所以同样一条隧道在不同区域的小时成本会有差异。
400+
401+
当前可直接参考下面这张小时单价表:
402+
403+
| 区域 | CPU (核/小时) | 内存 (GB/小时) | 端口 (个/小时) | 网络 |
404+
| --- | ---: | ---: | ---: | ---: |
405+
| `杭州 H` | `0.027671` | `0.013956` | `0.013900` | `0.000781 /M` |
406+
| `新加坡 B` | `0.067000` | `0.033792` | `0.013900` | `0.000781 /M` |
407+
| `北京 A` | `0.017125` | `0.008637` | `0.007000` | `0.000781 /M` |
408+
| `广州 G` | `0.017420` | `0.008786` | `0.007000` | `0.000781 /M` |
409+
| `US West` | `0.020833` | `0.012500` | `0.006944` | `0.000107 /M` |
410+
411+
如果只做一个最小 HTTPS 隧道,通常至少会涉及:
412+
413+
- 1 个远端 Pod 的 `CPU + 内存`
414+
- 1 个公网入口端口
415+
- 实际产生的公网流量
416+
417+
估算时可以用这个思路:
418+
419+
```text
420+
总成本 ~= Pod(CPU + 内存) + 公网端口 + 网络流量
421+
```
422+
423+
如果你要压低成本,优先考虑:
424+
425+
- 关闭不用的隧道,或用 `sealtun stop` 把副本缩容为 0
426+
-`sealtun resources set/unset` 调低 Pod requests / limits
427+
- 避免给短时调试隧道分配过高资源
428+
- 对低流量场景优先按需开启,而不是长期常驻
429+
383430
查看或停止当前 cluster connect:
384431
```bash
385432
sealtun connect status
@@ -389,7 +436,7 @@ sudo sealtun disconnect
389436

390437
限制:当前透明数据面仅支持 Linux + TCP,需要 root 权限和 `iptables`;不支持 ICMP/ping 和 UDP。macOS/Windows 会明确提示暂不支持。
391438

392-
### 7. 观测和本地控制台
439+
### 8. 观测和本地控制台
393440
查看远端隧道 Pod 日志:
394441
```bash
395442
sealtun logs <tunnel-id>
@@ -487,7 +534,7 @@ sealtun dashboard --addr 0.0.0.0 --allow-remote --basic-auth-user admin --basic-
487534

488535
远程模式不会把 dashboard token 写进 HTML;访问者需要 URL fragment 或请求头中的 token。启用 dashboard Basic Auth 后,HTML、静态资源和 API 都会先经过 Basic Auth。所有写操作仍要求页面确认,并由后端再次校验 `confirm` 字段,避免误触或脚本误调用。
489536

490-
### 8. 协议模板
537+
### 9. 协议模板
491538
不确定该怎么写命令或声明式配置时,可以先生成模板:
492539

493540
```bash
@@ -500,7 +547,7 @@ sealtun template mongodb
500547

501548
模板会同时输出一次性 `sealtun expose` 命令和可提交到项目内的 `sealtun.yaml` 片段。`mysql``postgres``redis``mongodb``mqtt` 模板默认走通用 TCP 四层入口;HTTPS 模板才支持自定义域名和访问控制。
502549

503-
### 9. 声明式配置
550+
### 10. 声明式配置
504551
创建 `sealtun.yaml`
505552
```yaml
506553
version: v1

README_EN.md

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,54 @@ sudo sealtun disconnect
389389

390390
Limitations: transparent data-plane support is Linux + TCP only and requires root plus `iptables`; ICMP/ping and UDP are not supported. macOS/Windows fail clearly as unsupported for now.
391391

392-
### 7. Observe tunnels and run the local dashboard
392+
### 7. Pricing
393+
Sealtun itself does not charge a separate software fee. The actual cost comes from the Sealos Cloud resources allocated to the remote tunnel Pod and the public entrypoint. The CLI can show resource occupancy hints in `resources` and `dashboard`, but that is not billing estimation; the authoritative source is still the Sealos Cloud pricing page and your actual bill.
394+
395+
Based on the current Sealos Cloud pricing page, Sealtun tunnel cost is easiest to understand through these dimensions:
396+
397+
- `CPU`: billed per core / hour
398+
- `Memory`: billed per GB / hour
399+
- `Port`: billed per public port / hour
400+
- `Network`: billed by traffic usage
401+
402+
Common Sealtun cost sources are:
403+
404+
- one remote tunnel Pod: `CPU + Memory`
405+
- HTTP/HTTPS tunnels and TCP/SSH NodePort exposure: `Port`
406+
- public traffic served through the tunnel: `Network`
407+
408+
Unit prices differ by region. Based on the current Sealos Cloud console screenshots, `Hangzhou H`, `Singapore B`, `Beijing A`, `Guangzhou G`, and `US West` all have different CPU, memory, and port prices, so the same tunnel can have different hourly cost across regions.
409+
410+
You can currently use this hourly price table as a direct reference:
411+
412+
| Region | CPU (core/hour) | Memory (GB/hour) | Port (port/hour) | Network |
413+
| --- | ---: | ---: | ---: | ---: |
414+
| `Hangzhou H` | `0.027671` | `0.013956` | `0.013900` | `0.000781 /M` |
415+
| `Singapore B` | `0.067000` | `0.033792` | `0.013900` | `0.000781 /M` |
416+
| `Beijing A` | `0.017125` | `0.008637` | `0.007000` | `0.000781 /M` |
417+
| `Guangzhou G` | `0.017420` | `0.008786` | `0.007000` | `0.000781 /M` |
418+
| `US West` | `0.020833` | `0.012500` | `0.006944` | `0.000107 /M` |
419+
420+
For a minimal HTTPS tunnel, you should usually expect at least:
421+
422+
- one remote Pod worth of `CPU + Memory`
423+
- one public entry port
424+
- the actual public traffic consumed
425+
426+
Use this rough formula when estimating:
427+
428+
```text
429+
total cost ~= Pod(CPU + Memory) + public port + network traffic
430+
```
431+
432+
To keep cost lower, prioritize:
433+
434+
- stopping unused tunnels, or using `sealtun stop` to scale replicas to 0
435+
- lowering Pod requests / limits with `sealtun resources set` or `unset`
436+
- avoiding oversized resources for short-lived debugging tunnels
437+
- opening low-traffic tunnels on demand instead of keeping them always on
438+
439+
### 8. Observe tunnels and run the local dashboard
393440
Show remote tunnel pod logs:
394441
```bash
395442
sealtun logs <tunnel-id>
@@ -487,7 +534,7 @@ sealtun dashboard --addr 0.0.0.0 --allow-remote --basic-auth-user admin --basic-
487534

488535
Remote mode does not embed the dashboard token in HTML; callers need the URL fragment token or request header. When dashboard Basic Auth is enabled, HTML, static assets, and APIs are all protected before the dashboard token layer. Every mutating action still requires a page confirmation and a backend-validated `confirm` field to avoid accidental clicks or scripted misuse.
489536

490-
### 8. Protocol templates
537+
### 9. Protocol templates
491538
When you are unsure which command or declarative config to use, generate a template first:
492539

493540
```bash
@@ -500,7 +547,7 @@ sealtun template mongodb
500547

501548
Templates print both a one-shot `sealtun expose` command and a `sealtun.yaml` snippet. `mysql`, `postgres`, `redis`, `mongodb`, and `mqtt` templates default to generic TCP L4 entries; only HTTPS templates support custom domains and access controls.
502549

503-
### 9. Declarative config
550+
### 10. Declarative config
504551
Create `sealtun.yaml`:
505552
```yaml
506553
version: v1

cmd/dashboard_api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ func dashboardTunnelAudit(ctx context.Context, tunnelID string, r *http.Request)
556556
}
557557

558558
func dashboardScopedSession(tunnelID string) (*session.TunnelSession, error) {
559-
sess, err := findSession(tunnelID)
559+
sess, err := findSessionRefreshed(context.Background(), tunnelID)
560560
if err != nil {
561561
return nil, err
562562
}

cmd/doctor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func collectDoctorPayload() (*doctorPayload, error) {
146146
}
147147

148148
func collectTunnelDoctorPayload(ctx context.Context, tunnelID string) (*tunnelDoctorPayload, error) {
149-
sess, err := findSession(tunnelID)
149+
sess, err := findSessionRefreshed(ctx, tunnelID)
150150
if err != nil {
151151
return nil, err
152152
}

cmd/domain.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ func planSessionCustomDomain(tunnelID, customDomain string) (*domainPayload, err
297297
if err != nil {
298298
return nil, err
299299
}
300+
refreshSessionFromRemote(context.Background(), sess)
300301
if !sessionSupportsCustomDomain(*sess) {
301302
return nil, fmt.Errorf("custom domains are only supported for https tunnels")
302303
}
@@ -325,6 +326,7 @@ func configureSessionCustomDomain(parent context.Context, tunnelID, customDomain
325326
if err != nil {
326327
return nil, err
327328
}
329+
refreshSessionFromRemote(parent, sess)
328330
if !sessionSupportsCustomDomain(*sess) {
329331
return nil, fmt.Errorf("custom domains are only supported for https tunnels")
330332
}
@@ -364,6 +366,7 @@ func clearSessionCustomDomain(parent context.Context, tunnelID string) (*domainP
364366
if err != nil {
365367
return nil, err
366368
}
369+
refreshSessionFromRemote(parent, sess)
367370
if !sessionSupportsCustomDomain(*sess) {
368371
return nil, fmt.Errorf("custom domains are only supported for https tunnels")
369372
}
@@ -477,6 +480,9 @@ func collectDomainStatusWithVerifier(parent context.Context, tunnelID string, ti
477480
if err != nil {
478481
return nil, fmt.Errorf("load tunnel sessions: %w", err)
479482
}
483+
for i := range list {
484+
refreshSessionFromRemote(parent, &list[i])
485+
}
480486
sessions = list
481487
}
482488

@@ -679,6 +685,7 @@ func verifySessionDomain(parent context.Context, tunnelID string) (*domainVerify
679685
if err != nil {
680686
return nil, err
681687
}
688+
refreshSessionFromRemote(parent, sess)
682689
if !sessionSupportsCustomDomain(*sess) {
683690
return nil, fmt.Errorf("custom domains are only supported for https tunnels")
684691
}
@@ -696,6 +703,7 @@ func waitForSessionDomain(parent context.Context, tunnelID string, timeout time.
696703
if err != nil {
697704
return nil, err
698705
}
706+
refreshSessionFromRemote(parent, sess)
699707
if !sessionSupportsCustomDomain(*sess) {
700708
return nil, fmt.Errorf("custom domains are only supported for https tunnels")
701709
}

cmd/events.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func init() {
5353
}
5454

5555
func collectEventsPayloadWithContext(ctx context.Context, tunnelID string, timeout time.Duration) (*eventsPayload, error) {
56-
sess, err := findSession(tunnelID)
56+
sess, err := findSessionRefreshed(ctx, tunnelID)
5757
if err != nil {
5858
return nil, err
5959
}

cmd/export.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"os"
@@ -74,7 +75,7 @@ func runExport(args []string) (*applyFile, []string, error) {
7475
var err error
7576
switch {
7677
case len(args) == 1:
77-
sess, err := findSession(args[0])
78+
sess, err := findSessionRefreshed(context.Background(), args[0])
7879
if err != nil {
7980
return nil, nil, err
8081
}
@@ -84,6 +85,9 @@ func runExport(args []string) (*applyFile, []string, error) {
8485
if err != nil {
8586
return nil, nil, fmt.Errorf("load tunnel sessions: %w", err)
8687
}
88+
for i := range sessions {
89+
refreshSessionFromRemote(context.Background(), &sessions[i])
90+
}
8791
default:
8892
return nil, nil, fmt.Errorf("provide a tunnel id or use --all")
8993
}

cmd/export_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package cmd
22

33
import (
4+
"context"
45
"testing"
56

7+
"github.com/labring/sealtun/pkg/accesspolicy"
8+
"github.com/labring/sealtun/pkg/k8s"
69
"github.com/labring/sealtun/pkg/session"
710
)
811

@@ -147,3 +150,44 @@ func TestExportSessionRejectsInvalidLocalPort(t *testing.T) {
147150
t.Fatal("expected warning for invalid local port")
148151
}
149152
}
153+
154+
func TestRunExportRefreshesRemoteState(t *testing.T) {
155+
home := t.TempDir()
156+
t.Setenv("HOME", home)
157+
158+
originalCollector := collectSessionRemoteState
159+
collectSessionRemoteState = func(ctx context.Context, sess session.TunnelSession) (*k8s.TunnelRemoteState, error) {
160+
return &k8s.TunnelRemoteState{
161+
Protocol: "https",
162+
TargetURL: "https://api.internal:8443",
163+
AccessPolicy: &accesspolicy.Policy{RateLimit: "60/m"},
164+
DeploymentOK: true,
165+
AuthSecretOK: true,
166+
}, nil
167+
}
168+
t.Cleanup(func() { collectSessionRemoteState = originalCollector })
169+
170+
if err := session.Save(session.TunnelSession{
171+
TunnelID: "web",
172+
Protocol: "https",
173+
LocalPort: "3000",
174+
Region: "https://gzg.sealos.run",
175+
Namespace: "ns-demo",
176+
}); err != nil {
177+
t.Fatal(err)
178+
}
179+
180+
config, _, err := runExport([]string{"web"})
181+
if err != nil {
182+
t.Fatal(err)
183+
}
184+
if len(config.Tunnels) != 1 {
185+
t.Fatalf("expected one tunnel, got %#v", config.Tunnels)
186+
}
187+
if config.Tunnels[0].Target != "https://api.internal:8443" {
188+
t.Fatalf("expected refreshed remote target, got %#v", config.Tunnels[0])
189+
}
190+
if config.Tunnels[0].AccessPolicy == nil || config.Tunnels[0].AccessPolicy.RateLimit != "60/m" {
191+
t.Fatalf("expected refreshed remote access policy, got %#v", config.Tunnels[0].AccessPolicy)
192+
}
193+
}

cmd/inspect.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ func collectInspectPayloadWithContext(ctx context.Context, tunnelID string) (*in
9595
if err != nil {
9696
return nil, err
9797
}
98+
refreshSessionFromRemote(ctx, sess)
9899
ensureSessionPublicPort(ctx, sess)
99100

100101
snapshot := classifySession(*sess, true)

0 commit comments

Comments
 (0)