Skip to content

Commit 62a7ab2

Browse files
committed
Merge branch 'main' into fixclose
2 parents 8db791b + 9b4ab82 commit 62a7ab2

File tree

10 files changed

+153
-59
lines changed

10 files changed

+153
-59
lines changed

cli/deployment/flags.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func Flags() *codersdk.DeploymentFlags {
8888
DerpServerRelayAddress: &codersdk.StringFlag{
8989
Name: "DERP Server Relay Address",
9090
Flag: "derp-server-relay-address",
91-
EnvVar: "CODER_DERP_SERVER_RELAY_ADDRESS",
91+
EnvVar: "CODER_DERP_SERVER_RELAY_URL",
9292
Description: "An HTTP address that is accessible by other replicas to relay DERP traffic. Required for high availability.",
9393
Enterprise: true,
9494
},

docs/admin/enterprise.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,32 @@
33
Coder is free to use and includes some features that are only accessible with a paid license.
44
Contact sales@coder.com to obtain a license.
55

6-
These features are available in the enterprise edition:
7-
8-
- [Audit Logging](./audit-logs.md)
9-
- [Browser Only Connections](../networking.md#browser-only-connections)
6+
### User Management
107
- [Groups](./groups.md)
118
- [Template RBAC](./rbac.md)
12-
- [Quotas](./quotas.md)
139
- [SCIM](./auth.md#scim)
1410

15-
And we're releasing these imminently:
11+
### Networking & Deployment
12+
- [High Availability](./high-availability.md)
13+
- [Browser Only Connections](../networking.md#browser-only-connections)
14+
15+
### Other
16+
- [Audit Logging](./audit-logs.md)
17+
- [Quotas](./quotas.md)
18+
19+
### Coming soon
1620

17-
- High Availability
1821
- Multiple Git Provider Authentication
22+
- Max Workspace Auto-Stop
1923

2024
## Adding your license key
2125

22-
### You will need:
26+
### Requirements
2327

2428
- Your license key (contact sales@coder.com if you don't have yours)
2529
- Coder CLI installed
2630

27-
### Steps:
31+
### Instructions
2832

2933
1. Save your license key to disk and make note of the path
3034
2. Open a terminal

docs/admin/high-availability.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# High Availability
2+
3+
High Availability (HA) mode solves for horizontal scalability and automatic failover
4+
within a single region. When in HA mode, Coder continues using a single Postgres
5+
endpoint. [GCP](https://cloud.google.com/sql/docs/postgres/high-availability), [AWS](https://docs.aws.amazon.com/prescriptive-guidance/latest/saas-multitenant-managed-postgresql/availability.html),
6+
and other cloud vendors offer fully-managed HA Postgres services that pair
7+
nicely with Coder.
8+
9+
For Coder to operate correctly, every node must be within 10ms of each other
10+
and Postgres. We make a best-effort attempt to warn the user when inter-coder
11+
latency is too high, but if requests start dropping, this is one metric to investigate.
12+
Note that this latency requirement applies _only_ to coder services. Coder will
13+
operate correctly even with few seconds of latency on
14+
workspace <-> coder and user <-> coder connections.
15+
16+
## Setup
17+
18+
> We're stress testing High Availability this week before we enable it by default. To try HA beforehand, set `CODER_EXPERIMENTAL=true` for the Coder server as well as the additional configuration options below.
19+
20+
Coder automatically enters HA mode when multiple instances simultaneously connect
21+
to the same Postgres endpoint.
22+
23+
HA brings one configuration variable to set in each Coder
24+
node: `CODER_DERP_SERVER_RELAY_URL`. The HA nodes use these URLs to communicate
25+
with each other. Inter-node communication is only required while using the
26+
embedded relay (default). If you're using [custom relays](../networking.md#custom-relays), Coder ignores `CODER_DERP_SERVER_RELAY_URL` since Postgres is the sole rendezvous for the Coder nodes.
27+
28+
`CODER_DERP_SERVER_RELAY_URL` will never be `CODER_ACCESS_URL` because
29+
`CODER_ACCESS_URL` is a load balancer to all Coder nodes.
30+
31+
Here's an example 3-node network configuration setup:
32+
33+
| Name | `CODER_ADDRESS` | `CODER_DERP_SERVER_RELAY_URL` | `CODER_ACCESS_URL` |
34+
| --------- | --------------- | ----------------------------- | ------------------------ |
35+
| `coder-1` | `*:80` | `http://10.0.0.1:80` | `https://coder.big.corp` |
36+
| `coder-2` | `*:80` | `http://10.0.0.2:80` | `https://coder.big.corp` |
37+
| `coder-3` | `*:80` | `http://10.0.0.3:80` | `https://coder.big.corp` |
38+
39+
## Kubernetes
40+
41+
If you installed Coder via
42+
[our Helm Chart](../install/kubernetes.md#install-coder-with-helm), just
43+
increase `coder.replicaCount` in `values.yaml`.
44+
45+
If you installed Coder into Kubernetes by some other means, insert the relay URL
46+
via the environment like so:
47+
48+
```yaml
49+
env:
50+
- name: POD_IP
51+
valueFrom:
52+
fieldRef:
53+
fieldPath: status.podIP
54+
- name: CODER_DERP_SERVER_RELAY_URL
55+
value: http://$(POD_IP)
56+
```
57+
58+
Then, increase the number of pods.
59+
60+
## Up next
61+
62+
- [Networking](../networking.md)
63+
- [Kubernetes](../install/kubernetes.md)
64+
- [Enterprise](./enterprise.md)

docs/images/icons/hydra.svg

Lines changed: 5 additions & 0 deletions
Loading

docs/manifest.json

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@
2727
"children": [
2828
{
2929
"title": "Install script",
30-
"description": "One-line install script for macOS and Linux.",
30+
"description": "One-line install script for macOS and Linux",
3131
"path": "./install/install.sh.md"
3232
},
3333
{
3434
"title": "System packages",
35-
"description": "System packages for Debian, Ubuntu, Fedora, CentOS, RHEL, SUSE, and Alpine.",
35+
"description": "System packages for Debian, Ubuntu, Fedora, CentOS, RHEL, SUSE, and Alpine",
3636
"path": "./install/packages.md"
3737
},
3838
{
3939
"title": "Kubernetes",
40-
"description": "Install Coder with Kubernetes via Helm.",
40+
"description": "Install Coder with Kubernetes via Helm",
4141
"path": "./install/kubernetes.md"
4242
},
4343
{
@@ -47,12 +47,12 @@
4747
},
4848
{
4949
"title": "Standalone binaries",
50-
"description": "Download binaries for macOS, Windows, and Linux.",
50+
"description": "Download binaries for macOS, Windows, and Linux",
5151
"path": "./install/binary.md"
5252
},
5353
{
5454
"title": "Uninstall",
55-
"description": "Learn how to uninstall Coder.",
55+
"description": "Learn how to uninstall Coder",
5656
"path": "./install/uninstall.md"
5757
}
5858
]
@@ -182,7 +182,7 @@
182182
"children": [
183183
{
184184
"title": "Authentication",
185-
"description": "Learn how to set up authentication using GitHub or OpenID Connect.",
185+
"description": "Learn how to set up authentication using GitHub or OpenID Connect",
186186
"icon_path": "./images/icons/key.svg",
187187
"path": "./admin/auth.md"
188188
},
@@ -208,13 +208,13 @@
208208
},
209209
{
210210
"title": "Configuration",
211-
"description": "Learn how to configure Coder.",
211+
"description": "Learn how to configure Coder",
212212
"path": "./admin/configure.md",
213213
"icon_path": "./images/icons/toggle_on.svg"
214214
},
215215
{
216216
"title": "Upgrading",
217-
"description": "Learn how to upgrade Coder.",
217+
"description": "Learn how to upgrade Coder",
218218
"icon_path": "./images/icons/upgrade.svg",
219219
"path": "./admin/upgrade.md"
220220
},
@@ -226,27 +226,34 @@
226226
},
227227
{
228228
"title": "Audit Logs",
229-
"description": "Learn how to use Audit Logs in your Coder deployment.",
229+
"description": "Learn how to use Audit Logs in your Coder deployment",
230230
"icon_path": "./images/icons/radar.svg",
231231
"path": "./admin/audit-logs.md",
232232
"state": "enterprise"
233233
},
234234
{
235235
"title": "Quotas",
236-
"description": "Learn how to use Workspace Quotas in Coder.",
236+
"description": "Learn how to use Workspace Quotas in Coder",
237237
"icon_path": "./images/icons/dollar.svg",
238238
"path": "./admin/quotas.md",
239239
"state": "enterprise"
240240
},
241+
{
242+
"title": "High Availability",
243+
"description": "Learn how to configure Coder for High Availability",
244+
"icon_path": "./images/icons/hydra.svg",
245+
"path": "./admin/high-availability.md",
246+
"state": "enterprise"
247+
},
241248
{
242249
"title": "Enterprise",
243-
"description": "Learn how to enable Enterprise features.",
250+
"description": "Learn how to enable Enterprise features",
244251
"icon_path": "./images/icons/group.svg",
245252
"path": "./admin/enterprise.md"
246253
},
247254
{
248255
"title": "Telemetry",
249-
"description": "Learn what usage telemetry Coder collects.",
256+
"description": "Learn what usage telemetry Coder collects",
250257
"icon_path": "./images/icons/science.svg",
251258
"path": "./admin/telemetry.md"
252259
}

enterprise/coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
224224
if err != nil {
225225
return err
226226
}
227+
entitlements.Experimental = api.Experimental
227228

228229
featureChanged := func(featureName string) (changed bool, enabled bool) {
229230
if api.entitlements.Features == nil {

enterprise/replicasync/replicasync.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,14 @@ func (m *Manager) Self() database.Replica {
315315
func (m *Manager) All() []database.Replica {
316316
m.mutex.Lock()
317317
defer m.mutex.Unlock()
318-
return append(m.peers[:], m.self)
318+
replicas := make([]database.Replica, 0, len(m.peers))
319+
for _, replica := range append(m.peers, m.self) {
320+
// When we assign the non-pointer to a
321+
// variable it loses the reference.
322+
replica := replica
323+
replicas = append(replicas, replica)
324+
}
325+
return replicas
319326
}
320327

321328
// Regional returns all replicas in the same region excluding itself.

helm/templates/coder.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ spec:
4040
valueFrom:
4141
fieldRef:
4242
fieldPath: status.podIP
43-
- name: CODER_DERP_SERVER_RELAY_ADDRESS
43+
- name: CODER_DERP_SERVER_RELAY_URL
4444
value: "{{ include "coder.portName" . }}://$(KUBE_POD_IP):{{ include "coder.port" . }}"
4545
{{- include "coder.tlsEnv" . | nindent 12 }}
4646
{{- with .Values.coder.env -}}

site/src/components/WorkspaceBuildProgress/WorkspaceBuildProgress.tsx

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,19 @@ dayjs.extend(duration)
1111

1212
const estimateFinish = (
1313
startedAt: Dayjs,
14-
templateAverage?: number,
14+
buildEstimate: number,
1515
): [number, string] => {
16-
if (templateAverage === undefined) {
17-
return [0, "Unknown"]
18-
}
19-
const realPercentage = dayjs().diff(startedAt) / templateAverage
16+
const realPercentage = dayjs().diff(startedAt) / buildEstimate
2017

21-
// Showing a full bar is frustrating.
22-
const maxPercentage = 0.99
18+
const maxPercentage = 1
2319
if (realPercentage > maxPercentage) {
24-
return [maxPercentage, "Any moment now..."]
20+
return [maxPercentage * 100, "Any moment now..."]
2521
}
2622

2723
return [
28-
realPercentage,
24+
realPercentage * 100,
2925
`~${Math.ceil(
30-
dayjs.duration((1 - realPercentage) * templateAverage).asSeconds(),
26+
dayjs.duration((1 - realPercentage) * buildEstimate).asSeconds(),
3127
)} seconds remaining...`,
3228
]
3329
}
@@ -62,49 +58,55 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
6258
}) => {
6359
const styles = useStyles()
6460
const job = workspace.latest_build.job
65-
const [progressValue, setProgressValue] = useState(0)
61+
const [progressValue, setProgressValue] = useState<number | undefined>(0)
6662

6763
// By default workspace is updated every second, which can cause visual stutter
6864
// when the build estimate is a few seconds. The timer ensures no observable
6965
// stutter in all cases.
7066
useEffect(() => {
7167
const updateProgress = () => {
72-
if (job.status !== "running") {
73-
setProgressValue(0)
68+
if (job.status !== "running" || buildEstimate === undefined) {
69+
setProgressValue(undefined)
7470
return
7571
}
76-
setProgressValue(
77-
estimateFinish(dayjs(job.started_at), buildEstimate)[0] * 100,
78-
)
72+
const est = estimateFinish(dayjs(job.started_at), buildEstimate)[0]
73+
setProgressValue(est)
7974
}
80-
setTimeout(updateProgress, 100)
75+
setTimeout(updateProgress, 5)
8176
}, [progressValue, job, buildEstimate])
8277

83-
// buildEstimate may be undefined if the template is new or coderd hasn't
84-
// finished initial metrics collection.
85-
if (buildEstimate === undefined) {
86-
return (
87-
<div className={styles.stack}>
88-
<LinearProgress value={0} variant="indeterminate" />
89-
<div className={styles.barHelpers}>
90-
<div className={styles.label}>{`Build ${job.status}`}</div>
91-
<div className={styles.label}>Unknown ETA</div>
92-
</div>
93-
</div>
94-
)
95-
}
96-
9778
return (
9879
<div className={styles.stack}>
9980
<LinearProgress
100-
value={(job.status === "running" && progressValue) || 0}
101-
variant={job.status === "running" ? "determinate" : "indeterminate"}
81+
value={progressValue !== undefined ? progressValue : 0}
82+
variant={
83+
// There is an initial state where progressValue may be undefined
84+
// (e.g. the build isn't yet running). If we flicker from the
85+
// indeterminate bar to the determinate bar, the vigilant user
86+
// perceives the bar jumping from 100% to 0%.
87+
progressValue !== undefined &&
88+
progressValue < 100 &&
89+
buildEstimate !== undefined
90+
? "determinate"
91+
: "indeterminate"
92+
}
93+
// If a transition is set, there is a moment on new load where the
94+
// bar accelerates to progressValue and then rapidly decelerates, which
95+
// is not indicative of true progress.
96+
classes={{ bar: styles.noTransition }}
10297
/>
10398
<div className={styles.barHelpers}>
10499
<div className={styles.label}>{`Build ${job.status}`}</div>
105100
<div className={styles.label}>
106-
{job.status === "running" &&
107-
estimateFinish(dayjs(job.started_at), buildEstimate)[1]}
101+
{(() => {
102+
if (job.status !== "running") {
103+
return ""
104+
} else if (buildEstimate !== undefined) {
105+
return estimateFinish(dayjs(job.started_at), buildEstimate)[1]
106+
} else {
107+
return "Unknown ETA"
108+
}
109+
})()}
108110
</div>
109111
</div>
110112
</div>
@@ -116,6 +118,9 @@ const useStyles = makeStyles((theme) => ({
116118
paddingLeft: theme.spacing(0.2),
117119
paddingRight: theme.spacing(0.2),
118120
},
121+
noTransition: {
122+
transition: "none",
123+
},
119124
barHelpers: {
120125
display: "flex",
121126
justifyContent: "space-between",

tailnet/coordinator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ func (c *coordinator) handleNextAgentMessage(id uuid.UUID, decoder *json.Decoder
313313
}
314314
data, err := json.Marshal([]*Node{&node})
315315
if err != nil {
316+
c.mutex.Unlock()
316317
return xerrors.Errorf("marshal nodes: %w", err)
317318
}
318319

0 commit comments

Comments
 (0)